mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
Run cargo fmt on the whole code base (#9394)
* Run cargo fmt on the whole code base * Second run * Add CI check * Fix compilation * More unnecessary braces * Handle weights * Use --all * Use correct attributes... * Fix UI tests * AHHHHHHHHH * 🤦 * Docs * Fix compilation * 🤷 * Please stop * 🤦 x 2 * More * make rustfmt.toml consistent with polkadot Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
@@ -19,23 +19,26 @@
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use super::*;
|
||||
use sp_runtime::traits::Bounded;
|
||||
use frame_system::RawOrigin as SystemOrigin;
|
||||
use frame_benchmarking::{
|
||||
benchmarks_instance_pallet, account, whitelisted_caller, whitelist_account, impl_benchmark_test_suite
|
||||
account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelist_account,
|
||||
whitelisted_caller,
|
||||
};
|
||||
use frame_support::traits::Get;
|
||||
use frame_support::{traits::EnsureOrigin, dispatch::UnfilteredDispatchable};
|
||||
use frame_support::{
|
||||
dispatch::UnfilteredDispatchable,
|
||||
traits::{EnsureOrigin, Get},
|
||||
};
|
||||
use frame_system::RawOrigin as SystemOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use crate::Pallet as Assets;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn create_default_asset<T: Config<I>, I: 'static>(is_sufficient: bool)
|
||||
-> (T::AccountId, <T::Lookup as StaticLookup>::Source)
|
||||
{
|
||||
fn create_default_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
) -> (T::AccountId, <T::Lookup as StaticLookup>::Source) {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let root = SystemOrigin::Root.into();
|
||||
@@ -45,14 +48,16 @@ fn create_default_asset<T: Config<I>, I: 'static>(is_sufficient: bool)
|
||||
caller_lookup.clone(),
|
||||
is_sufficient,
|
||||
1u32.into(),
|
||||
).is_ok());
|
||||
)
|
||||
.is_ok());
|
||||
(caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn create_default_minted_asset<T: Config<I>, I: 'static>(is_sufficient: bool, amount: T::Balance)
|
||||
-> (T::AccountId, <T::Lookup as StaticLookup>::Source)
|
||||
{
|
||||
let (caller, caller_lookup) = create_default_asset::<T, I>(is_sufficient);
|
||||
fn create_default_minted_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
amount: T::Balance,
|
||||
) -> (T::AccountId, <T::Lookup as StaticLookup>::Source) {
|
||||
let (caller, caller_lookup) = create_default_asset::<T, I>(is_sufficient);
|
||||
if !is_sufficient {
|
||||
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance());
|
||||
}
|
||||
@@ -61,14 +66,17 @@ fn create_default_minted_asset<T: Config<I>, I: 'static>(is_sufficient: bool, am
|
||||
Default::default(),
|
||||
caller_lookup.clone(),
|
||||
amount,
|
||||
).is_ok());
|
||||
)
|
||||
.is_ok());
|
||||
(caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn swap_is_sufficient<T: Config<I>, I: 'static>(s: &mut bool) {
|
||||
Asset::<T, I>::mutate(&T::AssetId::default(), |maybe_a|
|
||||
if let Some(ref mut a) = maybe_a { sp_std::mem::swap(s, &mut a.is_sufficient) }
|
||||
);
|
||||
Asset::<T, I>::mutate(&T::AssetId::default(), |maybe_a| {
|
||||
if let Some(ref mut a) = maybe_a {
|
||||
sp_std::mem::swap(s, &mut a.is_sufficient)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_consumers<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
@@ -79,7 +87,13 @@ fn add_consumers<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let target = account("consumer", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
assert!(Assets::<T, I>::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok());
|
||||
assert!(Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
Default::default(),
|
||||
target_lookup,
|
||||
100u32.into()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
}
|
||||
@@ -91,7 +105,13 @@ fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
for i in 0..n {
|
||||
let target = account("sufficient", i, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
assert!(Assets::<T, I>::mint(origin.clone().into(), Default::default(), target_lookup, 100u32.into()).is_ok());
|
||||
assert!(Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
Default::default(),
|
||||
target_lookup,
|
||||
100u32.into()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
}
|
||||
@@ -105,7 +125,8 @@ fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
Default::default(),
|
||||
minter_lookup,
|
||||
(100 * (n + 1)).into(),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
for i in 0..n {
|
||||
let target = account("approval", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
|
||||
@@ -115,7 +136,8 @@ fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
Default::default(),
|
||||
target_lookup,
|
||||
100u32.into(),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ pub struct ExtraMutator<T: Config<I>, I: 'static = ()> {
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for ExtraMutator<T, I> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(
|
||||
self.commit().is_ok(),
|
||||
"attempt to write to non-existent asset account"
|
||||
);
|
||||
debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
|
||||
/// Get the total supply of an asset `id`.
|
||||
pub fn total_supply(id: T::AssetId) -> T::Balance {
|
||||
Asset::<T, I>::get(id)
|
||||
.map(|x| x.supply)
|
||||
.unwrap_or_else(Zero::zero)
|
||||
Asset::<T, I>::get(id).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
pub(super) fn new_account(
|
||||
@@ -134,7 +132,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
match frozen.checked_add(&details.min_balance) {
|
||||
Some(required) if rest < required => return Frozen,
|
||||
None => return Overflow,
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,9 +169,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
|
||||
let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) {
|
||||
// Frozen balance: account CANNOT be deleted
|
||||
let required = frozen
|
||||
.checked_add(&details.min_balance)
|
||||
.ok_or(ArithmeticError::Overflow)?;
|
||||
let required =
|
||||
frozen.checked_add(&details.min_balance).ok_or(ArithmeticError::Overflow)?;
|
||||
account.balance.saturating_sub(required)
|
||||
} else {
|
||||
let is_provider = false;
|
||||
@@ -219,7 +216,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Err(e) => {
|
||||
debug_assert!(false, "passed from reducible_balance; qed");
|
||||
return Err(e.into())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(actual)
|
||||
@@ -268,12 +265,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
) -> DispatchResult {
|
||||
Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult {
|
||||
if let Some(check_issuer) = maybe_check_issuer {
|
||||
ensure!(
|
||||
&check_issuer == &details.issuer,
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
ensure!(&check_issuer == &details.issuer, Error::<T, I>::NoPermission);
|
||||
}
|
||||
debug_assert!(T::Balance::max_value() - details.supply >= amount, "checked in prep; qed");
|
||||
debug_assert!(
|
||||
T::Balance::max_value() - details.supply >= amount,
|
||||
"checked in prep; qed"
|
||||
);
|
||||
details.supply = details.supply.saturating_add(amount);
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -295,7 +292,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
|
||||
) -> DispatchResult,
|
||||
) -> DispatchResult {
|
||||
if amount.is_zero() { return Ok(()) }
|
||||
if amount.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
Self::can_increase(id, beneficiary, amount).into_result()?;
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
@@ -364,7 +363,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
&mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
|
||||
) -> DispatchResult,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
if amount.is_zero() { return Ok(amount) }
|
||||
if amount.is_zero() {
|
||||
return Ok(amount)
|
||||
}
|
||||
|
||||
let actual = Self::prep_debit(id, target, amount, f)?;
|
||||
|
||||
|
||||
@@ -24,15 +24,11 @@ impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset)
|
||||
.map(|x| x.supply)
|
||||
.unwrap_or_else(Zero::zero)
|
||||
Asset::<T, I>::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset)
|
||||
.map(|x| x.min_balance)
|
||||
.unwrap_or_else(Zero::zero)
|
||||
Asset::<T, I>::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
@@ -78,10 +74,7 @@ impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId>
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
};
|
||||
let f = DebitFlags { keep_alive: false, best_effort: false };
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
|
||||
@@ -90,10 +83,7 @@ impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId>
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: false,
|
||||
best_effort: true,
|
||||
};
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
Self::do_burn(asset, who, amount, None, f)
|
||||
}
|
||||
}
|
||||
@@ -106,11 +96,7 @@ impl<T: Config<I>, I: 'static> fungibles::Transfer<T::AccountId> for Pallet<T, I
|
||||
amount: T::Balance,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let f = TransferFlags {
|
||||
keep_alive,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(asset, source, dest, amount, None, f)
|
||||
}
|
||||
}
|
||||
@@ -126,28 +112,35 @@ impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T,
|
||||
}
|
||||
});
|
||||
}
|
||||
fn decrease_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Result<Self::Balance, DispatchError>
|
||||
{
|
||||
fn decrease_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: false };
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
}
|
||||
fn decrease_balance_at_most(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Self::Balance
|
||||
{
|
||||
fn decrease_balance_at_most(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Self::Balance {
|
||||
let f = DebitFlags { keep_alive: false, best_effort: true };
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
.unwrap_or(Zero::zero())
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())).unwrap_or(Zero::zero())
|
||||
}
|
||||
fn increase_balance(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Result<Self::Balance, DispatchError>
|
||||
{
|
||||
fn increase_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
|
||||
Ok(amount)
|
||||
}
|
||||
fn increase_balance_at_most(asset: T::AssetId, who: &T::AccountId, amount: Self::Balance)
|
||||
-> Self::Balance
|
||||
{
|
||||
fn increase_balance_at_most(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Self::Balance {
|
||||
match Self::increase_balance(asset, who, amount, |_| Ok(())) {
|
||||
Ok(()) => amount,
|
||||
Err(_) => Zero::zero(),
|
||||
|
||||
+100
-120
@@ -122,40 +122,49 @@
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod weights;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
#[cfg(test)]
|
||||
pub mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
mod extra_mutator;
|
||||
pub use extra_mutator::*;
|
||||
mod impl_stored_map;
|
||||
mod impl_fungibles;
|
||||
mod functions;
|
||||
mod impl_fungibles;
|
||||
mod impl_stored_map;
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
use sp_std::{prelude::*, borrow::Borrow, convert::TryInto};
|
||||
use sp_runtime::{
|
||||
TokenError, ArithmeticError,
|
||||
traits::{AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, Bounded}
|
||||
};
|
||||
use codec::HasCompact;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap};
|
||||
use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles};
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
ensure,
|
||||
traits::{
|
||||
tokens::{fungibles, DepositConsequence, WithdrawConsequence},
|
||||
BalanceStatus::Reserved,
|
||||
Currency, ReservableCurrency, StoredMap,
|
||||
},
|
||||
};
|
||||
use frame_system::Config as SystemConfig;
|
||||
use sp_runtime::{
|
||||
traits::{
|
||||
AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero,
|
||||
},
|
||||
ArithmeticError, TokenError,
|
||||
};
|
||||
use sp_std::{borrow::Borrow, convert::TryInto, prelude::*};
|
||||
|
||||
pub use weights::WeightInfo;
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
use frame_support::{dispatch::DispatchResult, pallet_prelude::*};
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
@@ -267,11 +276,7 @@ pub mod pallet {
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
#[pallet::metadata(
|
||||
T::AccountId = "AccountId",
|
||||
T::Balance = "Balance",
|
||||
T::AssetId = "AssetId"
|
||||
)]
|
||||
#[pallet::metadata(T::AccountId = "AccountId", T::Balance = "Balance", T::AssetId = "AssetId")]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Some asset class was created. \[asset_id, creator, owner\]
|
||||
Created(T::AssetId, T::AccountId, T::AccountId),
|
||||
@@ -514,13 +519,12 @@ pub mod pallet {
|
||||
}
|
||||
Self::deposit_event(Event::Destroyed(id));
|
||||
|
||||
Ok(
|
||||
Some(T::WeightInfo::destroy(
|
||||
details.accounts.saturating_sub(details.sufficients),
|
||||
details.sufficients,
|
||||
details.approvals,
|
||||
)).into()
|
||||
)
|
||||
Ok(Some(T::WeightInfo::destroy(
|
||||
details.accounts.saturating_sub(details.sufficients),
|
||||
details.sufficients,
|
||||
details.approvals,
|
||||
))
|
||||
.into())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -541,7 +545,7 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
beneficiary: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] amount: T::Balance
|
||||
#[pallet::compact] amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let beneficiary = T::Lookup::lookup(beneficiary)?;
|
||||
@@ -569,7 +573,7 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
who: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] amount: T::Balance
|
||||
#[pallet::compact] amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
@@ -602,16 +606,12 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
target: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] amount: T::Balance
|
||||
#[pallet::compact] amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(target)?;
|
||||
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ())
|
||||
}
|
||||
|
||||
@@ -638,16 +638,12 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
target: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] amount: T::Balance
|
||||
#[pallet::compact] amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let source = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(target)?;
|
||||
|
||||
let f = TransferFlags {
|
||||
keep_alive: true,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ())
|
||||
}
|
||||
|
||||
@@ -682,11 +678,7 @@ pub mod pallet {
|
||||
let source = T::Lookup::lookup(source)?;
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ())
|
||||
}
|
||||
|
||||
@@ -704,17 +696,14 @@ pub mod pallet {
|
||||
pub fn freeze(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
who: <T::Lookup as StaticLookup>::Source
|
||||
who: <T::Lookup as StaticLookup>::Source,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(&origin == &d.freezer, Error::<T, I>::NoPermission);
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
ensure!(
|
||||
Account::<T, I>::contains_key(id, &who),
|
||||
Error::<T, I>::BalanceZero
|
||||
);
|
||||
ensure!(Account::<T, I>::contains_key(id, &who), Error::<T, I>::BalanceZero);
|
||||
|
||||
Account::<T, I>::mutate(id, &who, |a| a.is_frozen = true);
|
||||
|
||||
@@ -735,19 +724,15 @@ pub mod pallet {
|
||||
#[pallet::weight(T::WeightInfo::thaw())]
|
||||
pub fn thaw(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact]
|
||||
id: T::AssetId,
|
||||
who: <T::Lookup as StaticLookup>::Source
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
who: <T::Lookup as StaticLookup>::Source,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(&origin == &details.admin, Error::<T, I>::NoPermission);
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
ensure!(
|
||||
Account::<T, I>::contains_key(id, &who),
|
||||
Error::<T, I>::BalanceZero
|
||||
);
|
||||
ensure!(Account::<T, I>::contains_key(id, &who), Error::<T, I>::BalanceZero);
|
||||
|
||||
Account::<T, I>::mutate(id, &who, |a| a.is_frozen = false);
|
||||
|
||||
@@ -767,7 +752,7 @@ pub mod pallet {
|
||||
#[pallet::weight(T::WeightInfo::freeze_asset())]
|
||||
pub fn freeze_asset(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
@@ -794,7 +779,7 @@ pub mod pallet {
|
||||
#[pallet::weight(T::WeightInfo::thaw_asset())]
|
||||
pub fn thaw_asset(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
@@ -832,7 +817,7 @@ pub mod pallet {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(&origin == &details.owner, Error::<T, I>::NoPermission);
|
||||
if details.owner == owner {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let metadata_deposit = Metadata::<T, I>::get(id).deposit;
|
||||
@@ -912,14 +897,10 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let bounded_name: BoundedVec<u8, T::StringLimit> = name
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
let bounded_symbol: BoundedVec<u8, T::StringLimit> = symbol
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
let bounded_name: BoundedVec<u8, T::StringLimit> =
|
||||
name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
let bounded_symbol: BoundedVec<u8, T::StringLimit> =
|
||||
symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(&origin == &d.owner, Error::<T, I>::NoPermission);
|
||||
@@ -1008,15 +989,11 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
T::ForceOrigin::ensure_origin(origin)?;
|
||||
|
||||
let bounded_name: BoundedVec<u8, T::StringLimit> = name
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
let bounded_name: BoundedVec<u8, T::StringLimit> =
|
||||
name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
|
||||
let bounded_symbol: BoundedVec<u8, T::StringLimit> = symbol
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
let bounded_symbol: BoundedVec<u8, T::StringLimit> =
|
||||
symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
|
||||
ensure!(Asset::<T, I>::contains_key(id), Error::<T, I>::Unknown);
|
||||
Metadata::<T, I>::try_mutate_exists(id, |metadata| {
|
||||
@@ -1145,25 +1122,28 @@ pub mod pallet {
|
||||
|
||||
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(!d.is_frozen, Error::<T, I>::Frozen);
|
||||
Approvals::<T, I>::try_mutate((id, &owner, &delegate), |maybe_approved| -> DispatchResult {
|
||||
let mut approved = match maybe_approved.take() {
|
||||
// an approval already exists and is being updated
|
||||
Some(a) => a,
|
||||
// a new approval is created
|
||||
None => {
|
||||
d.approvals.saturating_inc();
|
||||
Default::default()
|
||||
Approvals::<T, I>::try_mutate(
|
||||
(id, &owner, &delegate),
|
||||
|maybe_approved| -> DispatchResult {
|
||||
let mut approved = match maybe_approved.take() {
|
||||
// an approval already exists and is being updated
|
||||
Some(a) => a,
|
||||
// a new approval is created
|
||||
None => {
|
||||
d.approvals.saturating_inc();
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
let deposit_required = T::ApprovalDeposit::get();
|
||||
if approved.deposit < deposit_required {
|
||||
T::Currency::reserve(&owner, deposit_required - approved.deposit)?;
|
||||
approved.deposit = deposit_required;
|
||||
}
|
||||
};
|
||||
let deposit_required = T::ApprovalDeposit::get();
|
||||
if approved.deposit < deposit_required {
|
||||
T::Currency::reserve(&owner, deposit_required - approved.deposit)?;
|
||||
approved.deposit = deposit_required;
|
||||
}
|
||||
approved.amount = approved.amount.saturating_add(amount);
|
||||
*maybe_approved = Some(approved);
|
||||
Ok(())
|
||||
})?;
|
||||
approved.amount = approved.amount.saturating_add(amount);
|
||||
*maybe_approved = Some(approved);
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Asset::<T, I>::insert(id, d);
|
||||
Self::deposit_event(Event::ApprovedTransfer(id, owner, delegate, amount));
|
||||
|
||||
@@ -1192,7 +1172,8 @@ pub mod pallet {
|
||||
let owner = ensure_signed(origin)?;
|
||||
let delegate = T::Lookup::lookup(delegate)?;
|
||||
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
let approval = Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
|
||||
let approval =
|
||||
Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
|
||||
T::Currency::unreserve(&owner, approval.deposit);
|
||||
|
||||
d.approvals.saturating_dec();
|
||||
@@ -1234,7 +1215,8 @@ pub mod pallet {
|
||||
let owner = T::Lookup::lookup(owner)?;
|
||||
let delegate = T::Lookup::lookup(delegate)?;
|
||||
|
||||
let approval = Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
|
||||
let approval =
|
||||
Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
|
||||
T::Currency::unreserve(&owner, approval.deposit);
|
||||
d.approvals.saturating_dec();
|
||||
Asset::<T, I>::insert(id, d);
|
||||
@@ -1273,33 +1255,31 @@ pub mod pallet {
|
||||
let owner = T::Lookup::lookup(owner)?;
|
||||
let destination = T::Lookup::lookup(destination)?;
|
||||
|
||||
Approvals::<T, I>::try_mutate_exists((id, &owner, delegate), |maybe_approved| -> DispatchResult {
|
||||
let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
|
||||
let remaining = approved
|
||||
.amount
|
||||
.checked_sub(&amount)
|
||||
.ok_or(Error::<T, I>::Unapproved)?;
|
||||
Approvals::<T, I>::try_mutate_exists(
|
||||
(id, &owner, delegate),
|
||||
|maybe_approved| -> DispatchResult {
|
||||
let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
|
||||
let remaining =
|
||||
approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;
|
||||
|
||||
let f = TransferFlags {
|
||||
keep_alive: false,
|
||||
best_effort: false,
|
||||
burn_dust: false
|
||||
};
|
||||
Self::do_transfer(id, &owner, &destination, amount, None, f)?;
|
||||
let f =
|
||||
TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
||||
Self::do_transfer(id, &owner, &destination, amount, None, f)?;
|
||||
|
||||
if remaining.is_zero() {
|
||||
T::Currency::unreserve(&owner, approved.deposit);
|
||||
Asset::<T, I>::mutate(id, |maybe_details| {
|
||||
if let Some(details) = maybe_details {
|
||||
details.approvals.saturating_dec();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
approved.amount = remaining;
|
||||
*maybe_approved = Some(approved);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
if remaining.is_zero() {
|
||||
T::Currency::unreserve(&owner, approved.deposit);
|
||||
Asset::<T, I>::mutate(id, |maybe_details| {
|
||||
if let Some(details) = maybe_details {
|
||||
details.approvals.saturating_dec();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
approved.amount = remaining;
|
||||
*maybe_approved = Some(approved);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
use super::*;
|
||||
use crate as pallet_assets;
|
||||
|
||||
use frame_support::{construct_runtime, parameter_types};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
|
||||
use frame_support::{parameter_types, construct_runtime};
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
@@ -108,8 +111,7 @@ impl Config for Test {
|
||||
type Extra = ();
|
||||
}
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub(crate) enum Hook {
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
//! Tests for Assets pallet.
|
||||
|
||||
use super::*;
|
||||
use crate::{Error, mock::*};
|
||||
use sp_runtime::{TokenError, traits::ConvertInto};
|
||||
use frame_support::{assert_ok, assert_noop, traits::Currency};
|
||||
use crate::{mock::*, Error};
|
||||
use frame_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use pallet_balances::Error as BalancesError;
|
||||
use sp_runtime::{traits::ConvertInto, TokenError};
|
||||
|
||||
#[test]
|
||||
fn basic_minting_should_work() {
|
||||
@@ -151,13 +151,25 @@ fn force_cancel_approval_works() {
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
let e = Error::<Test>::NoPermission;
|
||||
assert_noop!(Assets::force_cancel_approval(Origin::signed(2), 0, 1, 2), e);
|
||||
assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 1, 1, 2), Error::<Test>::Unknown);
|
||||
assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 2, 2), Error::<Test>::Unknown);
|
||||
assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 3), Error::<Test>::Unknown);
|
||||
assert_noop!(
|
||||
Assets::force_cancel_approval(Origin::signed(1), 1, 1, 2),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
assert_noop!(
|
||||
Assets::force_cancel_approval(Origin::signed(1), 0, 2, 2),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
assert_noop!(
|
||||
Assets::force_cancel_approval(Origin::signed(1), 0, 1, 3),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
|
||||
assert_ok!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2));
|
||||
assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 0);
|
||||
assert_noop!(Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2), Error::<Test>::Unknown);
|
||||
assert_noop!(
|
||||
Assets::force_cancel_approval(Origin::signed(1), 0, 1, 2),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,7 +234,6 @@ fn destroy_with_bad_witness_should_not_work() {
|
||||
w.accounts += 2;
|
||||
w.sufficients += 2;
|
||||
assert_ok!(Assets::destroy(Origin::signed(1), 0, w));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,7 +270,10 @@ fn non_providing_should_work() {
|
||||
// ...or transfer...
|
||||
assert_noop!(Assets::transfer(Origin::signed(0), 0, 1, 50), TokenError::CannotCreate);
|
||||
// ...or force-transfer
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50), TokenError::CannotCreate);
|
||||
assert_noop!(
|
||||
Assets::force_transfer(Origin::signed(1), 0, 0, 1, 50),
|
||||
TokenError::CannotCreate
|
||||
);
|
||||
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
Balances::make_free_balance_be(&2, 100);
|
||||
@@ -278,7 +292,10 @@ fn min_balance_should_work() {
|
||||
// Cannot create a new account with a balance that is below minimum...
|
||||
assert_noop!(Assets::mint(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum);
|
||||
assert_noop!(Assets::transfer(Origin::signed(1), 0, 2, 9), TokenError::BelowMinimum);
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9), TokenError::BelowMinimum);
|
||||
assert_noop!(
|
||||
Assets::force_transfer(Origin::signed(1), 0, 1, 2, 9),
|
||||
TokenError::BelowMinimum
|
||||
);
|
||||
|
||||
// When deducting from an account to below minimum, it should be reaped.
|
||||
assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 91));
|
||||
@@ -333,7 +350,10 @@ fn transferring_enough_to_kill_source_when_keep_alive_should_fail() {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 10));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 1), 100);
|
||||
assert_noop!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91), Error::<Test>::BalanceLow);
|
||||
assert_noop!(
|
||||
Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 91),
|
||||
Error::<Test>::BalanceLow
|
||||
);
|
||||
assert_ok!(Assets::transfer_keep_alive(Origin::signed(1), 0, 2, 90));
|
||||
assert_eq!(Assets::balance(0, 1), 10);
|
||||
assert_eq!(Assets::balance(0, 2), 90);
|
||||
@@ -385,13 +405,19 @@ fn origin_guards_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_noop!(Assets::transfer_ownership(Origin::signed(2), 0, 2), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Assets::transfer_ownership(Origin::signed(2), 0, 2),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(Assets::set_team(Origin::signed(2), 0, 2, 2, 2), Error::<Test>::NoPermission);
|
||||
assert_noop!(Assets::freeze(Origin::signed(2), 0, 1), Error::<Test>::NoPermission);
|
||||
assert_noop!(Assets::thaw(Origin::signed(2), 0, 2), Error::<Test>::NoPermission);
|
||||
assert_noop!(Assets::mint(Origin::signed(2), 0, 2, 100), Error::<Test>::NoPermission);
|
||||
assert_noop!(Assets::burn(Origin::signed(2), 0, 1, 100), Error::<Test>::NoPermission);
|
||||
assert_noop!(Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Assets::force_transfer(Origin::signed(2), 0, 1, 2, 100),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_noop!(Assets::destroy(Origin::signed(2), 0, w), Error::<Test>::NoPermission);
|
||||
});
|
||||
@@ -410,7 +436,10 @@ fn transfer_owner_should_work() {
|
||||
assert_eq!(Balances::reserved_balance(&2), 1);
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
|
||||
assert_noop!(Assets::transfer_ownership(Origin::signed(1), 0, 1), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Assets::transfer_ownership(Origin::signed(1), 0, 1),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
|
||||
// Set metadata now and make sure that deposit gets transferred back.
|
||||
assert_ok!(Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12));
|
||||
@@ -513,25 +542,25 @@ fn set_metadata_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Cannot add metadata to unknown asset
|
||||
assert_noop!(
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12),
|
||||
Error::<Test>::Unknown,
|
||||
);
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12),
|
||||
Error::<Test>::Unknown,
|
||||
);
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
|
||||
// Cannot add metadata to unowned asset
|
||||
assert_noop!(
|
||||
Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
Assets::set_metadata(Origin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12),
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
|
||||
// Cannot add oversized metadata
|
||||
assert_noop!(
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12),
|
||||
Error::<Test>::BadMetadata,
|
||||
);
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 100], vec![0u8; 10], 12),
|
||||
Error::<Test>::BadMetadata,
|
||||
);
|
||||
assert_noop!(
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12),
|
||||
Error::<Test>::BadMetadata,
|
||||
);
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 10], vec![0u8; 100], 12),
|
||||
Error::<Test>::BadMetadata,
|
||||
);
|
||||
|
||||
// Successfully add metadata and take deposit
|
||||
Balances::make_free_balance_be(&1, 30);
|
||||
@@ -546,9 +575,9 @@ fn set_metadata_should_work() {
|
||||
|
||||
// Cannot over-reserve
|
||||
assert_noop!(
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12),
|
||||
BalancesError::<Test, _>::InsufficientBalance,
|
||||
);
|
||||
Assets::set_metadata(Origin::signed(1), 0, vec![0u8; 20], vec![0u8; 20], 12),
|
||||
BalancesError::<Test, _>::InsufficientBalance,
|
||||
);
|
||||
|
||||
// Clear Metadata
|
||||
assert!(Metadata::<Test>::contains_key(0));
|
||||
@@ -566,7 +595,6 @@ fn freezer_should_work() {
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 1), 100);
|
||||
|
||||
|
||||
// freeze 50 of it.
|
||||
set_frozen_balance(0, 1, 50);
|
||||
|
||||
@@ -624,45 +652,73 @@ fn imbalances_should_work() {
|
||||
#[test]
|
||||
fn force_metadata_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
//force set metadata works
|
||||
// force set metadata works
|
||||
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::force_set_metadata(Origin::root(), 0, vec![0u8; 10], vec![0u8; 10], 8, false));
|
||||
assert_ok!(Assets::force_set_metadata(
|
||||
Origin::root(),
|
||||
0,
|
||||
vec![0u8; 10],
|
||||
vec![0u8; 10],
|
||||
8,
|
||||
false
|
||||
));
|
||||
assert!(Metadata::<Test>::contains_key(0));
|
||||
|
||||
//overwrites existing metadata
|
||||
// overwrites existing metadata
|
||||
let asset_original_metadata = Metadata::<Test>::get(0);
|
||||
assert_ok!(Assets::force_set_metadata(Origin::root(), 0, vec![1u8; 10], vec![1u8; 10], 8, false));
|
||||
assert_ok!(Assets::force_set_metadata(
|
||||
Origin::root(),
|
||||
0,
|
||||
vec![1u8; 10],
|
||||
vec![1u8; 10],
|
||||
8,
|
||||
false
|
||||
));
|
||||
assert_ne!(Metadata::<Test>::get(0), asset_original_metadata);
|
||||
|
||||
//attempt to set metadata for non-existent asset class
|
||||
// attempt to set metadata for non-existent asset class
|
||||
assert_noop!(
|
||||
Assets::force_set_metadata(Origin::root(), 1, vec![0u8; 10], vec![0u8; 10], 8, false),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
|
||||
//string length limit check
|
||||
// string length limit check
|
||||
let limit = StringLimit::get() as usize;
|
||||
assert_noop!(
|
||||
Assets::force_set_metadata(Origin::root(), 0, vec![0u8; limit + 1], vec![0u8; 10], 8, false),
|
||||
Assets::force_set_metadata(
|
||||
Origin::root(),
|
||||
0,
|
||||
vec![0u8; limit + 1],
|
||||
vec![0u8; 10],
|
||||
8,
|
||||
false
|
||||
),
|
||||
Error::<Test>::BadMetadata
|
||||
);
|
||||
assert_noop!(
|
||||
Assets::force_set_metadata(Origin::root(), 0, vec![0u8; 10], vec![0u8; limit + 1], 8, false),
|
||||
Assets::force_set_metadata(
|
||||
Origin::root(),
|
||||
0,
|
||||
vec![0u8; 10],
|
||||
vec![0u8; limit + 1],
|
||||
8,
|
||||
false
|
||||
),
|
||||
Error::<Test>::BadMetadata
|
||||
);
|
||||
|
||||
//force clear metadata works
|
||||
// force clear metadata works
|
||||
assert!(Metadata::<Test>::contains_key(0));
|
||||
assert_ok!(Assets::force_clear_metadata(Origin::root(), 0));
|
||||
assert!(!Metadata::<Test>::contains_key(0));
|
||||
|
||||
//Error handles clearing non-existent asset class
|
||||
// Error handles clearing non-existent asset class
|
||||
assert_noop!(Assets::force_clear_metadata(Origin::root(), 1), Error::<Test>::Unknown);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_asset_status_should_work(){
|
||||
fn force_asset_status_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::make_free_balance_be(&1, 10);
|
||||
Balances::make_free_balance_be(&2, 10);
|
||||
@@ -670,28 +726,28 @@ fn force_asset_status_should_work(){
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 50));
|
||||
assert_ok!(Assets::mint(Origin::signed(1), 0, 2, 150));
|
||||
|
||||
//force asset status to change min_balance > balance
|
||||
// force asset status to change min_balance > balance
|
||||
assert_ok!(Assets::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, 100, true, false));
|
||||
assert_eq!(Assets::balance(0, 1), 50);
|
||||
|
||||
//account can recieve assets for balance < min_balance
|
||||
// account can recieve assets for balance < min_balance
|
||||
assert_ok!(Assets::transfer(Origin::signed(2), 0, 1, 1));
|
||||
assert_eq!(Assets::balance(0, 1), 51);
|
||||
|
||||
//account on outbound transfer will cleanup for balance < min_balance
|
||||
// account on outbound transfer will cleanup for balance < min_balance
|
||||
assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 1));
|
||||
assert_eq!(Assets::balance(0,1), 0);
|
||||
assert_eq!(Assets::balance(0, 1), 0);
|
||||
|
||||
//won't create new account with balance below min_balance
|
||||
// won't create new account with balance below min_balance
|
||||
assert_noop!(Assets::transfer(Origin::signed(2), 0, 3, 50), TokenError::BelowMinimum);
|
||||
|
||||
//force asset status will not execute for non-existent class
|
||||
// force asset status will not execute for non-existent class
|
||||
assert_noop!(
|
||||
Assets::force_asset_status(Origin::root(), 1, 1, 1, 1, 1, 90, true, false),
|
||||
Error::<Test>::Unknown
|
||||
);
|
||||
|
||||
//account drains to completion when funds dip below min_balance
|
||||
// account drains to completion when funds dip below min_balance
|
||||
assert_ok!(Assets::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, 110, true, false));
|
||||
assert_ok!(Assets::transfer(Origin::signed(2), 0, 1, 110));
|
||||
assert_eq!(Assets::balance(0, 1), 200);
|
||||
@@ -715,7 +771,10 @@ fn balance_conversion_should_work() {
|
||||
Err(ConversionError::AssetMissing)
|
||||
);
|
||||
assert_eq!(
|
||||
BalanceToAssetBalance::<Balances, Test, ConvertInto>::to_asset_balance(100, not_sufficient),
|
||||
BalanceToAssetBalance::<Balances, Test, ConvertInto>::to_asset_balance(
|
||||
100,
|
||||
not_sufficient
|
||||
),
|
||||
Err(ConversionError::AssetNotSufficient)
|
||||
);
|
||||
// 10 / 1 == 10 -> the conversion should 10x the value
|
||||
|
||||
@@ -21,18 +21,13 @@ use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use frame_support::traits::{fungible, tokens::BalanceConversion};
|
||||
use sp_runtime::{FixedPointNumber, FixedPointOperand, FixedU128};
|
||||
use sp_runtime::traits::Convert;
|
||||
use sp_runtime::{traits::Convert, FixedPointNumber, FixedPointOperand, FixedU128};
|
||||
|
||||
pub(super) type DepositBalanceOf<T, I = ()> =
|
||||
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)]
|
||||
pub struct AssetDetails<
|
||||
Balance,
|
||||
AccountId,
|
||||
DepositBalance,
|
||||
> {
|
||||
pub struct AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
pub(super) owner: AccountId,
|
||||
/// Can mint tokens.
|
||||
@@ -144,7 +139,9 @@ pub trait FrozenBalance<AssetId, AccountId, Balance> {
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
|
||||
fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> { None }
|
||||
fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> {
|
||||
None
|
||||
}
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
}
|
||||
|
||||
@@ -175,10 +172,7 @@ pub(super) struct DebitFlags {
|
||||
|
||||
impl From<TransferFlags> for DebitFlags {
|
||||
fn from(f: TransferFlags) -> Self {
|
||||
Self {
|
||||
keep_alive: f.keep_alive,
|
||||
best_effort: f.best_effort,
|
||||
}
|
||||
Self { keep_alive: f.keep_alive, best_effort: f.best_effort }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +199,7 @@ type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;
|
||||
/// minimum balance and the minimum asset balance.
|
||||
pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
|
||||
impl<F, T, CON, I> BalanceConversion<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
|
||||
for BalanceToAssetBalance<F, T, CON, I>
|
||||
for BalanceToAssetBalance<F, T, CON, I>
|
||||
where
|
||||
F: fungible::Inspect<AccountIdOf<T>>,
|
||||
T: Config<I>,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -42,16 +42,20 @@
|
||||
|
||||
mod tests;
|
||||
|
||||
use sp_std::{prelude::*, marker::PhantomData, ops::{Deref, DerefMut}};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
RuntimeDebugNoBound,
|
||||
traits::{Get, Currency, ReservableCurrency, BalanceStatus},
|
||||
weights::Weight,
|
||||
dispatch::DispatchResult,
|
||||
traits::{BalanceStatus, Currency, Get, ReservableCurrency},
|
||||
weights::Weight,
|
||||
RuntimeDebugNoBound,
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
/// Pending atomic swap operation.
|
||||
#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode)]
|
||||
@@ -93,14 +97,20 @@ pub struct BalanceSwapAction<AccountId, C: ReservableCurrency<AccountId>> {
|
||||
_marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<AccountId, C> BalanceSwapAction<AccountId, C> where C: ReservableCurrency<AccountId> {
|
||||
impl<AccountId, C> BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
/// Create a new swap action value of balance.
|
||||
pub fn new(value: <C as Currency<AccountId>>::Balance) -> Self {
|
||||
Self { value, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C> where C: ReservableCurrency<AccountId> {
|
||||
impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
type Target = <C as Currency<AccountId>>::Balance;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -108,14 +118,18 @@ impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C> where C: Reservable
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, C> DerefMut for BalanceSwapAction<AccountId, C> where C: ReservableCurrency<AccountId> {
|
||||
impl<AccountId, C> DerefMut for BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, AccountId, C> SwapAction<AccountId, T> for BalanceSwapAction<AccountId, C>
|
||||
where C: ReservableCurrency<AccountId>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
fn reserve(&self, source: &AccountId) -> DispatchResult {
|
||||
C::reserve(&source, self.value)
|
||||
@@ -138,9 +152,9 @@ pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
|
||||
/// Atomic swap's pallet configuration trait.
|
||||
#[pallet::config]
|
||||
@@ -168,9 +182,12 @@ pub mod pallet {
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
#[pallet::storage]
|
||||
pub type PendingSwaps<T: Config> = StorageDoubleMap<_,
|
||||
Twox64Concat, T::AccountId,
|
||||
Blake2_128Concat, HashedProof,
|
||||
pub type PendingSwaps<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Blake2_128Concat,
|
||||
HashedProof,
|
||||
PendingSwap<T>,
|
||||
>;
|
||||
|
||||
@@ -209,7 +226,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
/// Old name generated by `decl_event`.
|
||||
#[deprecated(note="use `Event` instead")]
|
||||
#[deprecated(note = "use `Event` instead")]
|
||||
pub type RawEvent<T> = Event<T>;
|
||||
|
||||
#[pallet::call]
|
||||
@@ -249,9 +266,7 @@ pub mod pallet {
|
||||
};
|
||||
PendingSwaps::<T>::insert(target.clone(), hashed_proof.clone(), swap.clone());
|
||||
|
||||
Self::deposit_event(
|
||||
Event::NewSwap(target, hashed_proof, swap)
|
||||
);
|
||||
Self::deposit_event(Event::NewSwap(target, hashed_proof, swap));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -274,25 +289,20 @@ pub mod pallet {
|
||||
proof: Vec<u8>,
|
||||
action: T::SwapAction,
|
||||
) -> DispatchResult {
|
||||
ensure!(
|
||||
proof.len() <= T::ProofLimit::get() as usize,
|
||||
Error::<T>::ProofTooLarge,
|
||||
);
|
||||
ensure!(proof.len() <= T::ProofLimit::get() as usize, Error::<T>::ProofTooLarge,);
|
||||
|
||||
let target = ensure_signed(origin)?;
|
||||
let hashed_proof = blake2_256(&proof);
|
||||
|
||||
let swap = PendingSwaps::<T>::get(&target, hashed_proof)
|
||||
.ok_or(Error::<T>::InvalidProof)?;
|
||||
let swap =
|
||||
PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::InvalidProof)?;
|
||||
ensure!(swap.action == action, Error::<T>::ClaimActionMismatch);
|
||||
|
||||
let succeeded = swap.action.claim(&swap.source, &target);
|
||||
|
||||
PendingSwaps::<T>::remove(target.clone(), hashed_proof.clone());
|
||||
|
||||
Self::deposit_event(
|
||||
Event::SwapClaimed(target, hashed_proof, succeeded)
|
||||
);
|
||||
Self::deposit_event(Event::SwapClaimed(target, hashed_proof, succeeded));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -311,12 +321,8 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let source = ensure_signed(origin)?;
|
||||
|
||||
let swap = PendingSwaps::<T>::get(&target, hashed_proof)
|
||||
.ok_or(Error::<T>::NotExist)?;
|
||||
ensure!(
|
||||
swap.source == source,
|
||||
Error::<T>::SourceMismatch,
|
||||
);
|
||||
let swap = PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::NotExist)?;
|
||||
ensure!(swap.source == source, Error::<T>::SourceMismatch,);
|
||||
ensure!(
|
||||
frame_system::Pallet::<T>::block_number() >= swap.end_block,
|
||||
Error::<T>::DurationNotPassed,
|
||||
@@ -325,9 +331,7 @@ pub mod pallet {
|
||||
swap.action.cancel(&swap.source);
|
||||
PendingSwaps::<T>::remove(&target, hashed_proof.clone());
|
||||
|
||||
Self::deposit_event(
|
||||
Event::SwapCancelled(target, hashed_proof)
|
||||
);
|
||||
Self::deposit_event(Event::SwapCancelled(target, hashed_proof));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -84,12 +84,7 @@ const B: u64 = 2;
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
let genesis = pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(A, 100),
|
||||
(B, 200),
|
||||
],
|
||||
};
|
||||
let genesis = pallet_balances::GenesisConfig::<Test> { balances: vec![(A, 100), (B, 200)] };
|
||||
genesis.assimilate_storage(&mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
@@ -112,7 +107,8 @@ fn two_party_successful_swap() {
|
||||
hashed_proof.clone(),
|
||||
BalanceSwapAction::new(50),
|
||||
1000,
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 - 50);
|
||||
assert_eq!(Balances::free_balance(B), 200);
|
||||
@@ -126,7 +122,8 @@ fn two_party_successful_swap() {
|
||||
hashed_proof.clone(),
|
||||
BalanceSwapAction::new(75),
|
||||
1000,
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100);
|
||||
assert_eq!(Balances::free_balance(B), 200 - 75);
|
||||
@@ -134,11 +131,8 @@ fn two_party_successful_swap() {
|
||||
|
||||
// A reveals the proof and claims the swap on chain2.
|
||||
chain2.execute_with(|| {
|
||||
AtomicSwap::claim_swap(
|
||||
Origin::signed(A),
|
||||
proof.to_vec(),
|
||||
BalanceSwapAction::new(75),
|
||||
).unwrap();
|
||||
AtomicSwap::claim_swap(Origin::signed(A), proof.to_vec(), BalanceSwapAction::new(75))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 + 75);
|
||||
assert_eq!(Balances::free_balance(B), 200 - 75);
|
||||
@@ -146,11 +140,8 @@ fn two_party_successful_swap() {
|
||||
|
||||
// B use the revealed proof to claim the swap on chain1.
|
||||
chain1.execute_with(|| {
|
||||
AtomicSwap::claim_swap(
|
||||
Origin::signed(B),
|
||||
proof.to_vec(),
|
||||
BalanceSwapAction::new(50),
|
||||
).unwrap();
|
||||
AtomicSwap::claim_swap(Origin::signed(B), proof.to_vec(), BalanceSwapAction::new(50))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 - 50);
|
||||
assert_eq!(Balances::free_balance(B), 200 + 50);
|
||||
|
||||
@@ -37,20 +37,22 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use codec::{Encode, Decode};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
Parameter, traits::{Get, FindAuthor, OneSessionHandler, OnTimestampSet}, ConsensusEngineId,
|
||||
traits::{FindAuthor, Get, OnTimestampSet, OneSessionHandler},
|
||||
ConsensusEngineId, Parameter,
|
||||
};
|
||||
use sp_consensus_aura::{AuthorityIndex, ConsensusLog, Slot, AURA_ENGINE_ID};
|
||||
use sp_runtime::{
|
||||
generic::DigestItem,
|
||||
traits::{IsMember, Member, SaturatedConversion, Saturating, Zero},
|
||||
RuntimeAppPublic,
|
||||
traits::{SaturatedConversion, Saturating, Zero, Member, IsMember}, generic::DigestItem,
|
||||
};
|
||||
use sp_consensus_aura::{AURA_ENGINE_ID, ConsensusLog, AuthorityIndex, Slot};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub mod migrations;
|
||||
mod mock;
|
||||
mod tests;
|
||||
pub mod migrations;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
@@ -63,7 +65,11 @@ pub mod pallet {
|
||||
#[pallet::config]
|
||||
pub trait Config: pallet_timestamp::Config + frame_system::Config {
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member + Parameter + RuntimeAppPublic + Default + MaybeSerializeDeserialize;
|
||||
type AuthorityId: Member
|
||||
+ Parameter
|
||||
+ RuntimeAppPublic
|
||||
+ Default
|
||||
+ MaybeSerializeDeserialize;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -123,10 +129,8 @@ impl<T: Config> Pallet<T> {
|
||||
fn change_authorities(new: Vec<T::AuthorityId>) {
|
||||
<Authorities<T>>::put(&new);
|
||||
|
||||
let log: DigestItem<T::Hash> = DigestItem::Consensus(
|
||||
AURA_ENGINE_ID,
|
||||
ConsensusLog::AuthoritiesChange(new).encode()
|
||||
);
|
||||
let log: DigestItem<T::Hash> =
|
||||
DigestItem::Consensus(AURA_ENGINE_ID, ConsensusLog::AuthoritiesChange(new).encode());
|
||||
<frame_system::Pallet<T>>::deposit_log(log.into());
|
||||
}
|
||||
|
||||
@@ -143,7 +147,7 @@ impl<T: Config> Pallet<T> {
|
||||
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
|
||||
for (id, mut data) in pre_runtime_digests {
|
||||
if id == AURA_ENGINE_ID {
|
||||
return Slot::decode(&mut data).ok();
|
||||
return Slot::decode(&mut data).ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,14 +170,16 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = T::AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, T::AuthorityId)>
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
Self::initialize_authorities(&authorities);
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, T::AuthorityId)>
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
// instant changes
|
||||
if changed {
|
||||
@@ -196,8 +202,9 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
}
|
||||
|
||||
impl<T: Config> FindAuthor<u32> for Pallet<T> {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32> where
|
||||
I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
for (id, mut data) in digests.into_iter() {
|
||||
if id == AURA_ENGINE_ID {
|
||||
@@ -220,7 +227,8 @@ impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::AuthorityId>
|
||||
for FindAccountFromAuthorIndex<T, Inner>
|
||||
{
|
||||
fn find_author<'a, I>(digests: I) -> Option<T::AuthorityId>
|
||||
where I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
let i = Inner::find_author(digests)?;
|
||||
|
||||
@@ -234,9 +242,7 @@ pub type AuraAuthorId<T> = FindAccountFromAuthorIndex<T, Pallet<T>>;
|
||||
|
||||
impl<T: Config> IsMember<T::AuthorityId> for Pallet<T> {
|
||||
fn is_member(authority_id: &T::AuthorityId) -> bool {
|
||||
Self::authorities()
|
||||
.iter()
|
||||
.any(|id| id == authority_id)
|
||||
Self::authorities().iter().any(|id| id == authority_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +254,9 @@ impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
|
||||
let timestamp_slot = moment / slot_duration;
|
||||
let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
|
||||
|
||||
assert!(CurrentSlot::<T>::get() == timestamp_slot, "Timestamp slot must match `CurrentSlot`");
|
||||
assert!(
|
||||
CurrentSlot::<T>::get() == timestamp_slot,
|
||||
"Timestamp slot must match `CurrentSlot`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
//! Migrations for the AURA pallet.
|
||||
|
||||
use frame_support::{traits::Get, weights::Weight, pallet_prelude::*};
|
||||
use frame_support::{pallet_prelude::*, traits::Get, weights::Weight};
|
||||
|
||||
struct __LastTimestamp<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: RemoveLastTimestamp> frame_support::traits::StorageInstance for __LastTimestamp<T> {
|
||||
fn pallet_prefix() -> &'static str { T::PalletPrefix::get() }
|
||||
fn pallet_prefix() -> &'static str {
|
||||
T::PalletPrefix::get()
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "LastTimestamp";
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,13 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pallet_aura;
|
||||
use sp_consensus_aura::ed25519::AuthorityId;
|
||||
use sp_runtime::{traits::IdentityLookup, testing::{Header, UintAuthorityId}};
|
||||
use frame_support::{parameter_types, traits::GenesisBuild};
|
||||
use sp_consensus_aura::ed25519::AuthorityId;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::{Header, UintAuthorityId},
|
||||
traits::IdentityLookup,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
@@ -86,8 +89,10 @@ impl pallet_aura::Config for Test {
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<u64>) -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_aura::GenesisConfig::<Test>{
|
||||
pallet_aura::GenesisConfig::<Test> {
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a).to_public_key()).collect(),
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::mock::{Aura, new_test_ext};
|
||||
use crate::mock::{new_test_ext, Aura};
|
||||
|
||||
#[test]
|
||||
fn initial_values() {
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::traits::OneSessionHandler;
|
||||
use sp_authority_discovery::AuthorityId;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
@@ -45,20 +45,12 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn keys)]
|
||||
/// Keys of the current authority set.
|
||||
pub(super) type Keys<T: Config> = StorageValue<
|
||||
_,
|
||||
Vec<AuthorityId>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type Keys<T: Config> = StorageValue<_, Vec<AuthorityId>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn next_keys)]
|
||||
/// Keys of the next authority set.
|
||||
pub(super) type NextKeys<T: Config> = StorageValue<
|
||||
_,
|
||||
Vec<AuthorityId>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type NextKeys<T: Config> = StorageValue<_, Vec<AuthorityId>, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig {
|
||||
@@ -68,9 +60,7 @@ pub mod pallet {
|
||||
#[cfg(feature = "std")]
|
||||
impl Default for GenesisConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
keys: Default::default(),
|
||||
}
|
||||
Self { keys: Default::default() }
|
||||
}
|
||||
}
|
||||
#[pallet::genesis_build]
|
||||
@@ -148,18 +138,18 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as pallet_authority_discovery;
|
||||
use super::*;
|
||||
use sp_authority_discovery::AuthorityPair;
|
||||
use crate as pallet_authority_discovery;
|
||||
use frame_support::{parameter_types, traits::GenesisBuild};
|
||||
use sp_application_crypto::Pair;
|
||||
use sp_authority_discovery::AuthorityPair;
|
||||
use sp_core::{crypto::key_types, H256};
|
||||
use sp_io::TestExternalities;
|
||||
use sp_runtime::{
|
||||
testing::{Header, UintAuthorityId}, traits::{ConvertInto, IdentityLookup, OpaqueKeys},
|
||||
Perbill, KeyTypeId,
|
||||
testing::{Header, UintAuthorityId},
|
||||
traits::{ConvertInto, IdentityLookup, OpaqueKeys},
|
||||
KeyTypeId, Perbill,
|
||||
};
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::traits::GenesisBuild;
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
@@ -260,41 +250,44 @@ mod tests {
|
||||
// everywhere.
|
||||
let account_id = AuthorityPair::from_seed_slice(vec![10; 32].as_ref()).unwrap().public();
|
||||
|
||||
let mut first_authorities: Vec<AuthorityId> = vec![0, 1].into_iter()
|
||||
let mut first_authorities: Vec<AuthorityId> = vec![0, 1]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
|
||||
let second_authorities: Vec<AuthorityId> = vec![2, 3].into_iter()
|
||||
let second_authorities: Vec<AuthorityId> = vec![2, 3]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
// Needed for `pallet_session::OneSessionHandler::on_new_session`.
|
||||
let second_authorities_and_account_ids = second_authorities.clone()
|
||||
let second_authorities_and_account_ids = second_authorities
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|id| (&account_id, id))
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)> >();
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
||||
|
||||
let mut third_authorities: Vec<AuthorityId> = vec![4, 5].into_iter()
|
||||
let mut third_authorities: Vec<AuthorityId> = vec![4, 5]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
// Needed for `pallet_session::OneSessionHandler::on_new_session`.
|
||||
let third_authorities_and_account_ids = third_authorities.clone()
|
||||
let third_authorities_and_account_ids = third_authorities
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|id| (&account_id, id))
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)> >();
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
||||
|
||||
// Build genesis.
|
||||
let mut t = frame_system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap();
|
||||
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
|
||||
GenesisBuild::<Test>::assimilate_storage(
|
||||
&pallet_authority_discovery::GenesisConfig{keys: vec![]},
|
||||
&mut t
|
||||
).unwrap();
|
||||
&pallet_authority_discovery::GenesisConfig { keys: vec![] },
|
||||
&mut t,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create externalities.
|
||||
let mut externalities = TestExternalities::new(t);
|
||||
@@ -303,7 +296,7 @@ mod tests {
|
||||
use frame_support::traits::OneSessionHandler;
|
||||
|
||||
AuthorityDiscovery::on_genesis_session(
|
||||
first_authorities.iter().map(|id| (id, id.clone()))
|
||||
first_authorities.iter().map(|id| (id, id.clone())),
|
||||
);
|
||||
first_authorities.sort();
|
||||
let mut authorities_returned = AuthorityDiscovery::authorities();
|
||||
@@ -318,8 +311,7 @@ mod tests {
|
||||
);
|
||||
let authorities_returned = AuthorityDiscovery::authorities();
|
||||
assert_eq!(
|
||||
first_authorities,
|
||||
authorities_returned,
|
||||
first_authorities, authorities_returned,
|
||||
"Expected authority set not to change as `changed` was set to false.",
|
||||
);
|
||||
|
||||
@@ -329,7 +321,8 @@ mod tests {
|
||||
second_authorities_and_account_ids.into_iter(),
|
||||
third_authorities_and_account_ids.clone().into_iter(),
|
||||
);
|
||||
let mut second_and_third_authorities = second_authorities.iter()
|
||||
let mut second_and_third_authorities = second_authorities
|
||||
.iter()
|
||||
.chain(third_authorities.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<AuthorityId>>();
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::{result, prelude::*, collections::btree_set::BTreeSet};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
dispatch, traits::{FindAuthor, VerifySeal, Get},
|
||||
dispatch,
|
||||
traits::{FindAuthor, Get, VerifySeal},
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_authorship::{InherentError, UnclesInherentData, INHERENT_IDENTIFIER};
|
||||
use sp_runtime::traits::{Header as HeaderT, One, Saturating};
|
||||
use sp_authorship::{INHERENT_IDENTIFIER, UnclesInherentData, InherentError};
|
||||
use sp_std::{collections::btree_set::BTreeSet, prelude::*, result};
|
||||
|
||||
const MAX_UNCLES: usize = 10;
|
||||
|
||||
@@ -56,15 +57,15 @@ pub trait FilterUncle<Header, Author> {
|
||||
|
||||
/// Do additional filtering on a seal-checked uncle block, with the accumulated
|
||||
/// filter.
|
||||
fn filter_uncle(header: &Header, acc: &mut Self::Accumulator)
|
||||
-> Result<Option<Author>, &'static str>;
|
||||
fn filter_uncle(
|
||||
header: &Header,
|
||||
acc: &mut Self::Accumulator,
|
||||
) -> Result<Option<Author>, &'static str>;
|
||||
}
|
||||
|
||||
impl<H, A> FilterUncle<H, A> for () {
|
||||
type Accumulator = ();
|
||||
fn filter_uncle(_: &H, _acc: &mut Self::Accumulator)
|
||||
-> Result<Option<A>, &'static str>
|
||||
{
|
||||
fn filter_uncle(_: &H, _acc: &mut Self::Accumulator) -> Result<Option<A>, &'static str> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -74,14 +75,10 @@ impl<H, A> FilterUncle<H, A> for () {
|
||||
/// equivocating is high.
|
||||
pub struct SealVerify<T>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<Header, Author, T: VerifySeal<Header, Author>> FilterUncle<Header, Author>
|
||||
for SealVerify<T>
|
||||
{
|
||||
impl<Header, Author, T: VerifySeal<Header, Author>> FilterUncle<Header, Author> for SealVerify<T> {
|
||||
type Accumulator = ();
|
||||
|
||||
fn filter_uncle(header: &Header, _acc: &mut ())
|
||||
-> Result<Option<Author>, &'static str>
|
||||
{
|
||||
fn filter_uncle(header: &Header, _acc: &mut ()) -> Result<Option<Author>, &'static str> {
|
||||
T::verify_seal(header)
|
||||
}
|
||||
}
|
||||
@@ -92,8 +89,7 @@ impl<Header, Author, T: VerifySeal<Header, Author>> FilterUncle<Header, Author>
|
||||
/// This does O(n log n) work in the number of uncles included.
|
||||
pub struct OnePerAuthorPerHeight<T, N>(sp_std::marker::PhantomData<(T, N)>);
|
||||
|
||||
impl<Header, Author, T> FilterUncle<Header, Author>
|
||||
for OnePerAuthorPerHeight<T, Header::Number>
|
||||
impl<Header, Author, T> FilterUncle<Header, Author> for OnePerAuthorPerHeight<T, Header::Number>
|
||||
where
|
||||
Header: HeaderT + PartialEq,
|
||||
Header::Number: Ord,
|
||||
@@ -102,15 +98,16 @@ where
|
||||
{
|
||||
type Accumulator = BTreeSet<(Header::Number, Author)>;
|
||||
|
||||
fn filter_uncle(header: &Header, acc: &mut Self::Accumulator)
|
||||
-> Result<Option<Author>, &'static str>
|
||||
{
|
||||
fn filter_uncle(
|
||||
header: &Header,
|
||||
acc: &mut Self::Accumulator,
|
||||
) -> Result<Option<Author>, &'static str> {
|
||||
let author = T::verify_seal(header)?;
|
||||
let number = header.number();
|
||||
|
||||
if let Some(ref author) = author {
|
||||
if !acc.insert((number.clone(), author.clone())) {
|
||||
return Err("more than one uncle per number per author included");
|
||||
return Err("more than one uncle per number per author included")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +123,9 @@ enum UncleEntryItem<BlockNumber, Hash, Author> {
|
||||
}
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
@@ -161,10 +158,8 @@ pub mod pallet {
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
|
||||
fn on_initialize(now: T::BlockNumber) -> Weight {
|
||||
let uncle_generations = T::UncleGenerations::get();
|
||||
// prune uncles that are older than the allowed number of generations.
|
||||
@@ -189,11 +184,8 @@ pub mod pallet {
|
||||
|
||||
#[pallet::storage]
|
||||
/// Uncles
|
||||
pub(super) type Uncles<T: Config> = StorageValue<
|
||||
_,
|
||||
Vec<UncleEntryItem<T::BlockNumber, T::Hash, T::AccountId>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type Uncles<T: Config> =
|
||||
StorageValue<_, Vec<UncleEntryItem<T::BlockNumber, T::Hash, T::AccountId>>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
/// Author of current block.
|
||||
@@ -203,7 +195,6 @@ pub mod pallet {
|
||||
/// Whether uncles were already set in this block.
|
||||
pub(super) type DidSetUncles<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The uncle parent not in the chain.
|
||||
@@ -251,14 +242,16 @@ pub mod pallet {
|
||||
|
||||
if !uncles.is_empty() {
|
||||
let prev_uncles = <Uncles<T>>::get();
|
||||
let mut existing_hashes: Vec<_> = prev_uncles.into_iter().filter_map(|entry|
|
||||
match entry {
|
||||
let mut existing_hashes: Vec<_> = prev_uncles
|
||||
.into_iter()
|
||||
.filter_map(|entry| match entry {
|
||||
UncleEntryItem::InclusionHeight(_) => None,
|
||||
UncleEntryItem::Uncle(h, _) => Some(h),
|
||||
}
|
||||
).collect();
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut acc: <T::FilterUncle as FilterUncle<_, _>>::Accumulator = Default::default();
|
||||
let mut acc: <T::FilterUncle as FilterUncle<_, _>>::Accumulator =
|
||||
Default::default();
|
||||
|
||||
for uncle in uncles {
|
||||
match Self::verify_uncle(&uncle, &existing_hashes, &mut acc) {
|
||||
@@ -270,10 +263,10 @@ pub mod pallet {
|
||||
if set_uncles.len() == MAX_UNCLES {
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// skip this uncle
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,14 +278,14 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inherent(call: &Self::Call, _data: &InherentData) -> result::Result<(), Self::Error> {
|
||||
fn check_inherent(
|
||||
call: &Self::Call,
|
||||
_data: &InherentData,
|
||||
) -> result::Result<(), Self::Error> {
|
||||
match call {
|
||||
Call::set_uncles(ref uncles) if uncles.len() > MAX_UNCLES => {
|
||||
Err(InherentError::Uncles(Error::<T>::TooManyUncles.as_str().into()))
|
||||
},
|
||||
_ => {
|
||||
Ok(())
|
||||
},
|
||||
Call::set_uncles(ref uncles) if uncles.len() > MAX_UNCLES =>
|
||||
Err(InherentError::Uncles(Error::<T>::TooManyUncles.as_str().into())),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +303,7 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn author() -> T::AccountId {
|
||||
// Check the memoized storage value.
|
||||
if let Some(author) = <Author<T>>::get() {
|
||||
return author;
|
||||
return author
|
||||
}
|
||||
|
||||
let digest = <frame_system::Pallet<T>>::digest();
|
||||
@@ -332,11 +325,10 @@ impl<T: Config> Pallet<T> {
|
||||
let mut acc: <T::FilterUncle as FilterUncle<_, _>>::Accumulator = Default::default();
|
||||
|
||||
for uncle in new_uncles {
|
||||
let prev_uncles = uncles.iter().filter_map(|entry|
|
||||
match entry {
|
||||
UncleEntryItem::InclusionHeight(_) => None,
|
||||
UncleEntryItem::Uncle(h, _) => Some(h),
|
||||
});
|
||||
let prev_uncles = uncles.iter().filter_map(|entry| match entry {
|
||||
UncleEntryItem::InclusionHeight(_) => None,
|
||||
UncleEntryItem::Uncle(h, _) => Some(h),
|
||||
});
|
||||
let author = Self::verify_uncle(&uncle, prev_uncles, &mut acc)?;
|
||||
let hash = uncle.hash();
|
||||
|
||||
@@ -351,7 +343,7 @@ impl<T: Config> Pallet<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_uncle<'a, I: IntoIterator<Item=&'a T::Hash>>(
|
||||
fn verify_uncle<'a, I: IntoIterator<Item = &'a T::Hash>>(
|
||||
uncle: &T::Header,
|
||||
existing_uncles: I,
|
||||
accumulator: &mut <T::FilterUncle as FilterUncle<T::Header, T::AccountId>>::Accumulator,
|
||||
@@ -368,23 +360,23 @@ impl<T: Config> Pallet<T> {
|
||||
let hash = uncle.hash();
|
||||
|
||||
if uncle.number() < &One::one() {
|
||||
return Err(Error::<T>::GenesisUncle.into());
|
||||
return Err(Error::<T>::GenesisUncle.into())
|
||||
}
|
||||
|
||||
if uncle.number() > &maximum_height {
|
||||
return Err(Error::<T>::TooHighUncle.into());
|
||||
return Err(Error::<T>::TooHighUncle.into())
|
||||
}
|
||||
|
||||
{
|
||||
let parent_number = uncle.number().clone() - One::one();
|
||||
let parent_hash = <frame_system::Pallet<T>>::block_hash(&parent_number);
|
||||
if &parent_hash != uncle.parent_hash() {
|
||||
return Err(Error::<T>::InvalidUncleParent.into());
|
||||
return Err(Error::<T>::InvalidUncleParent.into())
|
||||
}
|
||||
}
|
||||
|
||||
if uncle.number() < &minimum_height {
|
||||
return Err(Error::<T>::OldUncle.into());
|
||||
return Err(Error::<T>::OldUncle.into())
|
||||
}
|
||||
|
||||
let duplicate = existing_uncles.into_iter().any(|h| *h == hash);
|
||||
@@ -412,13 +404,15 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as pallet_authorship;
|
||||
use super::*;
|
||||
use crate as pallet_authorship;
|
||||
use frame_support::{parameter_types, ConsensusEngineId};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup}, testing::Header, generic::DigestItem,
|
||||
generic::DigestItem,
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
};
|
||||
use frame_support::{parameter_types, ConsensusEngineId};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
@@ -483,11 +477,12 @@ mod tests {
|
||||
|
||||
impl FindAuthor<u64> for AuthorGiven {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u64>
|
||||
where I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
for (id, data) in digests {
|
||||
if id == TEST_ID {
|
||||
return u64::decode(&mut &data[..]).ok();
|
||||
return u64::decode(&mut &data[..]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +497,8 @@ mod tests {
|
||||
let pre_runtime_digests = header.digest.logs.iter().filter_map(|d| d.as_pre_runtime());
|
||||
let seals = header.digest.logs.iter().filter_map(|d| d.as_seal());
|
||||
|
||||
let author = AuthorGiven::find_author(pre_runtime_digests).ok_or_else(|| "no author")?;
|
||||
let author =
|
||||
AuthorGiven::find_author(pre_runtime_digests).ok_or_else(|| "no author")?;
|
||||
|
||||
for (id, seal) in seals {
|
||||
if id == TEST_ID {
|
||||
@@ -510,10 +506,10 @@ mod tests {
|
||||
Err(_) => return Err("wrong seal"),
|
||||
Ok(a) => {
|
||||
if a != author {
|
||||
return Err("wrong author in seal");
|
||||
return Err("wrong author in seal")
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -533,13 +529,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn create_header(number: u64, parent_hash: H256, state_root: H256) -> Header {
|
||||
Header::new(
|
||||
number,
|
||||
Default::default(),
|
||||
state_root,
|
||||
parent_hash,
|
||||
Default::default(),
|
||||
)
|
||||
Header::new(number, Default::default(), state_root, parent_hash, Default::default())
|
||||
}
|
||||
|
||||
fn new_test_ext() -> sp_io::TestExternalities {
|
||||
@@ -554,9 +544,14 @@ mod tests {
|
||||
let hash = Default::default();
|
||||
let author = Default::default();
|
||||
let uncles = vec![
|
||||
InclusionHeight(1u64), Uncle(hash, Some(author)), Uncle(hash, None), Uncle(hash, None),
|
||||
InclusionHeight(2u64), Uncle(hash, None),
|
||||
InclusionHeight(3u64), Uncle(hash, None),
|
||||
InclusionHeight(1u64),
|
||||
Uncle(hash, Some(author)),
|
||||
Uncle(hash, None),
|
||||
Uncle(hash, None),
|
||||
InclusionHeight(2u64),
|
||||
Uncle(hash, None),
|
||||
InclusionHeight(3u64),
|
||||
Uncle(hash, None),
|
||||
];
|
||||
|
||||
<Authorship as Store>::Uncles::put(uncles);
|
||||
@@ -595,15 +590,15 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut canon_chain = CanonChain {
|
||||
inner: vec![seal_header(create_header(0, Default::default(), Default::default()), 999)],
|
||||
inner: vec![seal_header(
|
||||
create_header(0, Default::default(), Default::default()),
|
||||
999,
|
||||
)],
|
||||
};
|
||||
|
||||
let initialize_block = |number, hash: H256| System::initialize(
|
||||
&number,
|
||||
&hash,
|
||||
&Default::default(),
|
||||
Default::default()
|
||||
);
|
||||
let initialize_block = |number, hash: H256| {
|
||||
System::initialize(&number, &hash, &Default::default(), Default::default())
|
||||
};
|
||||
|
||||
for number in 1..8 {
|
||||
initialize_block(number, canon_chain.best_hash());
|
||||
@@ -691,18 +686,11 @@ mod tests {
|
||||
fn sets_author_lazily() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let author = 42;
|
||||
let mut header = seal_header(
|
||||
create_header(1, Default::default(), [1; 32].into()),
|
||||
author,
|
||||
);
|
||||
let mut header =
|
||||
seal_header(create_header(1, Default::default(), [1; 32].into()), author);
|
||||
|
||||
header.digest_mut().pop(); // pop the seal off.
|
||||
System::initialize(
|
||||
&1,
|
||||
&Default::default(),
|
||||
header.digest(),
|
||||
Default::default(),
|
||||
);
|
||||
System::initialize(&1, &Default::default(), header.digest(), Default::default());
|
||||
|
||||
assert_eq!(Authorship::author(), author);
|
||||
});
|
||||
@@ -716,27 +704,15 @@ mod tests {
|
||||
let author_b = 43;
|
||||
|
||||
let mut acc: <Filter as FilterUncle<Header, u64>>::Accumulator = Default::default();
|
||||
let header_a1 = seal_header(
|
||||
create_header(1, Default::default(), [1; 32].into()),
|
||||
author_a,
|
||||
);
|
||||
let header_b1 = seal_header(
|
||||
create_header(1, Default::default(), [1; 32].into()),
|
||||
author_b,
|
||||
);
|
||||
let header_a1 = seal_header(create_header(1, Default::default(), [1; 32].into()), author_a);
|
||||
let header_b1 = seal_header(create_header(1, Default::default(), [1; 32].into()), author_b);
|
||||
|
||||
let header_a2_1 = seal_header(
|
||||
create_header(2, Default::default(), [1; 32].into()),
|
||||
author_a,
|
||||
);
|
||||
let header_a2_2 = seal_header(
|
||||
create_header(2, Default::default(), [2; 32].into()),
|
||||
author_a,
|
||||
);
|
||||
let header_a2_1 =
|
||||
seal_header(create_header(2, Default::default(), [1; 32].into()), author_a);
|
||||
let header_a2_2 =
|
||||
seal_header(create_header(2, Default::default(), [2; 32].into()), author_a);
|
||||
|
||||
let mut check_filter = move |uncle| {
|
||||
Filter::filter_uncle(uncle, &mut acc)
|
||||
};
|
||||
let mut check_filter = move |uncle| Filter::filter_uncle(uncle, &mut acc);
|
||||
|
||||
// same height, different author is OK.
|
||||
assert_eq!(check_filter(&header_a1), Ok(Some(author_a)));
|
||||
|
||||
@@ -95,10 +95,7 @@ mod tests {
|
||||
);
|
||||
|
||||
println!("equivocation_proof: {:?}", equivocation_proof);
|
||||
println!(
|
||||
"equivocation_proof.encode(): {:?}",
|
||||
equivocation_proof.encode()
|
||||
);
|
||||
println!("equivocation_proof.encode(): {:?}", equivocation_proof.encode());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
//! This file was not auto-generated.
|
||||
|
||||
use frame_support::weights::{
|
||||
Weight, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS, RocksDbWeight as DbWeight},
|
||||
constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS},
|
||||
Weight,
|
||||
};
|
||||
|
||||
impl crate::WeightInfo for () {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//!
|
||||
//! An opt-in utility module for reporting equivocations.
|
||||
//!
|
||||
//! This module defines an offence type for BABE equivocations
|
||||
@@ -33,22 +32,23 @@
|
||||
//! When using this module for enabling equivocation reporting it is required
|
||||
//! that the `ValidateUnsigned` for the BABE pallet is used in the runtime
|
||||
//! definition.
|
||||
//!
|
||||
|
||||
use frame_support::traits::{Get, KeyOwnerProofSystem};
|
||||
use sp_consensus_babe::{EquivocationProof, Slot};
|
||||
use sp_runtime::transaction_validity::{
|
||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||
TransactionValidityError, ValidTransaction,
|
||||
use sp_runtime::{
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||
TransactionValidityError, ValidTransaction,
|
||||
},
|
||||
DispatchResult, Perbill,
|
||||
};
|
||||
use sp_runtime::{DispatchResult, Perbill};
|
||||
use sp_staking::{
|
||||
offence::{Kind, Offence, OffenceError, ReportOffence},
|
||||
SessionIndex,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use crate::{Call, Pallet, Config};
|
||||
use crate::{Call, Config, Pallet};
|
||||
|
||||
/// A trait with utility methods for handling equivocation reports in BABE.
|
||||
/// The trait provides methods for reporting an offence triggered by a valid
|
||||
@@ -115,9 +115,7 @@ pub struct EquivocationHandler<I, R, L> {
|
||||
|
||||
impl<I, R, L> Default for EquivocationHandler<I, R, L> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
Self { _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,30 +186,28 @@ impl<T: Config> Pallet<T> {
|
||||
if let Call::report_equivocation_unsigned(equivocation_proof, key_owner_proof) = call {
|
||||
// discard equivocation report not coming from the local node
|
||||
match source {
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
|
||||
_ => {
|
||||
log::warn!(
|
||||
target: "runtime::babe",
|
||||
"rejecting unsigned report equivocation transaction because it is not local/in-block.",
|
||||
);
|
||||
|
||||
return InvalidTransaction::Call.into();
|
||||
}
|
||||
return InvalidTransaction::Call.into()
|
||||
},
|
||||
}
|
||||
|
||||
// check report staleness
|
||||
is_known_offence::<T>(equivocation_proof, key_owner_proof)?;
|
||||
|
||||
let longevity = <T::HandleEquivocation as HandleEquivocation<T>>::ReportLongevity::get();
|
||||
let longevity =
|
||||
<T::HandleEquivocation as HandleEquivocation<T>>::ReportLongevity::get();
|
||||
|
||||
ValidTransaction::with_tag_prefix("BabeEquivocation")
|
||||
// We assign the maximum priority for any equivocation report.
|
||||
.priority(TransactionPriority::max_value())
|
||||
// Only one equivocation report for the same offender at the same slot.
|
||||
.and_provides((
|
||||
equivocation_proof.offender.clone(),
|
||||
*equivocation_proof.slot,
|
||||
))
|
||||
.and_provides((equivocation_proof.offender.clone(), *equivocation_proof.slot))
|
||||
.longevity(longevity)
|
||||
// We don't propagate this. This can never be included on a remote node.
|
||||
.propagate(false)
|
||||
@@ -235,10 +231,7 @@ fn is_known_offence<T: Config>(
|
||||
key_owner_proof: &T::KeyOwnerProof,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
// check the membership proof to extract the offender's id
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
equivocation_proof.offender.clone(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, equivocation_proof.offender.clone());
|
||||
|
||||
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof.clone())
|
||||
.ok_or(InvalidTransaction::BadProof)?;
|
||||
|
||||
+85
-117
@@ -24,7 +24,7 @@
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
dispatch::DispatchResultWithPostInfo,
|
||||
traits::{FindAuthor, Get, KeyOwnerProofSystem, OneSessionHandler, OnTimestampSet},
|
||||
traits::{FindAuthor, Get, KeyOwnerProofSystem, OnTimestampSet, OneSessionHandler},
|
||||
weights::{Pays, Weight},
|
||||
};
|
||||
use sp_application_crypto::Public;
|
||||
@@ -38,8 +38,8 @@ use sp_std::prelude::*;
|
||||
|
||||
use sp_consensus_babe::{
|
||||
digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest},
|
||||
BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch,
|
||||
EquivocationProof, Slot, BABE_ENGINE_ID,
|
||||
BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, EquivocationProof, Slot,
|
||||
BABE_ENGINE_ID,
|
||||
};
|
||||
use sp_consensus_vrf::schnorrkel;
|
||||
|
||||
@@ -80,7 +80,7 @@ pub trait EpochChangeTrigger {
|
||||
pub struct ExternalTrigger;
|
||||
|
||||
impl EpochChangeTrigger for ExternalTrigger {
|
||||
fn trigger<T: Config>(_: T::BlockNumber) { } // nothing - trigger is external.
|
||||
fn trigger<T: Config>(_: T::BlockNumber) {} // nothing - trigger is external.
|
||||
}
|
||||
|
||||
/// A type signifying to BABE that it should perform epoch changes
|
||||
@@ -104,9 +104,9 @@ type MaybeRandomness = Option<schnorrkel::Randomness>;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
|
||||
/// The BABE Pallet
|
||||
#[pallet::pallet]
|
||||
@@ -222,11 +222,8 @@ pub mod pallet {
|
||||
|
||||
/// Next epoch authorities.
|
||||
#[pallet::storage]
|
||||
pub(super) type NextAuthorities<T> = StorageValue<
|
||||
_,
|
||||
Vec<(AuthorityId, BabeAuthorityWeight)>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type NextAuthorities<T> =
|
||||
StorageValue<_, Vec<(AuthorityId, BabeAuthorityWeight)>, ValueQuery>;
|
||||
|
||||
/// Randomness under construction.
|
||||
///
|
||||
@@ -242,13 +239,8 @@ pub mod pallet {
|
||||
|
||||
/// TWOX-NOTE: `SegmentIndex` is an increasing integer, so this is okay.
|
||||
#[pallet::storage]
|
||||
pub(super) type UnderConstruction<T> = StorageMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
u32,
|
||||
Vec<schnorrkel::Randomness>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type UnderConstruction<T> =
|
||||
StorageMap<_, Twox64Concat, u32, Vec<schnorrkel::Randomness>, ValueQuery>;
|
||||
|
||||
/// Temporary value (cleared at block finalization) which is `Some`
|
||||
/// if per-block initialization has already been called for current block.
|
||||
@@ -270,11 +262,8 @@ pub mod pallet {
|
||||
/// entropy was fixed (i.e. it was known to chain observers). Since epochs are defined in
|
||||
/// slots, which may be skipped, the block numbers may not line up with the slot numbers.
|
||||
#[pallet::storage]
|
||||
pub(super) type EpochStart<T: Config> = StorageValue<
|
||||
_,
|
||||
(T::BlockNumber, T::BlockNumber),
|
||||
ValueQuery,
|
||||
>;
|
||||
pub(super) type EpochStart<T: Config> =
|
||||
StorageValue<_, (T::BlockNumber, T::BlockNumber), ValueQuery>;
|
||||
|
||||
/// How late the current block is compared to its parent.
|
||||
///
|
||||
@@ -303,10 +292,7 @@ pub mod pallet {
|
||||
#[cfg(feature = "std")]
|
||||
impl Default for GenesisConfig {
|
||||
fn default() -> Self {
|
||||
GenesisConfig {
|
||||
authorities: Default::default(),
|
||||
epoch_config: Default::default(),
|
||||
}
|
||||
GenesisConfig { authorities: Default::default(), epoch_config: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +301,9 @@ pub mod pallet {
|
||||
fn build(&self) {
|
||||
SegmentIndex::<T>::put(0);
|
||||
Pallet::<T>::initialize_authorities(&self.authorities);
|
||||
EpochConfig::<T>::put(self.epoch_config.clone().expect("epoch_config must not be None"));
|
||||
EpochConfig::<T>::put(
|
||||
self.epoch_config.clone().expect("epoch_config must not be None"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,11 +347,7 @@ pub mod pallet {
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let reporter = ensure_signed(origin)?;
|
||||
|
||||
Self::do_report_equivocation(
|
||||
Some(reporter),
|
||||
equivocation_proof,
|
||||
key_owner_proof,
|
||||
)
|
||||
Self::do_report_equivocation(Some(reporter), equivocation_proof, key_owner_proof)
|
||||
}
|
||||
|
||||
/// Report authority equivocation/misbehavior. This method will verify
|
||||
@@ -423,8 +407,9 @@ pub mod pallet {
|
||||
pub type BabeKey = [u8; PUBLIC_KEY_LENGTH];
|
||||
|
||||
impl<T: Config> FindAuthor<u32> for Pallet<T> {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32> where
|
||||
I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
for (id, mut data) in digests.into_iter() {
|
||||
if id == BABE_ENGINE_ID {
|
||||
@@ -433,15 +418,13 @@ impl<T: Config> FindAuthor<u32> for Pallet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> IsMember<AuthorityId> for Pallet<T> {
|
||||
fn is_member(authority_id: &AuthorityId) -> bool {
|
||||
<Pallet<T>>::authorities()
|
||||
.iter()
|
||||
.any(|id| &id.0 == authority_id)
|
||||
<Pallet<T>>::authorities().iter().any(|id| &id.0 == authority_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +473,6 @@ impl<T: Config> Pallet<T> {
|
||||
/// In other word, this is only accurate if no slots are missed. Given missed slots, the slot
|
||||
/// number will grow while the block number will not. Hence, the result can be interpreted as an
|
||||
/// upper bound.
|
||||
//
|
||||
// ## IMPORTANT NOTE
|
||||
//
|
||||
// This implementation is linked to how [`should_epoch_change`] is working. This might need to
|
||||
@@ -500,13 +482,11 @@ impl<T: Config> Pallet<T> {
|
||||
// update this function, you must also update the corresponding weight.
|
||||
pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option<T::BlockNumber> {
|
||||
let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get());
|
||||
next_slot
|
||||
.checked_sub(*CurrentSlot::<T>::get())
|
||||
.map(|slots_remaining| {
|
||||
// This is a best effort guess. Drifts in the slot/block ratio will cause errors here.
|
||||
let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into();
|
||||
now.saturating_add(blocks_remaining)
|
||||
})
|
||||
next_slot.checked_sub(*CurrentSlot::<T>::get()).map(|slots_remaining| {
|
||||
// This is a best effort guess. Drifts in the slot/block ratio will cause errors here.
|
||||
let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into();
|
||||
now.saturating_add(blocks_remaining)
|
||||
})
|
||||
}
|
||||
|
||||
/// DANGEROUS: Enact an epoch change. Should be done on every block where `should_epoch_change` has returned `true`,
|
||||
@@ -553,10 +533,8 @@ impl<T: Config> Pallet<T> {
|
||||
// so that nodes can track changes.
|
||||
let next_randomness = NextRandomness::<T>::get();
|
||||
|
||||
let next_epoch = NextEpochDescriptor {
|
||||
authorities: next_authorities,
|
||||
randomness: next_randomness,
|
||||
};
|
||||
let next_epoch =
|
||||
NextEpochDescriptor { authorities: next_authorities, randomness: next_randomness };
|
||||
Self::deposit_consensus(ConsensusLog::NextEpochData(next_epoch));
|
||||
|
||||
if let Some(next_config) = NextEpochConfig::<T>::get() {
|
||||
@@ -587,7 +565,8 @@ impl<T: Config> Pallet<T> {
|
||||
duration: T::EpochDuration::get(),
|
||||
authorities: Self::authorities(),
|
||||
randomness: Self::randomness(),
|
||||
config: EpochConfig::<T>::get().expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed"),
|
||||
config: EpochConfig::<T>::get()
|
||||
.expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,7 +585,9 @@ impl<T: Config> Pallet<T> {
|
||||
authorities: NextAuthorities::<T>::get(),
|
||||
randomness: NextRandomness::<T>::get(),
|
||||
config: NextEpochConfig::<T>::get().unwrap_or_else(|| {
|
||||
EpochConfig::<T>::get().expect("EpochConfig is initialized in genesis; we never `take` or `kill` it; qed")
|
||||
EpochConfig::<T>::get().expect(
|
||||
"EpochConfig is initialized in genesis; we never `take` or `kill` it; qed",
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -617,9 +598,7 @@ impl<T: Config> Pallet<T> {
|
||||
const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \
|
||||
if u64 is not enough we should crash for safety; qed.";
|
||||
|
||||
let epoch_start = epoch_index
|
||||
.checked_mul(T::EpochDuration::get())
|
||||
.expect(PROOF);
|
||||
let epoch_start = epoch_index.checked_mul(T::EpochDuration::get()).expect(PROOF);
|
||||
|
||||
epoch_start.checked_add(*GenesisSlot::<T>::get()).expect(PROOF).into()
|
||||
}
|
||||
@@ -649,19 +628,22 @@ impl<T: Config> Pallet<T> {
|
||||
// => let's ensure that we only modify the storage once per block
|
||||
let initialized = Self::initialized().is_some();
|
||||
if initialized {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let maybe_pre_digest: Option<PreDigest> = <frame_system::Pallet<T>>::digest()
|
||||
.logs
|
||||
.iter()
|
||||
.filter_map(|s| s.as_pre_runtime())
|
||||
.filter_map(|(id, mut data)| if id == BABE_ENGINE_ID {
|
||||
PreDigest::decode(&mut data).ok()
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.next();
|
||||
let maybe_pre_digest: Option<PreDigest> =
|
||||
<frame_system::Pallet<T>>::digest()
|
||||
.logs
|
||||
.iter()
|
||||
.filter_map(|s| s.as_pre_runtime())
|
||||
.filter_map(|(id, mut data)| {
|
||||
if id == BABE_ENGINE_ID {
|
||||
PreDigest::decode(&mut data).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next();
|
||||
|
||||
let is_primary = matches!(maybe_pre_digest, Some(PreDigest::Primary(..)));
|
||||
|
||||
@@ -697,31 +679,22 @@ impl<T: Config> Pallet<T> {
|
||||
let authority_index = digest.authority_index();
|
||||
|
||||
// Extract out the VRF output if we have it
|
||||
digest
|
||||
.vrf_output()
|
||||
.and_then(|vrf_output| {
|
||||
// Reconstruct the bytes of VRFInOut using the authority id.
|
||||
Authorities::<T>::get()
|
||||
.get(authority_index as usize)
|
||||
.and_then(|author| {
|
||||
schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok()
|
||||
})
|
||||
.and_then(|pubkey| {
|
||||
let transcript = sp_consensus_babe::make_transcript(
|
||||
&Self::randomness(),
|
||||
current_slot,
|
||||
EpochIndex::<T>::get(),
|
||||
);
|
||||
digest.vrf_output().and_then(|vrf_output| {
|
||||
// Reconstruct the bytes of VRFInOut using the authority id.
|
||||
Authorities::<T>::get()
|
||||
.get(authority_index as usize)
|
||||
.and_then(|author| schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok())
|
||||
.and_then(|pubkey| {
|
||||
let transcript = sp_consensus_babe::make_transcript(
|
||||
&Self::randomness(),
|
||||
current_slot,
|
||||
EpochIndex::<T>::get(),
|
||||
);
|
||||
|
||||
vrf_output.0.attach_input_hash(
|
||||
&pubkey,
|
||||
transcript
|
||||
).ok()
|
||||
})
|
||||
.map(|inout| {
|
||||
inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)
|
||||
})
|
||||
})
|
||||
vrf_output.0.attach_input_hash(&pubkey, transcript).ok()
|
||||
})
|
||||
.map(|inout| inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT))
|
||||
})
|
||||
});
|
||||
|
||||
// For primary VRF output we place it in the `Initialized` storage
|
||||
@@ -774,7 +747,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// validate the equivocation proof
|
||||
if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) {
|
||||
return Err(Error::<T>::InvalidEquivocationProof.into());
|
||||
return Err(Error::<T>::InvalidEquivocationProof.into())
|
||||
}
|
||||
|
||||
let validator_set_count = key_owner_proof.validator_count();
|
||||
@@ -786,7 +759,7 @@ impl<T: Config> Pallet<T> {
|
||||
// check that the slot number is consistent with the session index
|
||||
// in the key ownership proof (i.e. slot is for that epoch)
|
||||
if epoch_index != session_index {
|
||||
return Err(Error::<T>::InvalidKeyOwnershipProof.into());
|
||||
return Err(Error::<T>::InvalidKeyOwnershipProof.into())
|
||||
}
|
||||
|
||||
// check the membership proof and extract the offender's id
|
||||
@@ -794,12 +767,8 @@ impl<T: Config> Pallet<T> {
|
||||
let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof)
|
||||
.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
|
||||
|
||||
let offence = BabeEquivocationOffence {
|
||||
slot,
|
||||
validator_set_count,
|
||||
offender,
|
||||
session_index,
|
||||
};
|
||||
let offence =
|
||||
BabeEquivocationOffence { slot, validator_set_count, offender, session_index };
|
||||
|
||||
let reporters = match reporter {
|
||||
Some(id) => vec![id],
|
||||
@@ -837,7 +806,10 @@ impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
|
||||
let timestamp_slot = moment / slot_duration;
|
||||
let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
|
||||
|
||||
assert!(CurrentSlot::<T>::get() == timestamp_slot, "Timestamp slot must match `CurrentSlot`");
|
||||
assert!(
|
||||
CurrentSlot::<T>::get() == timestamp_slot,
|
||||
"Timestamp slot must match `CurrentSlot`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,10 +822,7 @@ impl<T: Config> frame_support::traits::EstimateNextSessionRotation<T::BlockNumbe
|
||||
let elapsed = CurrentSlot::<T>::get().saturating_sub(Self::current_epoch_start()) + 1;
|
||||
|
||||
(
|
||||
Some(Permill::from_rational(
|
||||
*elapsed,
|
||||
T::EpochDuration::get(),
|
||||
)),
|
||||
Some(Permill::from_rational(*elapsed, T::EpochDuration::get())),
|
||||
// Read: Current Slot, Epoch Index, Genesis Slot
|
||||
T::DbWeight::get().reads(3),
|
||||
)
|
||||
@@ -882,22 +851,20 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, AuthorityId)>,
|
||||
{
|
||||
let authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
|
||||
Self::initialize_authorities(&authorities);
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, AuthorityId)>,
|
||||
{
|
||||
let authorities = validators.map(|(_account, k)| {
|
||||
(k, 1)
|
||||
}).collect::<Vec<_>>();
|
||||
let authorities = validators.map(|(_account, k)| (k, 1)).collect::<Vec<_>>();
|
||||
|
||||
let next_authorities = queued_validators.map(|(_account, k)| {
|
||||
(k, 1)
|
||||
}).collect::<Vec<_>>();
|
||||
let next_authorities = queued_validators.map(|(_account, k)| (k, 1)).collect::<Vec<_>>();
|
||||
|
||||
Self::enact_epoch_change(authorities, next_authorities)
|
||||
}
|
||||
@@ -914,7 +881,7 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
fn compute_randomness(
|
||||
last_epoch_randomness: schnorrkel::Randomness,
|
||||
epoch_index: u64,
|
||||
rho: impl Iterator<Item=schnorrkel::Randomness>,
|
||||
rho: impl Iterator<Item = schnorrkel::Randomness>,
|
||||
rho_size_hint: Option<usize>,
|
||||
) -> schnorrkel::Randomness {
|
||||
let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH);
|
||||
@@ -930,7 +897,7 @@ fn compute_randomness(
|
||||
|
||||
pub mod migrations {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::{ValueQuery, StorageValue};
|
||||
use frame_support::pallet_prelude::{StorageValue, ValueQuery};
|
||||
|
||||
/// Something that can return the storage prefix of the `Babe` pallet.
|
||||
pub trait BabePalletPrefix: Config {
|
||||
@@ -939,13 +906,14 @@ pub mod migrations {
|
||||
|
||||
struct __OldNextEpochConfig<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: BabePalletPrefix> frame_support::traits::StorageInstance for __OldNextEpochConfig<T> {
|
||||
fn pallet_prefix() -> &'static str { T::pallet_prefix() }
|
||||
fn pallet_prefix() -> &'static str {
|
||||
T::pallet_prefix()
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "NextEpochConfig";
|
||||
}
|
||||
|
||||
type OldNextEpochConfig<T> = StorageValue<
|
||||
__OldNextEpochConfig<T>, Option<NextConfigDescriptor>, ValueQuery
|
||||
>;
|
||||
type OldNextEpochConfig<T> =
|
||||
StorageValue<__OldNextEpochConfig<T>, Option<NextConfigDescriptor>, ValueQuery>;
|
||||
|
||||
/// A storage migration that adds the current epoch configuration for Babe
|
||||
/// to storage.
|
||||
|
||||
@@ -17,27 +17,31 @@
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use codec::Encode;
|
||||
use crate::{self as pallet_babe, Config, CurrentSlot};
|
||||
use sp_runtime::{
|
||||
Perbill, impl_opaque_keys,
|
||||
curve::PiecewiseLinear,
|
||||
testing::{Digest, DigestItem, Header, TestXt,},
|
||||
traits::{Header as _, IdentityLookup, OpaqueKeys},
|
||||
};
|
||||
use frame_system::InitKind;
|
||||
use codec::Encode;
|
||||
use frame_election_provider_support::onchain;
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{KeyOwnerProofSystem, OnInitialize, GenesisBuild},
|
||||
traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize},
|
||||
};
|
||||
use sp_io;
|
||||
use sp_core::{H256, U256, crypto::{IsWrappedBy, KeyTypeId, Pair}};
|
||||
use frame_system::InitKind;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
use pallet_staking::EraIndex;
|
||||
use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot};
|
||||
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
|
||||
use sp_core::{
|
||||
crypto::{IsWrappedBy, KeyTypeId, Pair},
|
||||
H256, U256,
|
||||
};
|
||||
use sp_io;
|
||||
use sp_runtime::{
|
||||
curve::PiecewiseLinear,
|
||||
impl_opaque_keys,
|
||||
testing::{Digest, DigestItem, Header, TestXt},
|
||||
traits::{Header as _, IdentityLookup, OpaqueKeys},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::SessionIndex;
|
||||
use pallet_staking::EraIndex;
|
||||
use frame_election_provider_support::onchain;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
|
||||
type DummyValidatorId = u64;
|
||||
|
||||
@@ -277,7 +281,7 @@ pub fn go_to_block(n: u64, s: u64) {
|
||||
/// Slots will grow accordingly to blocks
|
||||
pub fn progress_to_block(n: u64) {
|
||||
let mut slot = u64::from(Babe::current_slot()) + 1;
|
||||
for i in System::block_number() + 1 ..= n {
|
||||
for i in System::block_number() + 1..=n {
|
||||
go_to_block(i, slot);
|
||||
slot += 1;
|
||||
}
|
||||
@@ -308,7 +312,7 @@ pub fn make_primary_pre_digest(
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
},
|
||||
);
|
||||
let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
@@ -319,10 +323,7 @@ pub fn make_secondary_plain_pre_digest(
|
||||
slot: sp_consensus_babe::Slot,
|
||||
) -> Digest {
|
||||
let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryPlain(
|
||||
sp_consensus_babe::digests::SecondaryPlainPreDigest {
|
||||
authority_index,
|
||||
slot,
|
||||
}
|
||||
sp_consensus_babe::digests::SecondaryPlainPreDigest { authority_index, slot },
|
||||
);
|
||||
let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
@@ -340,7 +341,7 @@ pub fn make_secondary_vrf_pre_digest(
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
},
|
||||
);
|
||||
let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
@@ -348,13 +349,13 @@ pub fn make_secondary_vrf_pre_digest(
|
||||
|
||||
pub fn make_vrf_output(
|
||||
slot: Slot,
|
||||
pair: &sp_consensus_babe::AuthorityPair
|
||||
pair: &sp_consensus_babe::AuthorityPair,
|
||||
) -> (VRFOutput, VRFProof, [u8; 32]) {
|
||||
let pair = sp_core::sr25519::Pair::from_ref(pair).as_ref();
|
||||
let transcript = sp_consensus_babe::make_transcript(&Babe::randomness(), slot, 0);
|
||||
let vrf_inout = pair.vrf_sign(transcript);
|
||||
let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness = vrf_inout.0
|
||||
.make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT);
|
||||
let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness =
|
||||
vrf_inout.0.make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT);
|
||||
let vrf_output = VRFOutput(vrf_inout.0.to_output());
|
||||
let vrf_proof = VRFProof(vrf_inout.1);
|
||||
|
||||
@@ -365,10 +366,12 @@ pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities {
|
||||
new_test_ext_with_pairs(authorities_len).1
|
||||
}
|
||||
|
||||
pub fn new_test_ext_with_pairs(authorities_len: usize) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
|
||||
let pairs = (0..authorities_len).map(|i| {
|
||||
AuthorityPair::from_seed(&U256::from(i).into())
|
||||
}).collect::<Vec<_>>();
|
||||
pub fn new_test_ext_with_pairs(
|
||||
authorities_len: usize,
|
||||
) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
|
||||
let pairs = (0..authorities_len)
|
||||
.map(|i| AuthorityPair::from_seed(&U256::from(i).into()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let public = pairs.iter().map(|p| p.public()).collect();
|
||||
|
||||
@@ -376,13 +379,9 @@ pub fn new_test_ext_with_pairs(authorities_len: usize) -> (Vec<AuthorityPair>, s
|
||||
}
|
||||
|
||||
pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
|
||||
let balances: Vec<_> = (0..authorities.len())
|
||||
.map(|i| (i as u64, 10_000_000))
|
||||
.collect();
|
||||
let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect();
|
||||
|
||||
pallet_balances::GenesisConfig::<Test> { balances }
|
||||
.assimilate_storage(&mut t)
|
||||
@@ -393,13 +392,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::Tes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, k)| {
|
||||
(
|
||||
i as u64,
|
||||
i as u64,
|
||||
MockSessionKeys {
|
||||
babe_authority: AuthorityId::from(k.clone()),
|
||||
},
|
||||
)
|
||||
(i as u64, i as u64, MockSessionKeys { babe_authority: AuthorityId::from(k.clone()) })
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -412,12 +405,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::Tes
|
||||
// controllers are the index + 1000
|
||||
let stakers: Vec<_> = (0..authorities.len())
|
||||
.map(|i| {
|
||||
(
|
||||
i as u64,
|
||||
i as u64 + 1000,
|
||||
10_000,
|
||||
pallet_staking::StakerStatus::<u64>::Validator,
|
||||
)
|
||||
(i as u64, i as u64 + 1000, 10_000, pallet_staking::StakerStatus::<u64>::Validator)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use super::{
|
||||
AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH,
|
||||
};
|
||||
use frame_support::{traits::Randomness as RandomnessT};
|
||||
use frame_support::traits::Randomness as RandomnessT;
|
||||
use sp_runtime::traits::Hash;
|
||||
|
||||
/// Randomness usable by consensus protocols that **depend** upon finality and take action
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
use super::{Call, *};
|
||||
use frame_support::{
|
||||
assert_err, assert_ok, assert_noop,
|
||||
assert_err, assert_noop, assert_ok,
|
||||
traits::{Currency, EstimateNextSessionRotation, OnFinalize},
|
||||
weights::{GetDispatchInfo, Pays},
|
||||
};
|
||||
@@ -29,10 +29,8 @@ use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot};
|
||||
use sp_core::crypto::Pair;
|
||||
|
||||
const EMPTY_RANDOMNESS: [u8; 32] = [
|
||||
74, 25, 49, 128, 53, 97, 244, 49,
|
||||
222, 202, 176, 2, 231, 66, 95, 10,
|
||||
133, 49, 213, 228, 86, 161, 164, 127,
|
||||
217, 153, 138, 37, 48, 192, 248, 0,
|
||||
74, 25, 49, 128, 53, 97, 244, 49, 222, 202, 176, 2, 231, 66, 95, 10, 133, 49, 213, 228, 86,
|
||||
161, 164, 127, 217, 153, 138, 37, 48, 192, 248, 0,
|
||||
];
|
||||
|
||||
#[test]
|
||||
@@ -43,17 +41,17 @@ fn empty_randomness_is_correct() {
|
||||
|
||||
#[test]
|
||||
fn initial_values() {
|
||||
new_test_ext(4).execute_with(|| {
|
||||
assert_eq!(Babe::authorities().len(), 4)
|
||||
})
|
||||
new_test_ext(4).execute_with(|| assert_eq!(Babe::authorities().len(), 4))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_module() {
|
||||
new_test_ext(4).execute_with(|| {
|
||||
assert!(!Babe::should_end_session(0), "Genesis does not change sessions");
|
||||
assert!(!Babe::should_end_session(200000),
|
||||
"BABE does not include the block number in epoch calculations");
|
||||
assert!(
|
||||
!Babe::should_end_session(200000),
|
||||
"BABE does not include the block number in epoch calculations"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,20 +64,10 @@ fn first_block_epoch_zero_start() {
|
||||
let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]);
|
||||
|
||||
let first_vrf = vrf_output;
|
||||
let pre_digest = make_primary_pre_digest(
|
||||
0,
|
||||
genesis_slot,
|
||||
first_vrf.clone(),
|
||||
vrf_proof,
|
||||
);
|
||||
let pre_digest = make_primary_pre_digest(0, genesis_slot, first_vrf.clone(), vrf_proof);
|
||||
|
||||
assert_eq!(Babe::genesis_slot(), Slot::from(0));
|
||||
System::initialize(
|
||||
&1,
|
||||
&Default::default(),
|
||||
&pre_digest,
|
||||
Default::default(),
|
||||
);
|
||||
System::initialize(&1, &Default::default(), &pre_digest, Default::default());
|
||||
|
||||
// see implementation of the function for details why: we issue an
|
||||
// epoch-change digest but don't do it via the normal session mechanism.
|
||||
@@ -106,7 +94,7 @@ fn first_block_epoch_zero_start() {
|
||||
sp_consensus_babe::digests::NextEpochDescriptor {
|
||||
authorities: Babe::authorities(),
|
||||
randomness: Babe::randomness(),
|
||||
}
|
||||
},
|
||||
);
|
||||
let consensus_digest = DigestItem::Consensus(BABE_ENGINE_ID, consensus_log.encode());
|
||||
|
||||
@@ -124,12 +112,7 @@ fn author_vrf_output_for_primary() {
|
||||
let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]);
|
||||
let primary_pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof);
|
||||
|
||||
System::initialize(
|
||||
&1,
|
||||
&Default::default(),
|
||||
&primary_pre_digest,
|
||||
Default::default(),
|
||||
);
|
||||
System::initialize(&1, &Default::default(), &primary_pre_digest, Default::default());
|
||||
|
||||
Babe::do_initialize(1);
|
||||
assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness));
|
||||
@@ -147,14 +130,10 @@ fn author_vrf_output_for_secondary_vrf() {
|
||||
ext.execute_with(|| {
|
||||
let genesis_slot = Slot::from(10);
|
||||
let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]);
|
||||
let secondary_vrf_pre_digest = make_secondary_vrf_pre_digest(0, genesis_slot, vrf_output, vrf_proof);
|
||||
let secondary_vrf_pre_digest =
|
||||
make_secondary_vrf_pre_digest(0, genesis_slot, vrf_output, vrf_proof);
|
||||
|
||||
System::initialize(
|
||||
&1,
|
||||
&Default::default(),
|
||||
&secondary_vrf_pre_digest,
|
||||
Default::default(),
|
||||
);
|
||||
System::initialize(&1, &Default::default(), &secondary_vrf_pre_digest, Default::default());
|
||||
|
||||
Babe::do_initialize(1);
|
||||
assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness));
|
||||
@@ -192,8 +171,10 @@ fn no_author_vrf_output_for_secondary_plain() {
|
||||
fn authority_index() {
|
||||
new_test_ext(4).execute_with(|| {
|
||||
assert_eq!(
|
||||
Babe::find_author((&[(BABE_ENGINE_ID, &[][..])]).into_iter().cloned()), None,
|
||||
"Trivially invalid authorities are ignored")
|
||||
Babe::find_author((&[(BABE_ENGINE_ID, &[][..])]).into_iter().cloned()),
|
||||
None,
|
||||
"Trivially invalid authorities are ignored"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -237,7 +218,10 @@ fn can_estimate_current_epoch_progress() {
|
||||
Permill::from_percent(100)
|
||||
);
|
||||
} else {
|
||||
assert!(Babe::estimate_current_session_progress(i).0.unwrap() < Permill::from_percent(100));
|
||||
assert!(
|
||||
Babe::estimate_current_session_progress(i).0.unwrap() <
|
||||
Permill::from_percent(100)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +271,8 @@ fn can_enact_next_config() {
|
||||
c: next_next_config.c,
|
||||
allowed_slots: next_next_config.allowed_slots,
|
||||
},
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
progress_to_block(4);
|
||||
Babe::on_finalize(9);
|
||||
@@ -296,12 +281,11 @@ fn can_enact_next_config() {
|
||||
assert_eq!(EpochConfig::<Test>::get(), Some(next_config));
|
||||
assert_eq!(NextEpochConfig::<Test>::get(), Some(next_next_config.clone()));
|
||||
|
||||
let consensus_log = sp_consensus_babe::ConsensusLog::NextConfigData(
|
||||
NextConfigDescriptor::V1 {
|
||||
let consensus_log =
|
||||
sp_consensus_babe::ConsensusLog::NextConfigData(NextConfigDescriptor::V1 {
|
||||
c: next_next_config.c,
|
||||
allowed_slots: next_next_config.allowed_slots,
|
||||
}
|
||||
);
|
||||
});
|
||||
let consensus_digest = DigestItem::Consensus(BABE_ENGINE_ID, consensus_log.encode());
|
||||
|
||||
assert_eq!(header.digest.logs[2], consensus_digest.clone())
|
||||
@@ -313,29 +297,18 @@ fn only_root_can_enact_config_change() {
|
||||
use sp_runtime::DispatchError;
|
||||
|
||||
new_test_ext(1).execute_with(|| {
|
||||
let next_config = NextConfigDescriptor::V1 {
|
||||
c: (1, 4),
|
||||
allowed_slots: AllowedSlots::PrimarySlots,
|
||||
};
|
||||
let next_config =
|
||||
NextConfigDescriptor::V1 { c: (1, 4), allowed_slots: AllowedSlots::PrimarySlots };
|
||||
|
||||
let res = Babe::plan_config_change(
|
||||
Origin::none(),
|
||||
next_config.clone(),
|
||||
);
|
||||
let res = Babe::plan_config_change(Origin::none(), next_config.clone());
|
||||
|
||||
assert_noop!(res, DispatchError::BadOrigin);
|
||||
|
||||
let res = Babe::plan_config_change(
|
||||
Origin::signed(1),
|
||||
next_config.clone(),
|
||||
);
|
||||
let res = Babe::plan_config_change(Origin::signed(1), next_config.clone());
|
||||
|
||||
assert_noop!(res, DispatchError::BadOrigin);
|
||||
|
||||
let res = Babe::plan_config_change(
|
||||
Origin::root(),
|
||||
next_config,
|
||||
);
|
||||
let res = Babe::plan_config_change(Origin::root(), next_config);
|
||||
|
||||
assert!(res.is_ok());
|
||||
});
|
||||
@@ -350,10 +323,7 @@ fn can_fetch_current_and_next_epoch_data() {
|
||||
});
|
||||
|
||||
// genesis authorities should be used for the first and second epoch
|
||||
assert_eq!(
|
||||
Babe::current_epoch().authorities,
|
||||
Babe::next_epoch().authorities,
|
||||
);
|
||||
assert_eq!(Babe::current_epoch().authorities, Babe::next_epoch().authorities,);
|
||||
// 1 era = 3 epochs
|
||||
// 1 epoch = 3 slots
|
||||
// Eras start from 0.
|
||||
@@ -420,11 +390,7 @@ fn report_equivocation_current_session_works() {
|
||||
|
||||
assert_eq!(
|
||||
Staking::eras_stakers(1, validator),
|
||||
pallet_staking::Exposure {
|
||||
total: 10_000,
|
||||
own: 10_000,
|
||||
others: vec![],
|
||||
},
|
||||
pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -445,10 +411,7 @@ fn report_equivocation_current_session_works() {
|
||||
);
|
||||
|
||||
// create the key ownership proof
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public());
|
||||
let key_owner_proof = Historical::prove(key).unwrap();
|
||||
|
||||
// report the equivocation
|
||||
@@ -460,35 +423,24 @@ fn report_equivocation_current_session_works() {
|
||||
start_era(2);
|
||||
|
||||
// check that the balance of offending validator is slashed 100%.
|
||||
assert_eq!(
|
||||
Balances::total_balance(&offending_validator_id),
|
||||
10_000_000 - 10_000
|
||||
);
|
||||
assert_eq!(Balances::total_balance(&offending_validator_id), 10_000_000 - 10_000);
|
||||
assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0);
|
||||
assert_eq!(
|
||||
Staking::eras_stakers(2, offending_validator_id),
|
||||
pallet_staking::Exposure {
|
||||
total: 0,
|
||||
own: 0,
|
||||
others: vec![],
|
||||
},
|
||||
pallet_staking::Exposure { total: 0, own: 0, others: vec![] },
|
||||
);
|
||||
|
||||
// check that the balances of all other validators are left intact.
|
||||
for validator in &validators {
|
||||
if *validator == offending_validator_id {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
assert_eq!(Balances::total_balance(validator), 10_000_000);
|
||||
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
|
||||
assert_eq!(
|
||||
Staking::eras_stakers(2, validator),
|
||||
pallet_staking::Exposure {
|
||||
total: 10_000,
|
||||
own: 10_000,
|
||||
others: vec![],
|
||||
},
|
||||
pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -519,10 +471,7 @@ fn report_equivocation_old_session_works() {
|
||||
);
|
||||
|
||||
// create the key ownership proof
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public());
|
||||
let key_owner_proof = Historical::prove(key).unwrap();
|
||||
|
||||
// start a new era and report the equivocation
|
||||
@@ -531,10 +480,7 @@ fn report_equivocation_old_session_works() {
|
||||
|
||||
// check the balance of the offending validator
|
||||
assert_eq!(Balances::total_balance(&offending_validator_id), 10_000_000);
|
||||
assert_eq!(
|
||||
Staking::slashable_balance_of(&offending_validator_id),
|
||||
10_000
|
||||
);
|
||||
assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 10_000);
|
||||
|
||||
// report the equivocation
|
||||
Babe::report_equivocation_unsigned(Origin::none(), equivocation_proof, key_owner_proof)
|
||||
@@ -545,18 +491,11 @@ fn report_equivocation_old_session_works() {
|
||||
start_era(3);
|
||||
|
||||
// check that the balance of offending validator is slashed 100%.
|
||||
assert_eq!(
|
||||
Balances::total_balance(&offending_validator_id),
|
||||
10_000_000 - 10_000
|
||||
);
|
||||
assert_eq!(Balances::total_balance(&offending_validator_id), 10_000_000 - 10_000);
|
||||
assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0);
|
||||
assert_eq!(
|
||||
Staking::eras_stakers(3, offending_validator_id),
|
||||
pallet_staking::Exposure {
|
||||
total: 0,
|
||||
own: 0,
|
||||
others: vec![],
|
||||
},
|
||||
pallet_staking::Exposure { total: 0, own: 0, others: vec![] },
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -585,10 +524,7 @@ fn report_equivocation_invalid_key_owner_proof() {
|
||||
);
|
||||
|
||||
// create the key ownership proof
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public());
|
||||
let mut key_owner_proof = Historical::prove(key).unwrap();
|
||||
|
||||
// we change the session index in the key ownership proof
|
||||
@@ -640,10 +576,7 @@ fn report_equivocation_invalid_equivocation_proof() {
|
||||
.unwrap();
|
||||
|
||||
// create the key ownership proof
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public());
|
||||
let key_owner_proof = Historical::prove(key).unwrap();
|
||||
|
||||
let assert_invalid_equivocation = |equivocation_proof| {
|
||||
@@ -753,10 +686,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() {
|
||||
CurrentSlot::<Test>::get(),
|
||||
);
|
||||
|
||||
let key = (
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
);
|
||||
let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public());
|
||||
let key_owner_proof = Historical::prove(key).unwrap();
|
||||
|
||||
let inner =
|
||||
@@ -815,23 +745,19 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() {
|
||||
fn report_equivocation_has_valid_weight() {
|
||||
// the weight depends on the size of the validator set,
|
||||
// but there's a lower bound of 100 validators.
|
||||
assert!(
|
||||
(1..=100)
|
||||
.map(<Test as Config>::WeightInfo::report_equivocation)
|
||||
.collect::<Vec<_>>()
|
||||
.windows(2)
|
||||
.all(|w| w[0] == w[1])
|
||||
);
|
||||
assert!((1..=100)
|
||||
.map(<Test as Config>::WeightInfo::report_equivocation)
|
||||
.collect::<Vec<_>>()
|
||||
.windows(2)
|
||||
.all(|w| w[0] == w[1]));
|
||||
|
||||
// after 100 validators the weight should keep increasing
|
||||
// with every extra validator.
|
||||
assert!(
|
||||
(100..=1000)
|
||||
.map(<Test as Config>::WeightInfo::report_equivocation)
|
||||
.collect::<Vec<_>>()
|
||||
.windows(2)
|
||||
.all(|w| w[0] < w[1])
|
||||
);
|
||||
assert!((100..=1000)
|
||||
.map(<Test as Config>::WeightInfo::report_equivocation)
|
||||
.collect::<Vec<_>>()
|
||||
.windows(2)
|
||||
.all(|w| w[0] < w[1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -848,11 +774,9 @@ fn valid_equivocation_reports_dont_pay_fees() {
|
||||
generate_equivocation_proof(0, &offending_authority_pair, CurrentSlot::<Test>::get());
|
||||
|
||||
// create the key ownership proof.
|
||||
let key_owner_proof = Historical::prove((
|
||||
sp_consensus_babe::KEY_TYPE,
|
||||
&offending_authority_pair.public(),
|
||||
))
|
||||
.unwrap();
|
||||
let key_owner_proof =
|
||||
Historical::prove((sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public()))
|
||||
.unwrap();
|
||||
|
||||
// check the dispatch info for the call.
|
||||
let info = Call::<Test>::report_equivocation_unsigned(
|
||||
@@ -894,9 +818,7 @@ fn valid_equivocation_reports_dont_pay_fees() {
|
||||
|
||||
#[test]
|
||||
fn add_epoch_configurations_migration_works() {
|
||||
use frame_support::storage::migration::{
|
||||
put_storage_value, get_storage_value,
|
||||
};
|
||||
use frame_support::storage::migration::{get_storage_value, put_storage_value};
|
||||
|
||||
impl crate::migrations::BabePalletPrefix for Test {
|
||||
fn pallet_prefix() -> &'static str {
|
||||
@@ -905,38 +827,31 @@ fn add_epoch_configurations_migration_works() {
|
||||
}
|
||||
|
||||
new_test_ext(1).execute_with(|| {
|
||||
let next_config_descriptor = NextConfigDescriptor::V1 {
|
||||
c: (3, 4),
|
||||
allowed_slots: AllowedSlots::PrimarySlots
|
||||
};
|
||||
let next_config_descriptor =
|
||||
NextConfigDescriptor::V1 { c: (3, 4), allowed_slots: AllowedSlots::PrimarySlots };
|
||||
|
||||
put_storage_value(
|
||||
b"Babe",
|
||||
b"NextEpochConfig",
|
||||
&[],
|
||||
Some(next_config_descriptor.clone())
|
||||
);
|
||||
put_storage_value(b"Babe", b"NextEpochConfig", &[], Some(next_config_descriptor.clone()));
|
||||
|
||||
assert!(get_storage_value::<Option<NextConfigDescriptor>>(
|
||||
b"Babe",
|
||||
b"NextEpochConfig",
|
||||
&[],
|
||||
).is_some());
|
||||
)
|
||||
.is_some());
|
||||
|
||||
let current_epoch = BabeEpochConfiguration {
|
||||
c: (1, 4),
|
||||
allowed_slots: sp_consensus_babe::AllowedSlots::PrimarySlots,
|
||||
};
|
||||
|
||||
crate::migrations::add_epoch_configuration::<Test>(
|
||||
current_epoch.clone()
|
||||
);
|
||||
crate::migrations::add_epoch_configuration::<Test>(current_epoch.clone());
|
||||
|
||||
assert!(get_storage_value::<Option<NextConfigDescriptor>>(
|
||||
b"Babe",
|
||||
b"NextEpochConfig",
|
||||
&[],
|
||||
).is_none());
|
||||
)
|
||||
.is_none());
|
||||
|
||||
assert_eq!(EpochConfig::<Test>::get(), Some(current_epoch));
|
||||
assert_eq!(PendingEpochConfigChange::<Test>::get(), Some(next_config_descriptor));
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_benchmarking::{
|
||||
account, benchmarks_instance_pallet, impl_benchmark_test_suite, whitelisted_caller,
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use frame_benchmarking::{benchmarks_instance_pallet, account, whitelisted_caller, impl_benchmark_test_suite};
|
||||
use sp_runtime::traits::Bounded;
|
||||
|
||||
use crate::Pallet as Balances;
|
||||
@@ -31,7 +33,6 @@ const SEED: u32 = 0;
|
||||
// existential deposit multiplier
|
||||
const ED_MULTIPLIER: u32 = 10;
|
||||
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
// Benchmark `transfer` extrinsic with the worst possible conditions:
|
||||
// * Transfer will kill the sender account.
|
||||
|
||||
+421
-272
File diff suppressed because it is too large
Load Diff
@@ -19,19 +19,15 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sp_runtime::{
|
||||
traits::IdentityLookup,
|
||||
testing::Header,
|
||||
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
weights::{DispatchInfo, IdentityFee, Weight},
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use crate::{
|
||||
self as pallet_balances,
|
||||
Pallet, Config, decl_tests,
|
||||
};
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
@@ -110,10 +106,7 @@ pub struct ExtBuilder {
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
existential_deposit: 1,
|
||||
monied: false,
|
||||
}
|
||||
Self { existential_deposit: 1, monied: false }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
@@ -138,12 +131,14 @@ impl ExtBuilder {
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit)
|
||||
(12, 10 * self.existential_deposit),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
@@ -151,4 +146,4 @@ impl ExtBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
|
||||
@@ -19,20 +19,16 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sp_runtime::{
|
||||
traits::IdentityLookup,
|
||||
testing::Header,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::traits::StorageMapShim;
|
||||
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
|
||||
use crate::{
|
||||
self as pallet_balances,
|
||||
Pallet, Config, decl_tests,
|
||||
use crate::{self as pallet_balances, decl_tests, Config, Pallet};
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::StorageMapShim,
|
||||
weights::{DispatchInfo, IdentityFee, Weight},
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
@@ -98,12 +94,8 @@ impl Config for Test {
|
||||
type DustRemoval = ();
|
||||
type Event = Event;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = StorageMapShim<
|
||||
super::Account<Test>,
|
||||
system::Provider<Test>,
|
||||
u64,
|
||||
super::AccountData<u64>,
|
||||
>;
|
||||
type AccountStore =
|
||||
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
|
||||
type MaxLocks = MaxLocks;
|
||||
type MaxReserves = MaxReserves;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
@@ -116,10 +108,7 @@ pub struct ExtBuilder {
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
existential_deposit: 1,
|
||||
monied: false,
|
||||
}
|
||||
Self { existential_deposit: 1, monied: false }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
@@ -147,12 +136,14 @@ impl ExtBuilder {
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit)
|
||||
(12, 10 * self.existential_deposit),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
@@ -160,40 +151,37 @@ impl ExtBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
|
||||
#[test]
|
||||
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
|
||||
<ExtBuilder>::default()
|
||||
.existential_deposit(2)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
|
||||
<ExtBuilder>::default().existential_deposit(2).build().execute_with(|| {
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
Event::System(system::Event::NewAccount(1)),
|
||||
Event::Balances(crate::Event::Endowed(1, 100)),
|
||||
Event::Balances(crate::Event::BalanceSet(1, 100, 0)),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
Event::System(system::Event::NewAccount(1)),
|
||||
Event::Balances(crate::Event::Endowed(1, 100)),
|
||||
Event::Balances(crate::Event::BalanceSet(1, 100, 0)),
|
||||
]
|
||||
);
|
||||
|
||||
let res = Balances::slash(&1, 98);
|
||||
assert_eq!(res, (NegativeImbalance::new(98), 0));
|
||||
let res = Balances::slash(&1, 98);
|
||||
assert_eq!(res, (NegativeImbalance::new(98), 0));
|
||||
|
||||
// no events
|
||||
assert_eq!(events(), []);
|
||||
// no events
|
||||
assert_eq!(events(), []);
|
||||
|
||||
let res = Balances::slash(&1, 1);
|
||||
assert_eq!(res, (NegativeImbalance::new(1), 0));
|
||||
let res = Balances::slash(&1, 1);
|
||||
assert_eq!(res, (NegativeImbalance::new(1), 0));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
Event::System(system::Event::KilledAccount(1)),
|
||||
Event::Balances(crate::Event::DustLost(1, 1)),
|
||||
]
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
Event::System(system::Event::KilledAccount(1)),
|
||||
Event::Balances(crate::Event::DustLost(1, 1)),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,27 +19,17 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sp_runtime::{
|
||||
traits::IdentityLookup,
|
||||
testing::Header,
|
||||
};
|
||||
use crate::{self as pallet_balances, Config, Pallet};
|
||||
use frame_support::{parameter_types, traits::StorageMapShim, weights::IdentityFee};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_core::H256;
|
||||
use sp_io;
|
||||
use frame_support::parameter_types;
|
||||
use frame_support::traits::StorageMapShim;
|
||||
use frame_support::weights::{IdentityFee};
|
||||
use crate::{
|
||||
self as pallet_balances,
|
||||
Pallet, Config,
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_runtime::{testing::Header, traits::IdentityLookup};
|
||||
|
||||
use crate::*;
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{
|
||||
Currency, ReservableCurrency,
|
||||
}
|
||||
traits::{Currency, ReservableCurrency},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
@@ -113,12 +103,8 @@ impl Config for Test {
|
||||
type DustRemoval = OnDustRemoval;
|
||||
type Event = Event;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = StorageMapShim<
|
||||
super::Account<Test>,
|
||||
system::Provider<Test>,
|
||||
u64,
|
||||
super::AccountData<u64>,
|
||||
>;
|
||||
type AccountStore =
|
||||
StorageMapShim<super::Account<Test>, system::Provider<Test>, u64, super::AccountData<u64>>;
|
||||
type MaxLocks = MaxLocks;
|
||||
type MaxReserves = MaxReserves;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
@@ -130,13 +116,10 @@ pub struct ExtBuilder {
|
||||
}
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
existential_deposit: 1,
|
||||
}
|
||||
Self { existential_deposit: 1 }
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
@@ -149,9 +132,9 @@ impl ExtBuilder {
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
self.set_associated_consts();
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> { balances: vec![] }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
@@ -160,112 +143,103 @@ impl ExtBuilder {
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst1_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 3, 450));
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 3, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// As expected beneficiary account 3
|
||||
// received the transfered fund.
|
||||
assert_eq!(Balances::free_balance(&3), 450);
|
||||
// As expected beneficiary account 3
|
||||
// received the transfered fund.
|
||||
assert_eq!(Balances::free_balance(&3), 450);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1050);
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1050);
|
||||
|
||||
// Verify the events
|
||||
// Number of events expected is 8
|
||||
assert_eq!(System::events().len(), 11);
|
||||
// Verify the events
|
||||
// Number of events expected is 8
|
||||
assert_eq!(System::events().len(), 11);
|
||||
|
||||
System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 3, 450)));
|
||||
System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
}
|
||||
);
|
||||
System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 3, 450)));
|
||||
System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_dust_removal_tst2_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 1, 450));
|
||||
// In this transaction, account 2 free balance
|
||||
// drops below existential balance
|
||||
// and dust balance is removed from account 2
|
||||
assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 1, 450));
|
||||
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
// As expected dust balance is removed.
|
||||
assert_eq!(Balances::free_balance(&2), 0);
|
||||
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1500);
|
||||
// Dust balance is deposited to account 1
|
||||
// during the process of dust removal.
|
||||
assert_eq!(Balances::free_balance(&1), 1500);
|
||||
|
||||
// Verify the events
|
||||
// Number of events expected is 8
|
||||
assert_eq!(System::events().len(), 9);
|
||||
// Verify the events
|
||||
// Number of events expected is 8
|
||||
assert_eq!(System::events().len(), 9);
|
||||
|
||||
System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 1, 450)));
|
||||
System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
}
|
||||
);
|
||||
System::assert_has_event(Event::Balances(crate::Event::Transfer(2, 1, 450)));
|
||||
System::assert_has_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriating_reserved_balance_dust_removal_should_work() {
|
||||
ExtBuilder::default()
|
||||
.existential_deposit(100)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
// Verification of reentrancy in dust removal
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0));
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0));
|
||||
|
||||
// Reserve a value on account 2,
|
||||
// Such that free balance is lower than
|
||||
// Exestintial deposit.
|
||||
assert_ok!(Balances::reserve(&2, 450));
|
||||
// Reserve a value on account 2,
|
||||
// Such that free balance is lower than
|
||||
// Exestintial deposit.
|
||||
assert_ok!(Balances::reserve(&2, 450));
|
||||
|
||||
// Transfer of reserved fund from slashed account 2 to
|
||||
// beneficiary account 1
|
||||
assert_ok!(Balances::repatriate_reserved(&2, &1, 450, Status::Free), 0);
|
||||
// Transfer of reserved fund from slashed account 2 to
|
||||
// beneficiary account 1
|
||||
assert_ok!(Balances::repatriate_reserved(&2, &1, 450, Status::Free), 0);
|
||||
|
||||
// Since free balance of account 2 is lower than
|
||||
// existential deposit, dust amount is
|
||||
// removed from the account 2
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(2), 0);
|
||||
// Since free balance of account 2 is lower than
|
||||
// existential deposit, dust amount is
|
||||
// removed from the account 2
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(2), 0);
|
||||
|
||||
// account 1 is credited with reserved amount
|
||||
// together with dust balance during dust
|
||||
// removal.
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(1), 1500);
|
||||
// account 1 is credited with reserved amount
|
||||
// together with dust balance during dust
|
||||
// removal.
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(1), 1500);
|
||||
|
||||
// Verify the events
|
||||
// Number of events expected is 10
|
||||
assert_eq!(System::events().len(), 10);
|
||||
// Verify the events
|
||||
// Number of events expected is 10
|
||||
assert_eq!(System::events().len(), 10);
|
||||
|
||||
System::assert_has_event(Event::Balances(
|
||||
crate::Event::ReserveRepatriated(2, 1, 450, Status::Free),
|
||||
));
|
||||
System::assert_has_event(Event::Balances(crate::Event::ReserveRepatriated(
|
||||
2,
|
||||
1,
|
||||
450,
|
||||
Status::Free,
|
||||
)));
|
||||
|
||||
System::assert_last_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
}
|
||||
);
|
||||
System::assert_last_event(Event::Balances(crate::Event::DustLost(2, 50)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
//! Tools for analyzing the benchmark results.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use crate::BenchmarkResults;
|
||||
use core::convert::TryFrom;
|
||||
use linregress::{FormulaRegressionBuilder, RegressionDataBuilder};
|
||||
use crate::BenchmarkResults;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use linregress::RegressionModel;
|
||||
|
||||
@@ -63,14 +63,12 @@ impl TryFrom<Option<String>> for AnalysisChoice {
|
||||
fn try_from(s: Option<String>) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
None => Ok(AnalysisChoice::default()),
|
||||
Some(i) => {
|
||||
match &i[..] {
|
||||
"min-squares" | "min_squares" => Ok(AnalysisChoice::MinSquares),
|
||||
"median-slopes" | "median_slopes" => Ok(AnalysisChoice::MedianSlopes),
|
||||
"max" => Ok(AnalysisChoice::Max),
|
||||
_ => Err("invalid analysis string")
|
||||
}
|
||||
}
|
||||
Some(i) => match &i[..] {
|
||||
"min-squares" | "min_squares" => Ok(AnalysisChoice::MinSquares),
|
||||
"median-slopes" | "median_slopes" => Ok(AnalysisChoice::MedianSlopes),
|
||||
"max" => Ok(AnalysisChoice::Max),
|
||||
_ => Err("invalid analysis string"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,17 +77,20 @@ impl Analysis {
|
||||
// Useful for when there are no components, and we just need an median value of the benchmark results.
|
||||
// Note: We choose the median value because it is more robust to outliers.
|
||||
fn median_value(r: &Vec<BenchmarkResults>, selector: BenchmarkSelector) -> Option<Self> {
|
||||
if r.is_empty() { return None }
|
||||
if r.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
let mut values: Vec<u128> = r.iter().map(|result|
|
||||
match selector {
|
||||
let mut values: Vec<u128> = r
|
||||
.iter()
|
||||
.map(|result| match selector {
|
||||
BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
|
||||
BenchmarkSelector::StorageRootTime => result.storage_root_time,
|
||||
BenchmarkSelector::Reads => result.reads.into(),
|
||||
BenchmarkSelector::Writes => result.writes.into(),
|
||||
BenchmarkSelector::ProofSize => result.proof_size.into(),
|
||||
}
|
||||
).collect();
|
||||
})
|
||||
.collect();
|
||||
|
||||
values.sort();
|
||||
let mid = values.len() / 2;
|
||||
@@ -104,64 +105,80 @@ impl Analysis {
|
||||
}
|
||||
|
||||
pub fn median_slopes(r: &Vec<BenchmarkResults>, selector: BenchmarkSelector) -> Option<Self> {
|
||||
if r[0].components.is_empty() { return Self::median_value(r, selector) }
|
||||
if r[0].components.is_empty() {
|
||||
return Self::median_value(r, selector)
|
||||
}
|
||||
|
||||
let results = r[0].components.iter().enumerate().map(|(i, &(param, _))| {
|
||||
let mut counted = BTreeMap::<Vec<u32>, usize>::new();
|
||||
for result in r.iter() {
|
||||
let mut p = result.components.iter().map(|x| x.1).collect::<Vec<_>>();
|
||||
p[i] = 0;
|
||||
*counted.entry(p).or_default() += 1;
|
||||
}
|
||||
let others: Vec<u32> = counted.iter().max_by_key(|i| i.1).expect("r is not empty; qed").0.clone();
|
||||
let values = r.iter()
|
||||
.filter(|v|
|
||||
v.components.iter()
|
||||
.map(|x| x.1)
|
||||
.zip(others.iter())
|
||||
.enumerate()
|
||||
.all(|(j, (v1, v2))| j == i || v1 == *v2)
|
||||
).map(|result| {
|
||||
// Extract the data we are interested in analyzing
|
||||
let data = match selector {
|
||||
BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
|
||||
BenchmarkSelector::StorageRootTime => result.storage_root_time,
|
||||
BenchmarkSelector::Reads => result.reads.into(),
|
||||
BenchmarkSelector::Writes => result.writes.into(),
|
||||
BenchmarkSelector::ProofSize => result.proof_size.into(),
|
||||
};
|
||||
(result.components[i].1, data)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(format!("{:?}", param), i, others, values)
|
||||
}).collect::<Vec<_>>();
|
||||
let results = r[0]
|
||||
.components
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &(param, _))| {
|
||||
let mut counted = BTreeMap::<Vec<u32>, usize>::new();
|
||||
for result in r.iter() {
|
||||
let mut p = result.components.iter().map(|x| x.1).collect::<Vec<_>>();
|
||||
p[i] = 0;
|
||||
*counted.entry(p).or_default() += 1;
|
||||
}
|
||||
let others: Vec<u32> =
|
||||
counted.iter().max_by_key(|i| i.1).expect("r is not empty; qed").0.clone();
|
||||
let values = r
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
v.components
|
||||
.iter()
|
||||
.map(|x| x.1)
|
||||
.zip(others.iter())
|
||||
.enumerate()
|
||||
.all(|(j, (v1, v2))| j == i || v1 == *v2)
|
||||
})
|
||||
.map(|result| {
|
||||
// Extract the data we are interested in analyzing
|
||||
let data = match selector {
|
||||
BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
|
||||
BenchmarkSelector::StorageRootTime => result.storage_root_time,
|
||||
BenchmarkSelector::Reads => result.reads.into(),
|
||||
BenchmarkSelector::Writes => result.writes.into(),
|
||||
BenchmarkSelector::ProofSize => result.proof_size.into(),
|
||||
};
|
||||
(result.components[i].1, data)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(format!("{:?}", param), i, others, values)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let models = results.iter().map(|(_, _, _, ref values)| {
|
||||
let mut slopes = vec![];
|
||||
for (i, &(x1, y1)) in values.iter().enumerate() {
|
||||
for &(x2, y2) in values.iter().skip(i + 1) {
|
||||
if x1 != x2 {
|
||||
slopes.push((y1 as f64 - y2 as f64) / (x1 as f64 - x2 as f64));
|
||||
let models = results
|
||||
.iter()
|
||||
.map(|(_, _, _, ref values)| {
|
||||
let mut slopes = vec![];
|
||||
for (i, &(x1, y1)) in values.iter().enumerate() {
|
||||
for &(x2, y2) in values.iter().skip(i + 1) {
|
||||
if x1 != x2 {
|
||||
slopes.push((y1 as f64 - y2 as f64) / (x1 as f64 - x2 as f64));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
slopes.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
|
||||
let slope = slopes[slopes.len() / 2];
|
||||
slopes.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
|
||||
let slope = slopes[slopes.len() / 2];
|
||||
|
||||
let mut offsets = vec![];
|
||||
for &(x, y) in values.iter() {
|
||||
offsets.push(y as f64 - slope * x as f64);
|
||||
}
|
||||
offsets.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
|
||||
let offset = offsets[offsets.len() / 2];
|
||||
let mut offsets = vec![];
|
||||
for &(x, y) in values.iter() {
|
||||
offsets.push(y as f64 - slope * x as f64);
|
||||
}
|
||||
offsets.sort_by(|a, b| a.partial_cmp(b).expect("values well defined; qed"));
|
||||
let offset = offsets[offsets.len() / 2];
|
||||
|
||||
(offset, slope)
|
||||
}).collect::<Vec<_>>();
|
||||
(offset, slope)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let models = models.iter()
|
||||
let models = models
|
||||
.iter()
|
||||
.zip(results.iter())
|
||||
.map(|((offset, slope), (_, i, others, _))| {
|
||||
let over = others.iter()
|
||||
let over = others
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(j, _)| j != i)
|
||||
.map(|(j, v)| models[j].1 * *v as f64)
|
||||
@@ -183,18 +200,20 @@ impl Analysis {
|
||||
}
|
||||
|
||||
pub fn min_squares_iqr(r: &Vec<BenchmarkResults>, selector: BenchmarkSelector) -> Option<Self> {
|
||||
if r[0].components.is_empty() { return Self::median_value(r, selector) }
|
||||
if r[0].components.is_empty() {
|
||||
return Self::median_value(r, selector)
|
||||
}
|
||||
|
||||
let mut results = BTreeMap::<Vec<u32>, Vec<u128>>::new();
|
||||
for result in r.iter() {
|
||||
let p = result.components.iter().map(|x| x.1).collect::<Vec<_>>();
|
||||
results.entry(p).or_default().push(match selector {
|
||||
BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
|
||||
BenchmarkSelector::StorageRootTime => result.storage_root_time,
|
||||
BenchmarkSelector::Reads => result.reads.into(),
|
||||
BenchmarkSelector::Writes => result.writes.into(),
|
||||
BenchmarkSelector::ProofSize => result.proof_size.into(),
|
||||
})
|
||||
BenchmarkSelector::ExtrinsicTime => result.extrinsic_time,
|
||||
BenchmarkSelector::StorageRootTime => result.storage_root_time,
|
||||
BenchmarkSelector::Reads => result.reads.into(),
|
||||
BenchmarkSelector::Writes => result.writes.into(),
|
||||
BenchmarkSelector::ProofSize => result.proof_size.into(),
|
||||
})
|
||||
}
|
||||
|
||||
for (_, rs) in results.iter_mut() {
|
||||
@@ -203,21 +222,19 @@ impl Analysis {
|
||||
*rs = rs[ql..rs.len() - ql].to_vec();
|
||||
}
|
||||
|
||||
let mut data = vec![("Y", results.iter().flat_map(|x| x.1.iter().map(|v| *v as f64)).collect())];
|
||||
let mut data =
|
||||
vec![("Y", results.iter().flat_map(|x| x.1.iter().map(|v| *v as f64)).collect())];
|
||||
|
||||
let names = r[0].components.iter().map(|x| format!("{:?}", x.0)).collect::<Vec<_>>();
|
||||
data.extend(names.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| (
|
||||
data.extend(names.iter().enumerate().map(|(i, p)| {
|
||||
(
|
||||
p.as_str(),
|
||||
results.iter()
|
||||
.flat_map(|x| Some(x.0[i] as f64)
|
||||
.into_iter()
|
||||
.cycle()
|
||||
.take(x.1.len())
|
||||
).collect::<Vec<_>>()
|
||||
))
|
||||
);
|
||||
results
|
||||
.iter()
|
||||
.flat_map(|x| Some(x.0[i] as f64).into_iter().cycle().take(x.1.len()))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}));
|
||||
|
||||
let data = RegressionDataBuilder::new().build_from(data).ok()?;
|
||||
|
||||
@@ -227,25 +244,31 @@ impl Analysis {
|
||||
.fit()
|
||||
.ok()?;
|
||||
|
||||
let slopes = model.parameters.regressor_values.iter()
|
||||
let slopes = model
|
||||
.parameters
|
||||
.regressor_values
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(_, x)| (*x + 0.5) as u128)
|
||||
.collect();
|
||||
|
||||
let value_dists = results.iter().map(|(p, vs)| {
|
||||
// Avoid divide by zero
|
||||
if vs.len() == 0 { return (p.clone(), 0, 0) }
|
||||
let total = vs.iter()
|
||||
.fold(0u128, |acc, v| acc + *v);
|
||||
let mean = total / vs.len() as u128;
|
||||
let sum_sq_diff = vs.iter()
|
||||
.fold(0u128, |acc, v| {
|
||||
let value_dists = results
|
||||
.iter()
|
||||
.map(|(p, vs)| {
|
||||
// Avoid divide by zero
|
||||
if vs.len() == 0 {
|
||||
return (p.clone(), 0, 0)
|
||||
}
|
||||
let total = vs.iter().fold(0u128, |acc, v| acc + *v);
|
||||
let mean = total / vs.len() as u128;
|
||||
let sum_sq_diff = vs.iter().fold(0u128, |acc, v| {
|
||||
let d = mean.max(*v) - mean.min(*v);
|
||||
acc + d * d
|
||||
});
|
||||
let stddev = (sum_sq_diff as f64 / vs.len() as f64).sqrt() as u128;
|
||||
(p.clone(), mean, stddev)
|
||||
}).collect::<Vec<_>>();
|
||||
let stddev = (sum_sq_diff as f64 / vs.len() as f64).sqrt() as u128;
|
||||
(p.clone(), mean, stddev)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(Self {
|
||||
base: (model.parameters.intercept_value + 0.5) as u128,
|
||||
@@ -261,32 +284,30 @@ impl Analysis {
|
||||
let min_squares = Self::min_squares_iqr(r, selector);
|
||||
|
||||
if median_slopes.is_none() || min_squares.is_none() {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
let median_slopes = median_slopes.unwrap();
|
||||
let min_squares = min_squares.unwrap();
|
||||
|
||||
let base = median_slopes.base.max(min_squares.base);
|
||||
let slopes = median_slopes.slopes.into_iter()
|
||||
let slopes = median_slopes
|
||||
.slopes
|
||||
.into_iter()
|
||||
.zip(min_squares.slopes.into_iter())
|
||||
.map(|(a, b): (u128, u128)| { a.max(b) })
|
||||
.map(|(a, b): (u128, u128)| a.max(b))
|
||||
.collect::<Vec<u128>>();
|
||||
// components should always be in the same order
|
||||
median_slopes.names.iter()
|
||||
median_slopes
|
||||
.names
|
||||
.iter()
|
||||
.zip(min_squares.names.iter())
|
||||
.for_each(|(a, b)| assert!(a == b, "benchmark results not in the same order"));
|
||||
let names = median_slopes.names;
|
||||
let value_dists = min_squares.value_dists;
|
||||
let model = min_squares.model;
|
||||
|
||||
Some(Self {
|
||||
base,
|
||||
slopes,
|
||||
names,
|
||||
value_dists,
|
||||
model,
|
||||
})
|
||||
Some(Self { base, slopes, names, value_dists, model })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +316,7 @@ fn ms(mut nanos: u128) -> String {
|
||||
while x > 1 {
|
||||
if nanos > x * 1_000 {
|
||||
nanos = nanos / x * x;
|
||||
break;
|
||||
break
|
||||
}
|
||||
x /= 10;
|
||||
}
|
||||
@@ -306,19 +327,35 @@ impl std::fmt::Display for Analysis {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
if let Some(ref value_dists) = self.value_dists {
|
||||
writeln!(f, "\nData points distribution:")?;
|
||||
writeln!(f, "{} mean µs sigma µs %", self.names.iter().map(|p| format!("{:>5}", p)).collect::<Vec<_>>().join(" "))?;
|
||||
writeln!(
|
||||
f,
|
||||
"{} mean µs sigma µs %",
|
||||
self.names.iter().map(|p| format!("{:>5}", p)).collect::<Vec<_>>().join(" ")
|
||||
)?;
|
||||
for (param_values, mean, sigma) in value_dists.iter() {
|
||||
if *mean == 0 {
|
||||
writeln!(f, "{} {:>8} {:>8} {:>3}.{}%",
|
||||
param_values.iter().map(|v| format!("{:>5}", v)).collect::<Vec<_>>().join(" "),
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:>8} {:>8} {:>3}.{}%",
|
||||
param_values
|
||||
.iter()
|
||||
.map(|v| format!("{:>5}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
ms(*mean),
|
||||
ms(*sigma),
|
||||
"?",
|
||||
"?"
|
||||
)?;
|
||||
} else {
|
||||
writeln!(f, "{} {:>8} {:>8} {:>3}.{}%",
|
||||
param_values.iter().map(|v| format!("{:>5}", v)).collect::<Vec<_>>().join(" "),
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:>8} {:>8} {:>3}.{}%",
|
||||
param_values
|
||||
.iter()
|
||||
.map(|v| format!("{:>5}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
ms(*mean),
|
||||
ms(*sigma),
|
||||
(sigma * 100 / mean),
|
||||
@@ -350,7 +387,7 @@ impl std::fmt::Debug for Analysis {
|
||||
for (&m, n) in self.slopes.iter().zip(self.names.iter()) {
|
||||
write!(f, " + ({} * {})", m, n)?;
|
||||
}
|
||||
write!(f,"")
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,17 +419,66 @@ mod tests {
|
||||
#[test]
|
||||
fn analysis_median_slopes_should_work() {
|
||||
let data = vec![
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)], 11_500_000, 0, 3, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)], 12_500_000, 0, 4, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)], 13_500_000, 0, 5, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)], 14_500_000, 0, 6, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)], 13_100_000, 0, 5, 2),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)], 13_300_000, 0, 5, 6),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)], 13_700_000, 0, 5, 14),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)], 14_000_000, 0, 5, 20),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)],
|
||||
11_500_000,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)],
|
||||
12_500_000,
|
||||
0,
|
||||
4,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)],
|
||||
13_500_000,
|
||||
0,
|
||||
5,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)],
|
||||
14_500_000,
|
||||
0,
|
||||
6,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)],
|
||||
13_100_000,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)],
|
||||
13_300_000,
|
||||
0,
|
||||
5,
|
||||
6,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)],
|
||||
13_700_000,
|
||||
0,
|
||||
5,
|
||||
14,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)],
|
||||
14_000_000,
|
||||
0,
|
||||
5,
|
||||
20,
|
||||
),
|
||||
];
|
||||
|
||||
let extrinsic_time = Analysis::median_slopes(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
|
||||
let extrinsic_time =
|
||||
Analysis::median_slopes(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
|
||||
assert_eq!(extrinsic_time.base, 10_000_000);
|
||||
assert_eq!(extrinsic_time.slopes, vec![1_000_000, 100_000]);
|
||||
|
||||
@@ -408,17 +494,66 @@ mod tests {
|
||||
#[test]
|
||||
fn analysis_median_min_squares_should_work() {
|
||||
let data = vec![
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)], 11_500_000, 0, 3, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)], 12_500_000, 0, 4, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)], 13_500_000, 0, 5, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)], 14_500_000, 0, 6, 10),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)], 13_100_000, 0, 5, 2),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)], 13_300_000, 0, 5, 6),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)], 13_700_000, 0, 5, 14),
|
||||
benchmark_result(vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)], 14_000_000, 0, 5, 20),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 1), (BenchmarkParameter::m, 5)],
|
||||
11_500_000,
|
||||
0,
|
||||
3,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 2), (BenchmarkParameter::m, 5)],
|
||||
12_500_000,
|
||||
0,
|
||||
4,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 5)],
|
||||
13_500_000,
|
||||
0,
|
||||
5,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 4), (BenchmarkParameter::m, 5)],
|
||||
14_500_000,
|
||||
0,
|
||||
6,
|
||||
10,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 1)],
|
||||
13_100_000,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 3)],
|
||||
13_300_000,
|
||||
0,
|
||||
5,
|
||||
6,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 7)],
|
||||
13_700_000,
|
||||
0,
|
||||
5,
|
||||
14,
|
||||
),
|
||||
benchmark_result(
|
||||
vec![(BenchmarkParameter::n, 3), (BenchmarkParameter::m, 10)],
|
||||
14_000_000,
|
||||
0,
|
||||
5,
|
||||
20,
|
||||
),
|
||||
];
|
||||
|
||||
let extrinsic_time = Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
|
||||
let extrinsic_time =
|
||||
Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap();
|
||||
assert_eq!(extrinsic_time.base, 10_000_000);
|
||||
assert_eq!(extrinsic_time.slopes, vec![1_000_000, 100_000]);
|
||||
|
||||
|
||||
@@ -19,35 +19,35 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod tests;
|
||||
mod utils;
|
||||
#[cfg(feature = "std")]
|
||||
mod analysis;
|
||||
mod tests;
|
||||
mod utils;
|
||||
|
||||
pub use utils::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use analysis::{Analysis, BenchmarkSelector, RegressionModel, AnalysisChoice};
|
||||
pub use analysis::{Analysis, AnalysisChoice, BenchmarkSelector, RegressionModel};
|
||||
#[doc(hidden)]
|
||||
pub use frame_support;
|
||||
#[doc(hidden)]
|
||||
pub use log;
|
||||
#[doc(hidden)]
|
||||
pub use paste;
|
||||
#[doc(hidden)]
|
||||
pub use sp_io::storage::root as storage_root;
|
||||
#[doc(hidden)]
|
||||
pub use sp_runtime::traits::Zero;
|
||||
#[doc(hidden)]
|
||||
pub use frame_support;
|
||||
#[doc(hidden)]
|
||||
pub use sp_std::{self, vec, prelude::Vec, boxed::Box};
|
||||
#[doc(hidden)]
|
||||
pub use paste;
|
||||
pub use sp_std::{self, boxed::Box, prelude::Vec, vec};
|
||||
#[doc(hidden)]
|
||||
pub use sp_storage::TrackedStorageKey;
|
||||
#[doc(hidden)]
|
||||
pub use log;
|
||||
pub use utils::*;
|
||||
|
||||
/// Whitelist the given account.
|
||||
#[macro_export]
|
||||
macro_rules! whitelist {
|
||||
($acc:ident) => {
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(
|
||||
frame_system::Account::<T>::hashed_key_for(&$acc).into()
|
||||
frame_system::Account::<T>::hashed_key_for(&$acc).into(),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1081,7 +1081,6 @@ macro_rules! impl_benchmark_test {
|
||||
///
|
||||
/// - It must be the name of a method applied to the output of the `new_test_ext` argument.
|
||||
/// - That method must have a signature capable of receiving a single argument of the form `impl FnOnce()`.
|
||||
///
|
||||
// ## Notes (not for rustdoc)
|
||||
//
|
||||
// The biggest challenge for this macro is communicating the actual test functions to be run. We
|
||||
@@ -1260,9 +1259,9 @@ pub fn show_benchmark_debug_info(
|
||||
* Verify: {:?}\n\
|
||||
* Error message: {}",
|
||||
sp_std::str::from_utf8(instance_string)
|
||||
.expect("it's all just strings ran through the wasm interface. qed"),
|
||||
.expect("it's all just strings ran through the wasm interface. qed"),
|
||||
sp_std::str::from_utf8(benchmark)
|
||||
.expect("it's all just strings ran through the wasm interface. qed"),
|
||||
.expect("it's all just strings ran through the wasm interface. qed"),
|
||||
lowest_range_values,
|
||||
highest_range_values,
|
||||
steps,
|
||||
|
||||
@@ -20,9 +20,13 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::{H256, Header}, BuildStorage};
|
||||
use frame_support::parameter_types;
|
||||
use sp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod pallet_test {
|
||||
use frame_support::pallet_prelude::Get;
|
||||
@@ -59,7 +63,8 @@ mod pallet_test {
|
||||
}
|
||||
|
||||
pub trait Config: frame_system::Config + OtherConfig
|
||||
where Self::OtherEvent: Into<<Self as Config>::Event>
|
||||
where
|
||||
Self::OtherEvent: Into<<Self as Config>::Event>,
|
||||
{
|
||||
type Event;
|
||||
type LowerBound: Get<u32>;
|
||||
@@ -107,7 +112,7 @@ impl frame_system::Config for Test {
|
||||
type OnSetCode = ();
|
||||
}
|
||||
|
||||
parameter_types!{
|
||||
parameter_types! {
|
||||
pub const LowerBound: u32 = 1;
|
||||
pub const UpperBound: u32 = 100;
|
||||
}
|
||||
@@ -127,16 +132,20 @@ fn new_test_ext() -> sp_io::TestExternalities {
|
||||
}
|
||||
|
||||
mod benchmarks {
|
||||
use sp_std::prelude::*;
|
||||
use super::{
|
||||
new_test_ext,
|
||||
pallet_test::{self, Value},
|
||||
Test,
|
||||
};
|
||||
use crate::{account, BenchmarkParameter, BenchmarkingSetup};
|
||||
use frame_support::{assert_err, assert_ok, ensure, traits::Get, StorageValue};
|
||||
use frame_system::RawOrigin;
|
||||
use super::{Test, pallet_test::{self, Value}, new_test_ext};
|
||||
use frame_support::{assert_ok, assert_err, ensure, traits::Get, StorageValue};
|
||||
use crate::{BenchmarkingSetup, BenchmarkParameter, account};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
// Additional used internally by the benchmark macro.
|
||||
use super::pallet_test::{Call, Config, Pallet};
|
||||
|
||||
crate::benchmarks!{
|
||||
crate::benchmarks! {
|
||||
where_clause {
|
||||
where
|
||||
<T as pallet_test::OtherConfig>::OtherEvent: Into<<T as pallet_test::Config>::Event> + Clone,
|
||||
@@ -204,7 +213,8 @@ mod benchmarks {
|
||||
&selected,
|
||||
&[(BenchmarkParameter::b, 1)],
|
||||
true,
|
||||
).expect("failed to create closure");
|
||||
)
|
||||
.expect("failed to create closure");
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(closure());
|
||||
@@ -222,7 +232,8 @@ mod benchmarks {
|
||||
&selected,
|
||||
&[(BenchmarkParameter::b, 1)],
|
||||
true,
|
||||
).expect("failed to create closure");
|
||||
)
|
||||
.expect("failed to create closure");
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(closure());
|
||||
@@ -240,7 +251,8 @@ mod benchmarks {
|
||||
&selected,
|
||||
&[(BenchmarkParameter::x, 1)],
|
||||
true,
|
||||
).expect("failed to create closure");
|
||||
)
|
||||
.expect("failed to create closure");
|
||||
|
||||
assert_ok!(closure());
|
||||
}
|
||||
@@ -254,7 +266,8 @@ mod benchmarks {
|
||||
&selected,
|
||||
&[(BenchmarkParameter::b, 1)],
|
||||
true,
|
||||
).expect("failed to create closure");
|
||||
)
|
||||
.expect("failed to create closure");
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(closure());
|
||||
@@ -267,7 +280,8 @@ mod benchmarks {
|
||||
&selected,
|
||||
&[(BenchmarkParameter::x, 10000)],
|
||||
true,
|
||||
).expect("failed to create closure");
|
||||
)
|
||||
.expect("failed to create closure");
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(closure(), "You forgot to sort!");
|
||||
|
||||
@@ -17,18 +17,43 @@
|
||||
|
||||
//! Interfaces, types and utils for benchmarking a FRAME runtime.
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use sp_std::{vec::Vec, prelude::Box};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_storage::TrackedStorageKey;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::traits::StorageInfo;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_std::{prelude::Box, vec::Vec};
|
||||
use sp_storage::TrackedStorageKey;
|
||||
|
||||
/// An alphabet of possible parameters to use for benchmarking.
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BenchmarkParameter {
|
||||
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l,
|
||||
m,
|
||||
n,
|
||||
o,
|
||||
p,
|
||||
q,
|
||||
r,
|
||||
s,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -105,7 +130,8 @@ pub trait Benchmarking {
|
||||
/// WARNING! This is a non-deterministic call. Do not use this within
|
||||
/// consensus critical logic.
|
||||
fn current_time() -> u128 {
|
||||
std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.expect("Unix time doesn't go backwards; qed")
|
||||
.as_nanos()
|
||||
}
|
||||
@@ -153,7 +179,7 @@ pub trait Benchmarking {
|
||||
// If the key does not exist, add it.
|
||||
None => {
|
||||
whitelist.push(add);
|
||||
}
|
||||
},
|
||||
}
|
||||
self.set_whitelist(whitelist);
|
||||
}
|
||||
@@ -217,12 +243,16 @@ pub trait BenchmarkingSetup<T, I = ()> {
|
||||
fn instance(
|
||||
&self,
|
||||
components: &[(BenchmarkParameter, u32)],
|
||||
verify: bool
|
||||
verify: bool,
|
||||
) -> Result<Box<dyn FnOnce() -> Result<(), &'static str>>, &'static str>;
|
||||
}
|
||||
|
||||
/// Grab an account, seeded by a name and index.
|
||||
pub fn account<AccountId: Decode + Default>(name: &'static str, index: u32, seed: u32) -> AccountId {
|
||||
pub fn account<AccountId: Decode + Default>(
|
||||
name: &'static str,
|
||||
index: u32,
|
||||
seed: u32,
|
||||
) -> AccountId {
|
||||
let entropy = (name, index, seed).using_encoded(blake2_256);
|
||||
AccountId::decode(&mut &entropy[..]).unwrap_or_default()
|
||||
}
|
||||
@@ -236,7 +266,7 @@ pub fn whitelisted_caller<AccountId: Decode + Default>() -> AccountId {
|
||||
macro_rules! whitelist_account {
|
||||
($acc:ident) => {
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(
|
||||
frame_system::Account::<T>::hashed_key_for(&$acc).into()
|
||||
frame_system::Account::<T>::hashed_key_for(&$acc).into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use sp_runtime::traits::Bounded;
|
||||
use frame_system::RawOrigin;
|
||||
use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite};
|
||||
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
|
||||
use frame_support::traits::OnInitialize;
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
|
||||
use crate::Module as Bounties;
|
||||
use pallet_treasury::Pallet as Treasury;
|
||||
@@ -33,7 +33,7 @@ const SEED: u32 = 0;
|
||||
|
||||
// Create bounties that are approved for use in `on_initialize`.
|
||||
fn create_approved_bounties<T: Config>(n: u32) -> Result<(), &'static str> {
|
||||
for i in 0 .. n {
|
||||
for i in 0..n {
|
||||
let (caller, _curator, _fee, value, reason) = setup_bounty::<T>(i, MAX_BYTES);
|
||||
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
|
||||
let bounty_id = BountyCount::get() - 1;
|
||||
@@ -44,13 +44,10 @@ fn create_approved_bounties<T: Config>(n: u32) -> Result<(), &'static str> {
|
||||
}
|
||||
|
||||
// Create the pre-requisite information needed to create a treasury `propose_bounty`.
|
||||
fn setup_bounty<T: Config>(u: u32, d: u32) -> (
|
||||
T::AccountId,
|
||||
T::AccountId,
|
||||
BalanceOf<T>,
|
||||
BalanceOf<T>,
|
||||
Vec<u8>,
|
||||
) {
|
||||
fn setup_bounty<T: Config>(
|
||||
u: u32,
|
||||
d: u32,
|
||||
) -> (T::AccountId, T::AccountId, BalanceOf<T>, BalanceOf<T>, Vec<u8>) {
|
||||
let caller = account("caller", u, SEED);
|
||||
let value: BalanceOf<T> = T::BountyValueMinimum::get().saturating_mul(100u32.into());
|
||||
let fee = value / 2u32.into();
|
||||
@@ -62,10 +59,8 @@ fn setup_bounty<T: Config>(u: u32, d: u32) -> (
|
||||
(caller, curator, fee, value, reason)
|
||||
}
|
||||
|
||||
fn create_bounty<T: Config>() -> Result<(
|
||||
<T::Lookup as StaticLookup>::Source,
|
||||
BountyIndex,
|
||||
), &'static str> {
|
||||
fn create_bounty<T: Config>(
|
||||
) -> Result<(<T::Lookup as StaticLookup>::Source, BountyIndex), &'static str> {
|
||||
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES);
|
||||
let curator_lookup = T::Lookup::unlookup(curator.clone());
|
||||
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
|
||||
@@ -216,8 +211,4 @@ benchmarks! {
|
||||
}
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Bounties,
|
||||
crate::tests::new_test_ext(),
|
||||
crate::tests::Test,
|
||||
);
|
||||
impl_benchmark_test_suite!(Bounties, crate::tests::new_test_ext(), crate::tests::Test,);
|
||||
|
||||
@@ -74,28 +74,28 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod tests;
|
||||
mod benchmarking;
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use frame_support::{decl_module, decl_storage, decl_event, ensure, decl_error};
|
||||
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
|
||||
|
||||
use frame_support::traits::{
|
||||
Currency, Get, Imbalance, OnUnbalanced, ExistenceRequirement::{AllowDeath},
|
||||
ReservableCurrency};
|
||||
Currency, ExistenceRequirement::AllowDeath, Get, Imbalance, OnUnbalanced, ReservableCurrency,
|
||||
};
|
||||
|
||||
use sp_runtime::{Permill, RuntimeDebug, DispatchResult, traits::{
|
||||
Zero, StaticLookup, AccountIdConversion, Saturating, BadOrigin
|
||||
}};
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, BadOrigin, Saturating, StaticLookup, Zero},
|
||||
DispatchResult, Permill, RuntimeDebug,
|
||||
};
|
||||
|
||||
use frame_support::dispatch::DispatchResultWithPostInfo;
|
||||
use frame_support::traits::{EnsureOrigin};
|
||||
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::EnsureOrigin};
|
||||
|
||||
use frame_support::weights::{Weight};
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_system::{self as system, ensure_signed};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
@@ -104,7 +104,6 @@ type BalanceOf<T> = pallet_treasury::BalanceOf<T>;
|
||||
type PositiveImbalanceOf<T> = pallet_treasury::PositiveImbalanceOf<T>;
|
||||
|
||||
pub trait Config: frame_system::Config + pallet_treasury::Config {
|
||||
|
||||
/// The amount held on deposit for placing a bounty proposal.
|
||||
type BountyDepositBase: Get<BalanceOf<Self>>;
|
||||
|
||||
@@ -692,14 +691,17 @@ impl<T: Config> Module<T> {
|
||||
description: Vec<u8>,
|
||||
value: BalanceOf<T>,
|
||||
) -> DispatchResult {
|
||||
ensure!(description.len() <= T::MaximumReasonLength::get() as usize, Error::<T>::ReasonTooBig);
|
||||
ensure!(
|
||||
description.len() <= T::MaximumReasonLength::get() as usize,
|
||||
Error::<T>::ReasonTooBig
|
||||
);
|
||||
ensure!(value >= T::BountyValueMinimum::get(), Error::<T>::InvalidValue);
|
||||
|
||||
let index = Self::bounty_count();
|
||||
|
||||
// reserve deposit for new bounty
|
||||
let bond = T::BountyDepositBase::get()
|
||||
+ T::DataDepositPerByte::get() * (description.len() as u32).into();
|
||||
let bond = T::BountyDepositBase::get() +
|
||||
T::DataDepositPerByte::get() * (description.len() as u32).into();
|
||||
T::Currency::reserve(&proposer, bond)
|
||||
.map_err(|_| Error::<T>::InsufficientProposersBalance)?;
|
||||
|
||||
@@ -721,7 +723,6 @@ impl<T: Config> Module<T> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<T: Config> pallet_treasury::SpendFunds<T> for Module<T> {
|
||||
@@ -729,7 +730,7 @@ impl<T: Config> pallet_treasury::SpendFunds<T> for Module<T> {
|
||||
budget_remaining: &mut BalanceOf<T>,
|
||||
imbalance: &mut PositiveImbalanceOf<T>,
|
||||
total_weight: &mut Weight,
|
||||
missed_any: &mut bool
|
||||
missed_any: &mut bool,
|
||||
) {
|
||||
let bounties_len = BountyApprovals::mutate(|v| {
|
||||
let bounties_approval_len = v.len() as u32;
|
||||
@@ -747,7 +748,10 @@ impl<T: Config> pallet_treasury::SpendFunds<T> for Module<T> {
|
||||
debug_assert!(err_amount.is_zero());
|
||||
|
||||
// fund the bounty account
|
||||
imbalance.subsume(T::Currency::deposit_creating(&Self::bounty_account_id(index), bounty.value));
|
||||
imbalance.subsume(T::Currency::deposit_creating(
|
||||
&Self::bounty_account_id(index),
|
||||
bounty.value,
|
||||
));
|
||||
|
||||
Self::deposit_event(RawEvent::BountyBecameActive(index));
|
||||
false
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pallet_bounties;
|
||||
use super::*;
|
||||
use crate as pallet_bounties;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, parameter_types, weights::Weight, traits::OnInitialize,
|
||||
PalletId, pallet_prelude::GenesisBuild,
|
||||
assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, traits::OnInitialize,
|
||||
weights::Weight, PalletId,
|
||||
};
|
||||
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
Perbill,
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup, BadOrigin},
|
||||
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
@@ -121,7 +121,7 @@ impl pallet_treasury::Config for Test {
|
||||
type ProposalBondMinimum = ProposalBondMinimum;
|
||||
type SpendPeriod = SpendPeriod;
|
||||
type Burn = Burn;
|
||||
type BurnDestination = (); // Just gets burned.
|
||||
type BurnDestination = (); // Just gets burned.
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = Bounties;
|
||||
type MaxApprovals = MaxApprovals;
|
||||
@@ -146,23 +146,25 @@ impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type TreasuryError = pallet_treasury::Error::<Test>;
|
||||
type TreasuryError = pallet_treasury::Error<Test>;
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test>{
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
// Total issuance will be 200 with treasury account initialized at ED.
|
||||
balances: vec![(0, 100), (1, 98), (2, 1)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
GenesisBuild::<Test>::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
fn last_event() -> RawEvent<u64, u128> {
|
||||
System::events().into_iter().map(|r| r.event)
|
||||
.filter_map(|e| {
|
||||
if let Event::Bounties(inner) = e { Some(inner) } else { None }
|
||||
})
|
||||
System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let Event::Bounties(inner) = e { Some(inner) } else { None })
|
||||
.last()
|
||||
.unwrap()
|
||||
}
|
||||
@@ -267,8 +269,10 @@ fn reject_already_rejected_spend_proposal_fails() {
|
||||
#[test]
|
||||
fn reject_non_existent_spend_proposal_fails() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Treasury::reject_proposal(Origin::root(), 0),
|
||||
pallet_treasury::Error::<Test, _>::InvalidIndex);
|
||||
assert_noop!(
|
||||
Treasury::reject_proposal(Origin::root(), 0),
|
||||
pallet_treasury::Error::<Test, _>::InvalidIndex
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,9 +357,9 @@ fn treasury_account_doesnt_get_deleted() {
|
||||
#[test]
|
||||
fn inexistent_account_works() {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test>{
|
||||
balances: vec![(0, 100), (1, 99), (2, 1)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> { balances: vec![(0, 100), (1, 99), (2, 1)] }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
// Treasury genesis config is not build thus treasury account does not exist
|
||||
let mut t: sp_io::TestExternalities = t.into();
|
||||
|
||||
@@ -398,14 +402,17 @@ fn propose_bounty_works() {
|
||||
assert_eq!(Balances::reserved_balance(0), deposit);
|
||||
assert_eq!(Balances::free_balance(0), 100 - deposit);
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 10,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Proposed,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 10,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Proposed,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Bounties::bounty_descriptions(0).unwrap(), b"1234567890".to_vec());
|
||||
|
||||
@@ -476,14 +483,17 @@ fn approve_bounty_works() {
|
||||
|
||||
let deposit: u64 = 80 + 5;
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
value: 50,
|
||||
curator_deposit: 0,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Approved,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
value: 50,
|
||||
curator_deposit: 0,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Approved,
|
||||
}
|
||||
);
|
||||
assert_eq!(Bounties::bounty_approvals(), vec![0]);
|
||||
|
||||
assert_noop!(Bounties::close_bounty(Origin::root(), 0), Error::<Test>::UnexpectedStatus);
|
||||
@@ -498,14 +508,17 @@ fn approve_bounty_works() {
|
||||
assert_eq!(Balances::reserved_balance(0), 0);
|
||||
assert_eq!(Balances::free_balance(0), 100);
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Funded,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: deposit,
|
||||
status: BountyStatus::Funded,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Treasury::pot(), 100 - 50 - 25); // burn 25
|
||||
assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50);
|
||||
@@ -518,7 +531,10 @@ fn assign_curator_works() {
|
||||
System::set_block_number(1);
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
|
||||
assert_noop!(Bounties::propose_curator(Origin::root(), 0, 4, 4), Error::<Test>::InvalidIndex);
|
||||
assert_noop!(
|
||||
Bounties::propose_curator(Origin::root(), 0, 4, 4),
|
||||
Error::<Test>::InvalidIndex
|
||||
);
|
||||
|
||||
assert_ok!(Bounties::propose_bounty(Origin::signed(0), 50, b"12345".to_vec()));
|
||||
|
||||
@@ -527,39 +543,46 @@ fn assign_curator_works() {
|
||||
System::set_block_number(2);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(2);
|
||||
|
||||
assert_noop!(Bounties::propose_curator(Origin::root(), 0, 4, 50), Error::<Test>::InvalidFee);
|
||||
assert_noop!(
|
||||
Bounties::propose_curator(Origin::root(), 0, 4, 50),
|
||||
Error::<Test>::InvalidFee
|
||||
);
|
||||
|
||||
assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::CuratorProposed {
|
||||
curator: 4,
|
||||
},
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::CuratorProposed { curator: 4 },
|
||||
}
|
||||
);
|
||||
|
||||
assert_noop!(Bounties::accept_curator(Origin::signed(1), 0), Error::<Test>::RequireCurator);
|
||||
assert_noop!(Bounties::accept_curator(Origin::signed(4), 0), pallet_balances::Error::<Test, _>::InsufficientBalance);
|
||||
assert_noop!(
|
||||
Bounties::accept_curator(Origin::signed(4), 0),
|
||||
pallet_balances::Error::<Test, _>::InsufficientBalance
|
||||
);
|
||||
|
||||
Balances::make_free_balance_be(&4, 10);
|
||||
|
||||
assert_ok!(Bounties::accept_curator(Origin::signed(4), 0));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 2,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active {
|
||||
curator: 4,
|
||||
update_due: 22,
|
||||
},
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 2,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active { curator: 4, update_due: 22 },
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(&4), 8);
|
||||
assert_eq!(Balances::reserved_balance(&4), 2);
|
||||
@@ -584,14 +607,17 @@ fn unassign_curator_works() {
|
||||
|
||||
assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
}
|
||||
);
|
||||
|
||||
assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4));
|
||||
|
||||
@@ -601,21 +627,23 @@ fn unassign_curator_works() {
|
||||
|
||||
assert_ok!(Bounties::unassign_curator(Origin::root(), 0));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(&4), 8);
|
||||
assert_eq!(Balances::reserved_balance(&4), 0); // slashed 2
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn award_and_claim_bounty_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -634,22 +662,24 @@ fn award_and_claim_bounty_works() {
|
||||
|
||||
assert_eq!(Balances::free_balance(4), 8); // inital 10 - 2 deposit
|
||||
|
||||
assert_noop!(Bounties::award_bounty(Origin::signed(1), 0, 3), Error::<Test>::RequireCurator);
|
||||
assert_noop!(
|
||||
Bounties::award_bounty(Origin::signed(1), 0, 3),
|
||||
Error::<Test>::RequireCurator
|
||||
);
|
||||
|
||||
assert_ok!(Bounties::award_bounty(Origin::signed(4), 0, 3));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 2,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::PendingPayout {
|
||||
curator: 4,
|
||||
beneficiary: 3,
|
||||
unlock_at: 5
|
||||
},
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 4,
|
||||
curator_deposit: 2,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::PendingPayout { curator: 4, beneficiary: 3, unlock_at: 5 },
|
||||
}
|
||||
);
|
||||
|
||||
assert_noop!(Bounties::claim_bounty(Origin::signed(1), 0), Error::<Test>::Premature);
|
||||
|
||||
@@ -713,7 +743,6 @@ fn claim_handles_high_fee() {
|
||||
#[test]
|
||||
fn cancel_and_refund() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
||||
System::set_block_number(1);
|
||||
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
@@ -727,14 +756,17 @@ fn cancel_and_refund() {
|
||||
|
||||
assert_ok!(Balances::transfer(Origin::signed(0), Bounties::bounty_account_id(0), 10));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 0,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 60);
|
||||
|
||||
@@ -743,9 +775,7 @@ fn cancel_and_refund() {
|
||||
assert_ok!(Bounties::close_bounty(Origin::root(), 0));
|
||||
|
||||
assert_eq!(Treasury::pot(), 85); // - 25 + 10
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -816,18 +846,20 @@ fn expire_and_unassign() {
|
||||
|
||||
assert_ok!(Bounties::unassign_curator(Origin::signed(0), 0));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 0,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Funded,
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(1), 93);
|
||||
assert_eq!(Balances::reserved_balance(1), 0); // slashed
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -841,7 +873,10 @@ fn extend_expiry() {
|
||||
|
||||
assert_ok!(Bounties::approve_bounty(Origin::root(), 0));
|
||||
|
||||
assert_noop!(Bounties::extend_bounty_expiry(Origin::signed(1), 0, Vec::new()), Error::<Test>::UnexpectedStatus);
|
||||
assert_noop!(
|
||||
Bounties::extend_bounty_expiry(Origin::signed(1), 0, Vec::new()),
|
||||
Error::<Test>::UnexpectedStatus
|
||||
);
|
||||
|
||||
System::set_block_number(2);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(2);
|
||||
@@ -855,28 +890,37 @@ fn extend_expiry() {
|
||||
System::set_block_number(10);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(10);
|
||||
|
||||
assert_noop!(Bounties::extend_bounty_expiry(Origin::signed(0), 0, Vec::new()), Error::<Test>::RequireCurator);
|
||||
assert_noop!(
|
||||
Bounties::extend_bounty_expiry(Origin::signed(0), 0, Vec::new()),
|
||||
Error::<Test>::RequireCurator
|
||||
);
|
||||
assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new()));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 5,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active { curator: 4, update_due: 30 },
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 5,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active { curator: 4, update_due: 30 },
|
||||
}
|
||||
);
|
||||
|
||||
assert_ok!(Bounties::extend_bounty_expiry(Origin::signed(4), 0, Vec::new()));
|
||||
|
||||
assert_eq!(Bounties::bounties(0).unwrap(), Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 5,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active { curator: 4, update_due: 30 }, // still the same
|
||||
});
|
||||
assert_eq!(
|
||||
Bounties::bounties(0).unwrap(),
|
||||
Bounty {
|
||||
proposer: 0,
|
||||
fee: 10,
|
||||
curator_deposit: 5,
|
||||
value: 50,
|
||||
bond: 85,
|
||||
status: BountyStatus::Active { curator: 4, update_due: 30 }, // still the same
|
||||
}
|
||||
);
|
||||
|
||||
System::set_block_number(25);
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(25);
|
||||
@@ -893,10 +937,12 @@ fn extend_expiry() {
|
||||
fn genesis_funding_works() {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
let initial_funding = 100;
|
||||
pallet_balances::GenesisConfig::<Test>{
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
// Total issuance will be 200 with treasury account initialized with 100.
|
||||
balances: vec![(0, 100), (Treasury::account_id(), initial_funding)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
GenesisBuild::<Test>::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap();
|
||||
let mut t: sp_io::TestExternalities = t.into();
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -19,19 +19,15 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_system::RawOrigin as SystemOrigin;
|
||||
use frame_benchmarking::{
|
||||
benchmarks_instance,
|
||||
account,
|
||||
whitelisted_caller,
|
||||
impl_benchmark_test_suite,
|
||||
account, benchmarks_instance, impl_benchmark_test_suite, whitelisted_caller,
|
||||
};
|
||||
use frame_system::RawOrigin as SystemOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_std::mem::size_of;
|
||||
|
||||
use frame_system::Call as SystemCall;
|
||||
use frame_system::Pallet as System;
|
||||
use crate::Module as Collective;
|
||||
use frame_system::{Call as SystemCall, Pallet as System};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
@@ -639,8 +635,4 @@ benchmarks_instance! {
|
||||
}
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Collective,
|
||||
crate::tests::new_test_ext(),
|
||||
crate::tests::Test,
|
||||
);
|
||||
impl_benchmark_test_suite!(Collective, crate::tests::new_test_ext(), crate::tests::Test,);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use sp_runtime::{DispatchError, RuntimeDebug};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Result type of a `bare_call` or `bare_instantiate` call.
|
||||
///
|
||||
@@ -163,7 +163,7 @@ pub enum Code<Hash> {
|
||||
#[cfg(feature = "std")]
|
||||
mod as_string {
|
||||
use super::*;
|
||||
use serde::{Serializer, Deserializer, ser::Error};
|
||||
use serde::{ser::Error, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
std::str::from_utf8(bytes)
|
||||
|
||||
@@ -21,11 +21,10 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::ToString;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident};
|
||||
use alloc::string::ToString;
|
||||
use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Ident};
|
||||
|
||||
/// This derives `Debug` for a struct where each field must be of some numeric type.
|
||||
/// It interprets each field as its represents some weight and formats it as times so that
|
||||
@@ -44,7 +43,7 @@ pub fn derive_schedule_debug(input: proc_macro::TokenStream) -> proc_macro::Toke
|
||||
|
||||
fn derive_debug(
|
||||
input: proc_macro::TokenStream,
|
||||
fmt: impl Fn(&Ident) -> TokenStream
|
||||
fmt: impl Fn(&Ident) -> TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
@@ -55,7 +54,8 @@ fn derive_debug(
|
||||
return quote_spanned! {
|
||||
name.span() =>
|
||||
compile_error!("WeightDebug is only supported for structs.");
|
||||
}.into();
|
||||
}
|
||||
.into()
|
||||
};
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
@@ -87,24 +87,22 @@ fn derive_debug(
|
||||
fn iterate_fields(data: &DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream {
|
||||
match &data.fields {
|
||||
Fields::Named(fields) => {
|
||||
let recurse = fields.named
|
||||
.iter()
|
||||
.filter_map(|f| {
|
||||
let recurse = fields.named.iter().filter_map(|f| {
|
||||
let name = f.ident.as_ref()?;
|
||||
if name.to_string().starts_with('_') {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
let value = fmt(name);
|
||||
let ret = quote_spanned!{ f.span() =>
|
||||
let ret = quote_spanned! { f.span() =>
|
||||
formatter.field(stringify!(#name), #value);
|
||||
};
|
||||
Some(ret)
|
||||
});
|
||||
quote!{
|
||||
quote! {
|
||||
#( #recurse )*
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => quote_spanned!{
|
||||
},
|
||||
Fields::Unnamed(fields) => quote_spanned! {
|
||||
fields.span() =>
|
||||
compile_error!("Unnamed fields are not supported")
|
||||
},
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Codec;
|
||||
use sp_std::vec::Vec;
|
||||
use pallet_contracts_primitives::{
|
||||
ContractExecResult, GetStorageResult, RentProjectionResult, Code, ContractInstantiateResult,
|
||||
Code, ContractExecResult, ContractInstantiateResult, GetStorageResult, RentProjectionResult,
|
||||
};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// The API to interact with contracts without using executive.
|
||||
|
||||
@@ -22,7 +22,9 @@ use std::sync::Arc;
|
||||
use codec::Codec;
|
||||
use jsonrpc_core::{Error, ErrorCode, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use pallet_contracts_primitives::RentProjection;
|
||||
use pallet_contracts_primitives::{
|
||||
Code, ContractExecResult, ContractInstantiateResult, RentProjection,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
@@ -33,7 +35,6 @@ use sp_runtime::{
|
||||
traits::{Block as BlockT, Header as HeaderT},
|
||||
};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult};
|
||||
|
||||
pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;
|
||||
|
||||
@@ -164,10 +165,7 @@ pub struct Contracts<C, B> {
|
||||
impl<C, B> Contracts<C, B> {
|
||||
/// Create new `Contracts` with the given reference to the client.
|
||||
pub fn new(client: Arc<C>) -> Self {
|
||||
Contracts {
|
||||
client,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
Contracts { client, _marker: Default::default() }
|
||||
}
|
||||
}
|
||||
impl<C, Block, AccountId, Balance, Hash>
|
||||
@@ -202,13 +200,7 @@ where
|
||||
// If the block hash is not supplied assume the best block.
|
||||
self.client.info().best_hash));
|
||||
|
||||
let CallRequest {
|
||||
origin,
|
||||
dest,
|
||||
value,
|
||||
gas_limit,
|
||||
input_data,
|
||||
} = call_request;
|
||||
let CallRequest { origin, dest, value, gas_limit, input_data } = call_request;
|
||||
|
||||
let value: Balance = decode_hex(value, "balance")?;
|
||||
let gas_limit: Weight = decode_hex(gas_limit, "weight")?;
|
||||
@@ -225,20 +217,15 @@ where
|
||||
&self,
|
||||
instantiate_request: InstantiateRequest<AccountId, Hash>,
|
||||
at: Option<<Block as BlockT>::Hash>,
|
||||
) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::Header as HeaderT>::Number>> {
|
||||
) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::Header as HeaderT>::Number>>
|
||||
{
|
||||
let api = self.client.runtime_api();
|
||||
let at = BlockId::hash(at.unwrap_or_else(||
|
||||
// If the block hash is not supplied assume the best block.
|
||||
self.client.info().best_hash));
|
||||
|
||||
let InstantiateRequest {
|
||||
origin,
|
||||
endowment,
|
||||
gas_limit,
|
||||
code,
|
||||
data,
|
||||
salt,
|
||||
} = instantiate_request;
|
||||
let InstantiateRequest { origin, endowment, gas_limit, code, data, salt } =
|
||||
instantiate_request;
|
||||
|
||||
let endowment: Balance = decode_hex(endowment, "balance")?;
|
||||
let gas_limit: Weight = decode_hex(gas_limit, "weight")?;
|
||||
@@ -337,7 +324,8 @@ mod tests {
|
||||
#[test]
|
||||
fn call_request_should_serialize_deserialize_properly() {
|
||||
type Req = CallRequest<String>;
|
||||
let req: Req = serde_json::from_str(r#"
|
||||
let req: Req = serde_json::from_str(
|
||||
r#"
|
||||
{
|
||||
"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
|
||||
"dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",
|
||||
@@ -345,7 +333,9 @@ mod tests {
|
||||
"gasLimit": 1000000000000,
|
||||
"inputData": "0x8c97db39"
|
||||
}
|
||||
"#).unwrap();
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));
|
||||
assert_eq!(req.value.into_u256(), U256::from(1234567890987654321u128));
|
||||
}
|
||||
@@ -353,7 +343,8 @@ mod tests {
|
||||
#[test]
|
||||
fn instantiate_request_should_serialize_deserialize_properly() {
|
||||
type Req = InstantiateRequest<String, String>;
|
||||
let req: Req = serde_json::from_str(r#"
|
||||
let req: Req = serde_json::from_str(
|
||||
r#"
|
||||
{
|
||||
"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
|
||||
"endowment": "0x88",
|
||||
@@ -362,7 +353,9 @@ mod tests {
|
||||
"data": "0x4299",
|
||||
"salt": "0x9988"
|
||||
}
|
||||
"#).unwrap();
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(req.origin, "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL");
|
||||
assert_eq!(req.endowment.into_u256(), 0x88.into());
|
||||
@@ -383,7 +376,8 @@ mod tests {
|
||||
let actual = serde_json::to_string(&res).unwrap();
|
||||
assert_eq!(actual, trim(expected).as_str());
|
||||
}
|
||||
test(r#"{
|
||||
test(
|
||||
r#"{
|
||||
"gasConsumed": 5000,
|
||||
"gasRequired": 8000,
|
||||
"debugMessage": "HelloWorld",
|
||||
@@ -393,25 +387,30 @@ mod tests {
|
||||
"data": "0x1234"
|
||||
}
|
||||
}
|
||||
}"#);
|
||||
test(r#"{
|
||||
}"#,
|
||||
);
|
||||
test(
|
||||
r#"{
|
||||
"gasConsumed": 3400,
|
||||
"gasRequired": 5200,
|
||||
"debugMessage": "HelloWorld",
|
||||
"result": {
|
||||
"Err": "BadOrigin"
|
||||
}
|
||||
}"#);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instantiate_result_should_serialize_deserialize_properly() {
|
||||
fn test(expected: &str) {
|
||||
let res: ContractInstantiateResult<String, u64> = serde_json::from_str(expected).unwrap();
|
||||
let res: ContractInstantiateResult<String, u64> =
|
||||
serde_json::from_str(expected).unwrap();
|
||||
let actual = serde_json::to_string(&res).unwrap();
|
||||
assert_eq!(actual, trim(expected).as_str());
|
||||
}
|
||||
test(r#"{
|
||||
test(
|
||||
r#"{
|
||||
"gasConsumed": 5000,
|
||||
"gasRequired": 8000,
|
||||
"debugMessage": "HelloWorld",
|
||||
@@ -425,14 +424,17 @@ mod tests {
|
||||
"rentProjection": null
|
||||
}
|
||||
}
|
||||
}"#);
|
||||
test(r#"{
|
||||
}"#,
|
||||
);
|
||||
test(
|
||||
r#"{
|
||||
"gasConsumed": 3400,
|
||||
"gasRequired": 5200,
|
||||
"debugMessage": "HelloWorld",
|
||||
"result": {
|
||||
"Err": "BadOrigin"
|
||||
}
|
||||
}"#);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +25,20 @@
|
||||
//! compiles it down into a `WasmModule` that can be used as a contract's code.
|
||||
|
||||
use crate::Config;
|
||||
use frame_support::traits::Get;
|
||||
use pwasm_utils::{
|
||||
stack_height::inject_limiter,
|
||||
parity_wasm::{
|
||||
elements::{
|
||||
self, Instruction, Instructions, FuncBody, ValueType, BlockType, Section,
|
||||
CustomSection,
|
||||
},
|
||||
builder,
|
||||
elements::{
|
||||
self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Section, ValueType,
|
||||
},
|
||||
},
|
||||
stack_height::inject_limiter,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};
|
||||
use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};
|
||||
use frame_support::traits::Get;
|
||||
use sp_std::{borrow::ToOwned, convert::TryFrom, prelude::*};
|
||||
|
||||
/// Pass to `create_code` in order to create a compiled `WasmModule`.
|
||||
///
|
||||
@@ -117,7 +116,7 @@ pub struct ImportedFunction {
|
||||
|
||||
/// A wasm module ready to be put on chain.
|
||||
#[derive(Clone)]
|
||||
pub struct WasmModule<T:Config> {
|
||||
pub struct WasmModule<T: Config> {
|
||||
pub code: Vec<u8>,
|
||||
pub hash: <T::Hashing as Hash>::Output,
|
||||
memory: Option<ImportedMemory>,
|
||||
@@ -136,27 +135,37 @@ where
|
||||
let mut contract = builder::module()
|
||||
// deploy function (first internal function)
|
||||
.function()
|
||||
.signature().build()
|
||||
.with_body(def.deploy_body.unwrap_or_else(||
|
||||
FuncBody::new(Vec::new(), Instructions::empty())
|
||||
))
|
||||
.build()
|
||||
.signature()
|
||||
.build()
|
||||
.with_body(
|
||||
def.deploy_body
|
||||
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
|
||||
)
|
||||
.build()
|
||||
// call function (second internal function)
|
||||
.function()
|
||||
.signature().build()
|
||||
.with_body(def.call_body.unwrap_or_else(||
|
||||
FuncBody::new(Vec::new(), Instructions::empty())
|
||||
))
|
||||
.build()
|
||||
.export().field("deploy").internal().func(func_offset).build()
|
||||
.export().field("call").internal().func(func_offset + 1).build();
|
||||
.signature()
|
||||
.build()
|
||||
.with_body(
|
||||
def.call_body
|
||||
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
|
||||
)
|
||||
.build()
|
||||
.export()
|
||||
.field("deploy")
|
||||
.internal()
|
||||
.func(func_offset)
|
||||
.build()
|
||||
.export()
|
||||
.field("call")
|
||||
.internal()
|
||||
.func(func_offset + 1)
|
||||
.build();
|
||||
|
||||
// If specified we add an additional internal function
|
||||
if let Some(body) = def.aux_body {
|
||||
let mut signature = contract
|
||||
.function()
|
||||
.signature();
|
||||
for _ in 0 .. def.aux_arg_num {
|
||||
let mut signature = contract.function().signature();
|
||||
for _ in 0..def.aux_arg_num {
|
||||
signature = signature.with_param(ValueType::I64);
|
||||
}
|
||||
contract = signature.build().with_body(body).build();
|
||||
@@ -164,9 +173,12 @@ where
|
||||
|
||||
// Grant access to linear memory.
|
||||
if let Some(memory) = &def.memory {
|
||||
contract = contract.import()
|
||||
.module("env").field("memory")
|
||||
.external().memory(memory.min_pages, Some(memory.max_pages))
|
||||
contract = contract
|
||||
.import()
|
||||
.module("env")
|
||||
.field("memory")
|
||||
.external()
|
||||
.memory(memory.min_pages, Some(memory.max_pages))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -177,7 +189,8 @@ where
|
||||
.with_results(func.return_type.into_iter().collect())
|
||||
.build_sig();
|
||||
let sig = contract.push_signature(sig);
|
||||
contract = contract.import()
|
||||
contract = contract
|
||||
.import()
|
||||
.module(func.module)
|
||||
.field(func.name)
|
||||
.with_external(elements::External::Function(sig))
|
||||
@@ -186,7 +199,8 @@ where
|
||||
|
||||
// Initialize memory
|
||||
for data in def.data_segments {
|
||||
contract = contract.data()
|
||||
contract = contract
|
||||
.data()
|
||||
.offset(Instruction::I32Const(data.offset as i32))
|
||||
.value(data.value)
|
||||
.build()
|
||||
@@ -194,12 +208,13 @@ where
|
||||
|
||||
// Add global variables
|
||||
if def.num_globals > 0 {
|
||||
use rand::{prelude::*, distributions::Standard};
|
||||
use rand::{distributions::Standard, prelude::*};
|
||||
let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
|
||||
for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
|
||||
contract = contract
|
||||
.global()
|
||||
.value_type().i64()
|
||||
.value_type()
|
||||
.i64()
|
||||
.mutable()
|
||||
.init_expr(Instruction::I64Const(val))
|
||||
.build()
|
||||
@@ -218,31 +233,22 @@ where
|
||||
|
||||
// Add the dummy section
|
||||
if def.dummy_section > 0 {
|
||||
contract = contract.with_section(
|
||||
Section::Custom(
|
||||
CustomSection::new("dummy".to_owned(), vec![42; def.dummy_section as usize])
|
||||
)
|
||||
);
|
||||
contract = contract.with_section(Section::Custom(CustomSection::new(
|
||||
"dummy".to_owned(),
|
||||
vec![42; def.dummy_section as usize],
|
||||
)));
|
||||
}
|
||||
|
||||
let mut code = contract.build();
|
||||
|
||||
// Inject stack height metering
|
||||
if def.inject_stack_metering {
|
||||
code = inject_limiter(
|
||||
code,
|
||||
T::Schedule::get().limits.stack_height
|
||||
)
|
||||
.unwrap();
|
||||
code = inject_limiter(code, T::Schedule::get().limits.stack_height).unwrap();
|
||||
}
|
||||
|
||||
let code = code.to_bytes().unwrap();
|
||||
let hash = T::Hashing::hash(&code);
|
||||
Self {
|
||||
code,
|
||||
hash,
|
||||
memory: def.memory,
|
||||
}
|
||||
Self { code, hash, memory: def.memory }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +272,7 @@ where
|
||||
ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
dummy_section: dummy_bytes.saturating_sub(module_overhead),
|
||||
.. Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -275,23 +281,18 @@ where
|
||||
/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
|
||||
/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
|
||||
pub fn sized(target_bytes: u32) -> Self {
|
||||
use self::elements::Instruction::{If, I32Const, Return, End};
|
||||
use self::elements::Instruction::{End, I32Const, If, Return};
|
||||
// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
|
||||
// We do one expansion less to account for the code section and function body
|
||||
// size fields inside the binary wasm module representation which are leb128 encoded
|
||||
// and therefore grow in size when the contract grows. We are not allowed to overshoot
|
||||
// because of the maximum code size that is enforced by `instantiate_with_code`.
|
||||
let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
|
||||
const EXPANSION: [Instruction; 4] = [
|
||||
I32Const(0),
|
||||
If(BlockType::NoResult),
|
||||
Return,
|
||||
End,
|
||||
];
|
||||
const EXPANSION: [Instruction; 4] = [I32Const(0), If(BlockType::NoResult), Return, End];
|
||||
ModuleDefinition {
|
||||
call_body: Some(body::repeated(expansions, &EXPANSION)),
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
.. Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -317,12 +318,15 @@ where
|
||||
offset: 0,
|
||||
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
|
||||
}],
|
||||
call_body: Some(body::repeated(repeat, &[
|
||||
Instruction::I32Const(4), // ptr where to store output
|
||||
Instruction::I32Const(0), // ptr to length
|
||||
Instruction::Call(0), // call the imported function
|
||||
])),
|
||||
.. Default::default()
|
||||
call_body: Some(body::repeated(
|
||||
repeat,
|
||||
&[
|
||||
Instruction::I32Const(4), // ptr where to store output
|
||||
Instruction::I32Const(0), // ptr to length
|
||||
Instruction::Call(0), // call the imported function
|
||||
],
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -339,13 +343,16 @@ where
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
}],
|
||||
call_body: Some(body::repeated(repeat, &[
|
||||
Instruction::I32Const(0), // input_ptr
|
||||
Instruction::I32Const(data_size as i32), // input_len
|
||||
Instruction::I32Const(0), // output_ptr
|
||||
Instruction::Call(0),
|
||||
])),
|
||||
.. Default::default()
|
||||
call_body: Some(body::repeated(
|
||||
repeat,
|
||||
&[
|
||||
Instruction::I32Const(0), // input_ptr
|
||||
Instruction::I32Const(data_size as i32), // input_len
|
||||
Instruction::I32Const(0), // output_ptr
|
||||
Instruction::Call(0),
|
||||
],
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -354,11 +361,7 @@ where
|
||||
/// and adds it to `env`. A reference to that memory is returned so that it can be used to
|
||||
/// access the memory contents from the supervisor.
|
||||
pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {
|
||||
let memory = if let Some(memory) = &self.memory {
|
||||
memory
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let memory = if let Some(memory) = &self.memory { memory } else { return None };
|
||||
let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();
|
||||
env.add_memory("env", "memory", memory.clone());
|
||||
Some(memory)
|
||||
@@ -367,25 +370,25 @@ where
|
||||
pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
|
||||
use body::DynInstr::{RandomI64Repeated, Regular};
|
||||
ModuleDefinition {
|
||||
call_body: Some(body::repeated_dyn(repeat, vec![
|
||||
RandomI64Repeated(1),
|
||||
Regular(instr),
|
||||
Regular(Instruction::Drop),
|
||||
])),
|
||||
.. Default::default()
|
||||
}.into()
|
||||
call_body: Some(body::repeated_dyn(
|
||||
repeat,
|
||||
vec![RandomI64Repeated(1), Regular(instr), Regular(Instruction::Drop)],
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {
|
||||
use body::DynInstr::{RandomI64Repeated, Regular};
|
||||
ModuleDefinition {
|
||||
call_body: Some(body::repeated_dyn(repeat, vec![
|
||||
RandomI64Repeated(2),
|
||||
Regular(instr),
|
||||
Regular(Instruction::Drop),
|
||||
])),
|
||||
.. Default::default()
|
||||
}.into()
|
||||
call_body: Some(body::repeated_dyn(
|
||||
repeat,
|
||||
vec![RandomI64Repeated(2), Regular(instr), Regular(Instruction::Drop)],
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,7 +429,7 @@ pub mod body {
|
||||
RandomGetGlobal(u32, u32),
|
||||
/// Insert a SetGlobal with a random offset in [low, high).
|
||||
/// (low, high)
|
||||
RandomSetGlobal(u32, u32)
|
||||
RandomSetGlobal(u32, u32),
|
||||
}
|
||||
|
||||
pub fn plain(instructions: Vec<Instruction>) -> FuncBody {
|
||||
@@ -441,13 +444,13 @@ pub mod body {
|
||||
.take(instructions.len() * usize::try_from(repetitions).unwrap())
|
||||
.cloned()
|
||||
.chain(sp_std::iter::once(Instruction::End))
|
||||
.collect()
|
||||
.collect(),
|
||||
);
|
||||
FuncBody::new(Vec::new(), instructions)
|
||||
}
|
||||
|
||||
pub fn repeated_dyn(repetitions: u32, mut instructions: Vec<DynInstr>) -> FuncBody {
|
||||
use rand::{prelude::*, distributions::Standard};
|
||||
use rand::{distributions::Standard, prelude::*};
|
||||
|
||||
// We do not need to be secure here.
|
||||
let mut rng = rand_pcg::Pcg32::seed_from_u64(8446744073709551615);
|
||||
@@ -456,50 +459,46 @@ pub mod body {
|
||||
let body = (0..instructions.len())
|
||||
.cycle()
|
||||
.take(instructions.len() * usize::try_from(repetitions).unwrap())
|
||||
.flat_map(|idx|
|
||||
match &mut instructions[idx] {
|
||||
DynInstr::Regular(instruction) => vec![instruction.clone()],
|
||||
DynInstr::Counter(offset, increment_by) => {
|
||||
let current = *offset;
|
||||
*offset += *increment_by;
|
||||
vec![Instruction::I32Const(current as i32)]
|
||||
},
|
||||
DynInstr::RandomUnaligned(low, high) => {
|
||||
let unaligned = rng.gen_range(*low..*high) | 1;
|
||||
vec![Instruction::I32Const(unaligned as i32)]
|
||||
},
|
||||
DynInstr::RandomI32(low, high) => {
|
||||
vec![Instruction::I32Const(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomI32Repeated(num) => {
|
||||
(&mut rng).sample_iter(Standard).take(*num).map(|val|
|
||||
Instruction::I32Const(val)
|
||||
)
|
||||
.collect()
|
||||
},
|
||||
DynInstr::RandomI64Repeated(num) => {
|
||||
(&mut rng).sample_iter(Standard).take(*num).map(|val|
|
||||
Instruction::I64Const(val)
|
||||
)
|
||||
.collect()
|
||||
},
|
||||
DynInstr::RandomGetLocal(low, high) => {
|
||||
vec![Instruction::GetLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomSetLocal(low, high) => {
|
||||
vec![Instruction::SetLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomTeeLocal(low, high) => {
|
||||
vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomGetGlobal(low, high) => {
|
||||
vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomSetGlobal(low, high) => {
|
||||
vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
}
|
||||
)
|
||||
.flat_map(|idx| match &mut instructions[idx] {
|
||||
DynInstr::Regular(instruction) => vec![instruction.clone()],
|
||||
DynInstr::Counter(offset, increment_by) => {
|
||||
let current = *offset;
|
||||
*offset += *increment_by;
|
||||
vec![Instruction::I32Const(current as i32)]
|
||||
},
|
||||
DynInstr::RandomUnaligned(low, high) => {
|
||||
let unaligned = rng.gen_range(*low..*high) | 1;
|
||||
vec![Instruction::I32Const(unaligned as i32)]
|
||||
},
|
||||
DynInstr::RandomI32(low, high) => {
|
||||
vec![Instruction::I32Const(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomI32Repeated(num) => (&mut rng)
|
||||
.sample_iter(Standard)
|
||||
.take(*num)
|
||||
.map(|val| Instruction::I32Const(val))
|
||||
.collect(),
|
||||
DynInstr::RandomI64Repeated(num) => (&mut rng)
|
||||
.sample_iter(Standard)
|
||||
.take(*num)
|
||||
.map(|val| Instruction::I64Const(val))
|
||||
.collect(),
|
||||
DynInstr::RandomGetLocal(low, high) => {
|
||||
vec![Instruction::GetLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomSetLocal(low, high) => {
|
||||
vec![Instruction::SetLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomTeeLocal(low, high) => {
|
||||
vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomGetGlobal(low, high) => {
|
||||
vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
DynInstr::RandomSetGlobal(low, high) => {
|
||||
vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]
|
||||
},
|
||||
})
|
||||
.chain(sp_std::iter::once(Instruction::End))
|
||||
.collect();
|
||||
FuncBody::new(Vec::new(), Instructions::new(body))
|
||||
|
||||
@@ -22,28 +22,28 @@
|
||||
mod code;
|
||||
mod sandbox;
|
||||
|
||||
use self::{
|
||||
code::{
|
||||
body::{self, DynInstr::*},
|
||||
DataSegment, ImportedFunction, ImportedMemory, ModuleDefinition, WasmModule,
|
||||
},
|
||||
sandbox::Sandbox,
|
||||
};
|
||||
use crate::{
|
||||
*, Pallet as Contracts,
|
||||
exec::StorageKey,
|
||||
rent::Rent,
|
||||
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
|
||||
storage::Storage,
|
||||
};
|
||||
use self::{
|
||||
code::{
|
||||
body::{self, DynInstr::*},
|
||||
ModuleDefinition, DataSegment, ImportedMemory, ImportedFunction, WasmModule,
|
||||
},
|
||||
sandbox::Sandbox,
|
||||
Pallet as Contracts, *,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite};
|
||||
use frame_system::{Pallet as System, RawOrigin};
|
||||
use pwasm_utils::parity_wasm::elements::{Instruction, ValueType, BlockType, BrTableData};
|
||||
use sp_runtime::traits::{Hash, Bounded, Zero};
|
||||
use sp_std::{default::Default, convert::{TryInto}, vec::Vec, vec};
|
||||
use pallet_contracts_primitives::RentProjection;
|
||||
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
|
||||
use frame_support::weights::Weight;
|
||||
use frame_system::{Pallet as System, RawOrigin};
|
||||
use pallet_contracts_primitives::RentProjection;
|
||||
use pwasm_utils::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType};
|
||||
use sp_runtime::traits::{Bounded, Hash, Zero};
|
||||
use sp_std::{convert::TryInto, default::Default, vec, vec::Vec};
|
||||
|
||||
/// How many batches we do per API benchmark.
|
||||
const API_BENCHMARK_BATCHES: u32 = 20;
|
||||
@@ -74,7 +74,7 @@ impl Endow {
|
||||
/// The maximum amount of balance a caller can transfer without being brought below
|
||||
/// the existential deposit. This assumes that every caller is funded with the amount
|
||||
/// returned by `caller_funding`.
|
||||
fn max<T:Config>() -> BalanceOf<T> {
|
||||
fn max<T: Config>() -> BalanceOf<T> {
|
||||
caller_funding::<T>().saturating_sub(T::Currency::minimum_balance())
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,7 @@ where
|
||||
module: WasmModule<T>,
|
||||
data: Vec<u8>,
|
||||
endowment: Endow,
|
||||
) -> Result<Contract<T>, &'static str>
|
||||
{
|
||||
) -> Result<Contract<T>, &'static str> {
|
||||
let (storage_size, endowment) = match endowment {
|
||||
Endow::CollectRent => {
|
||||
// storage_size cannot be zero because otherwise a contract that is just above
|
||||
@@ -182,7 +181,8 @@ where
|
||||
|
||||
/// Get the `AliveContractInfo` of the `addr` or an error if it is no longer alive.
|
||||
fn address_alive_info(addr: &T::AccountId) -> Result<AliveContractInfo<T>, &'static str> {
|
||||
ContractInfoOf::<T>::get(addr).and_then(|c| c.get_alive())
|
||||
ContractInfoOf::<T>::get(addr)
|
||||
.and_then(|c| c.get_alive())
|
||||
.ok_or("Expected contract to be alive at this point.")
|
||||
}
|
||||
|
||||
@@ -193,7 +193,8 @@ where
|
||||
|
||||
/// Return an error if this contract is no tombstone.
|
||||
fn ensure_tombstone(&self) -> Result<(), &'static str> {
|
||||
ContractInfoOf::<T>::get(&self.account_id).and_then(|c| c.get_tombstone())
|
||||
ContractInfoOf::<T>::get(&self.account_id)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or("Expected contract to be a tombstone at this point.")
|
||||
.map(|_| ())
|
||||
}
|
||||
@@ -236,16 +237,13 @@ where
|
||||
let contract = Contract::<T>::new(code, vec![], Endow::CollectRent)?;
|
||||
let storage_items = create_storage::<T>(stor_num, stor_size)?;
|
||||
contract.store(&storage_items)?;
|
||||
Ok(Self {
|
||||
contract,
|
||||
storage: storage_items,
|
||||
})
|
||||
Ok(Self { contract, storage: storage_items })
|
||||
}
|
||||
|
||||
/// Increase the system block number so that this contract is eligible for eviction.
|
||||
fn set_block_num_for_eviction(&self) -> Result<(), &'static str> {
|
||||
fn set_block_num_for_eviction(&self) -> Result<(), &'static str> {
|
||||
System::<T>::set_block_number(
|
||||
self.contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into()
|
||||
self.contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -261,15 +259,17 @@ where
|
||||
/// Generate `stor_num` storage items. Each has the size `stor_size`.
|
||||
fn create_storage<T: Config>(
|
||||
stor_num: u32,
|
||||
stor_size: u32
|
||||
stor_size: u32,
|
||||
) -> Result<Vec<(StorageKey, Vec<u8>)>, &'static str> {
|
||||
(0..stor_num).map(|i| {
|
||||
let hash = T::Hashing::hash_of(&i)
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| "Hash too big for storage key")?;
|
||||
Ok((hash, vec![42u8; stor_size as usize]))
|
||||
}).collect::<Result<Vec<_>, &'static str>>()
|
||||
(0..stor_num)
|
||||
.map(|i| {
|
||||
let hash = T::Hashing::hash_of(&i)
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| "Hash too big for storage key")?;
|
||||
Ok((hash, vec![42u8; stor_size as usize]))
|
||||
})
|
||||
.collect::<Result<Vec<_>, &'static str>>()
|
||||
}
|
||||
|
||||
/// The funding that each account that either calls or instantiates contracts is funded with.
|
||||
|
||||
@@ -15,14 +15,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
///! For instruction benchmarking we do no instantiate a full contract but merely the
|
||||
///! sandbox to execute the wasm code. This is because we do not need the full
|
||||
///! environment that provides the seal interface as imported functions.
|
||||
|
||||
use super::{
|
||||
Config,
|
||||
code::WasmModule,
|
||||
};
|
||||
/// ! For instruction benchmarking we do no instantiate a full contract but merely the
|
||||
/// ! sandbox to execute the wasm code. This is because we do not need the full
|
||||
/// ! environment that provides the seal interface as imported functions.
|
||||
use super::{code::WasmModule, Config};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_sandbox::{EnvironmentDefinitionBuilder, Instance, Memory};
|
||||
|
||||
@@ -51,9 +47,6 @@ where
|
||||
let memory = module.add_memory(&mut env_builder);
|
||||
let instance = Instance::new(&module.code, &env_builder, &mut ())
|
||||
.expect("Failed to create benchmarking Sandbox instance");
|
||||
Self {
|
||||
instance,
|
||||
_memory: memory,
|
||||
}
|
||||
Self { instance, _memory: memory }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,22 +55,19 @@
|
||||
//! on how to use a chain extension in order to provide new features to ink! contracts.
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
wasm::{Runtime, RuntimeCosts},
|
||||
gas::ChargedAmount,
|
||||
wasm::{Runtime, RuntimeCosts},
|
||||
Error,
|
||||
};
|
||||
use codec::{Decode, MaxEncodedLen};
|
||||
use frame_support::weights::Weight;
|
||||
use sp_runtime::DispatchError;
|
||||
use sp_std::{
|
||||
marker::PhantomData,
|
||||
vec::Vec,
|
||||
};
|
||||
use sp_std::{marker::PhantomData, vec::Vec};
|
||||
|
||||
pub use crate::{exec::Ext, Config};
|
||||
pub use frame_system::Config as SysConfig;
|
||||
pub use pallet_contracts_primitives::ReturnFlags;
|
||||
pub use sp_core::crypto::UncheckedFrom;
|
||||
pub use crate::{Config, exec::Ext};
|
||||
pub use state::Init as InitState;
|
||||
|
||||
/// Result that returns a [`DispatchError`] on error.
|
||||
@@ -90,7 +87,7 @@ pub trait ChainExtension<C: Config> {
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `func_id`: The first argument to `seal_call_chain_extension`. Usually used to
|
||||
/// determine which function to realize.
|
||||
/// determine which function to realize.
|
||||
/// - `env`: Access to the remaining arguments and the execution environment.
|
||||
///
|
||||
/// # Return
|
||||
@@ -143,7 +140,7 @@ pub enum RetVal {
|
||||
/// The semantic is the same as for calling `seal_return`: The control returns to
|
||||
/// the caller of the currently executing contract yielding the supplied buffer and
|
||||
/// flags.
|
||||
Diverging{flags: ReturnFlags, data: Vec<u8>},
|
||||
Diverging { flags: ReturnFlags, data: Vec<u8> },
|
||||
}
|
||||
|
||||
/// Grants the chain extension access to its parameters and execution environment.
|
||||
@@ -183,7 +180,9 @@ where
|
||||
/// This is when a maximum a priori amount was charged and then should be partially
|
||||
/// refunded to match the actual amount.
|
||||
pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) {
|
||||
self.inner.runtime.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight))
|
||||
self.inner
|
||||
.runtime
|
||||
.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight))
|
||||
}
|
||||
|
||||
/// Grants access to the execution environment of the current contract call.
|
||||
@@ -204,46 +203,31 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, state::Init> {
|
||||
/// It is only available to this crate because only the wasm runtime module needs to
|
||||
/// ever create this type. Chain extensions merely consume it.
|
||||
pub(crate) fn new(
|
||||
runtime: &'a mut Runtime::<'b, E>,
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
input_ptr: u32,
|
||||
input_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32,
|
||||
) -> Self {
|
||||
Environment {
|
||||
inner: Inner {
|
||||
runtime,
|
||||
input_ptr,
|
||||
input_len,
|
||||
output_ptr,
|
||||
output_len_ptr,
|
||||
},
|
||||
inner: Inner { runtime, input_ptr, input_len, output_ptr, output_len_ptr },
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use all arguments as integer values.
|
||||
pub fn only_in(self) -> Environment<'a, 'b, E, state::OnlyIn> {
|
||||
Environment {
|
||||
inner: self.inner,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Use input arguments as integer and output arguments as pointer to a buffer.
|
||||
pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, state::PrimInBufOut> {
|
||||
Environment {
|
||||
inner: self.inner,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Use input and output arguments as pointers to a buffer.
|
||||
pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, state::BufInBufOut> {
|
||||
Environment {
|
||||
inner: self.inner,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
Environment { inner: self.inner, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,10 +271,9 @@ where
|
||||
/// charge the overall costs either using `max_len` (worst case approximation) or using
|
||||
/// [`in_len()`](Self::in_len).
|
||||
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
|
||||
self.inner.runtime.read_sandbox_memory(
|
||||
self.inner.input_ptr,
|
||||
self.inner.input_len.min(max_len),
|
||||
)
|
||||
self.inner
|
||||
.runtime
|
||||
.read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len))
|
||||
}
|
||||
|
||||
/// Reads `min(buffer.len(), in_len) from contract memory.
|
||||
@@ -304,10 +287,7 @@ where
|
||||
let buffer = core::mem::take(buffer);
|
||||
&mut buffer[..len.min(self.inner.input_len as usize)]
|
||||
};
|
||||
self.inner.runtime.read_sandbox_memory_into_buf(
|
||||
self.inner.input_ptr,
|
||||
sliced,
|
||||
)?;
|
||||
self.inner.runtime.read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?;
|
||||
*buffer = sliced;
|
||||
Ok(())
|
||||
}
|
||||
@@ -377,7 +357,7 @@ where
|
||||
/// gets too large.
|
||||
struct Inner<'a, 'b, E: Ext> {
|
||||
/// The runtime contains all necessary functions to interact with the running contract.
|
||||
runtime: &'a mut Runtime::<'b, E>,
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
input_ptr: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,17 +15,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{Config, Error, exec::ExecError};
|
||||
use sp_std::marker::PhantomData;
|
||||
use sp_runtime::traits::Zero;
|
||||
use crate::{exec::ExecError, Config, Error};
|
||||
use frame_support::{
|
||||
dispatch::{
|
||||
DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo, DispatchError,
|
||||
DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo,
|
||||
},
|
||||
weights::Weight,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::traits::Zero;
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::{any::Any, fmt::Debug};
|
||||
@@ -88,7 +88,7 @@ pub struct GasMeter<T: Config> {
|
||||
|
||||
impl<T: Config> GasMeter<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(gas_limit: Weight) -> Self {
|
||||
GasMeter {
|
||||
@@ -107,11 +107,7 @@ where
|
||||
///
|
||||
/// Passing `0` as amount is interpreted as "all remaining gas".
|
||||
pub fn nested(&mut self, amount: Weight) -> Result<Self, DispatchError> {
|
||||
let amount = if amount == 0 {
|
||||
self.gas_left
|
||||
} else {
|
||||
amount
|
||||
};
|
||||
let amount = if amount == 0 { self.gas_left } else { amount };
|
||||
|
||||
// NOTE that it is ok to allocate all available gas since it still ensured
|
||||
// by `charge` that it doesn't reach zero.
|
||||
@@ -155,10 +151,8 @@ where
|
||||
#[cfg(test)]
|
||||
{
|
||||
// Unconditionally add the token to the storage.
|
||||
let erased_tok = ErasedToken {
|
||||
description: format!("{:?}", token),
|
||||
token: Box::new(token),
|
||||
};
|
||||
let erased_tok =
|
||||
ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
|
||||
self.tokens.push(erased_tok);
|
||||
}
|
||||
|
||||
@@ -277,7 +271,9 @@ mod tests {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct SimpleToken(u64);
|
||||
impl Token<Test> for SimpleToken {
|
||||
fn weight(&self) -> u64 { self.0 }
|
||||
fn weight(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -318,7 +314,6 @@ mod tests {
|
||||
assert!(gas_meter.charge(SimpleToken(1)).is_err());
|
||||
}
|
||||
|
||||
|
||||
// Charging the exact amount that the user paid for should be
|
||||
// possible.
|
||||
#[test]
|
||||
|
||||
@@ -78,17 +78,17 @@
|
||||
//! WebAssembly based smart contracts in the Rust programming language. This is a work in progress.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="512")]
|
||||
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "512")]
|
||||
|
||||
#[macro_use]
|
||||
mod gas;
|
||||
mod storage;
|
||||
mod exec;
|
||||
mod wasm;
|
||||
mod rent;
|
||||
mod benchmarking;
|
||||
mod schedule;
|
||||
mod exec;
|
||||
mod migration;
|
||||
mod rent;
|
||||
mod schedule;
|
||||
mod storage;
|
||||
mod wasm;
|
||||
|
||||
pub mod chain_extension;
|
||||
pub mod weights;
|
||||
@@ -97,49 +97,48 @@ pub mod weights;
|
||||
mod tests;
|
||||
|
||||
pub use crate::{
|
||||
pallet::*,
|
||||
schedule::{Schedule, Limits, InstructionWeights, HostFnWeights},
|
||||
exec::Frame,
|
||||
pallet::*,
|
||||
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
|
||||
};
|
||||
use crate::{
|
||||
exec::{Executable, Stack as ExecStack},
|
||||
gas::GasMeter,
|
||||
exec::{Stack as ExecStack, Executable},
|
||||
rent::Rent,
|
||||
storage::{Storage, DeletedContract, ContractInfo, AliveContractInfo, TombstoneContractInfo},
|
||||
weights::WeightInfo,
|
||||
storage::{AliveContractInfo, ContractInfo, DeletedContract, Storage, TombstoneContractInfo},
|
||||
wasm::PrefabWasmModule,
|
||||
};
|
||||
use sp_core::{Bytes, crypto::UncheckedFrom};
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::{
|
||||
traits::{
|
||||
Hash, StaticLookup, Convert, Saturating, Zero,
|
||||
},
|
||||
Perbill,
|
||||
weights::WeightInfo,
|
||||
};
|
||||
use frame_support::{
|
||||
traits::{OnUnbalanced, Currency, Get, Time, Randomness, Filter},
|
||||
weights::{Weight, PostDispatchInfo, WithPostDispatchInfo, GetDispatchInfo},
|
||||
dispatch::Dispatchable,
|
||||
traits::{Currency, Filter, Get, OnUnbalanced, Randomness, Time},
|
||||
weights::{GetDispatchInfo, PostDispatchInfo, Weight, WithPostDispatchInfo},
|
||||
};
|
||||
use frame_system::Pallet as System;
|
||||
use pallet_contracts_primitives::{
|
||||
RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult,
|
||||
ContractInstantiateResult, Code, InstantiateReturnValue,
|
||||
Code, ContractAccessError, ContractExecResult, ContractInstantiateResult, GetStorageResult,
|
||||
InstantiateReturnValue, RentProjectionResult,
|
||||
};
|
||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, Hash, Saturating, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
type CodeHash<T> = <T as frame_system::Config>::Hash;
|
||||
type TrieId = Vec<u8>;
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
|
||||
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::NegativeImbalance;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
@@ -156,11 +155,10 @@ pub mod pallet {
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
||||
|
||||
/// The overarching call type.
|
||||
type Call:
|
||||
Dispatchable<Origin=Self::Origin, PostInfo=PostDispatchInfo> +
|
||||
GetDispatchInfo +
|
||||
codec::Decode +
|
||||
IsType<<Self as frame_system::Config>::Call>;
|
||||
type Call: Dispatchable<Origin = Self::Origin, PostInfo = PostDispatchInfo>
|
||||
+ GetDispatchInfo
|
||||
+ codec::Decode
|
||||
+ IsType<<Self as frame_system::Config>::Call>;
|
||||
|
||||
/// Filter that is applied to calls dispatched by contracts.
|
||||
///
|
||||
@@ -263,7 +261,7 @@ pub mod pallet {
|
||||
/// The allowed depth is `CallStack::size() + 1`.
|
||||
/// Therefore a size of `0` means that a contract cannot use call or instantiate.
|
||||
/// In other words only the origin called "root contract" is allowed to execute then.
|
||||
type CallStack: smallvec::Array<Item=Frame<Self>>;
|
||||
type CallStack: smallvec::Array<Item = Frame<Self>>;
|
||||
|
||||
/// The maximum number of tries that can be queued for deletion.
|
||||
#[pallet::constant]
|
||||
@@ -286,7 +284,8 @@ pub mod pallet {
|
||||
fn on_initialize(_block: T::BlockNumber) -> Weight {
|
||||
// We do not want to go above the block limit and rather avoid lazy deletion
|
||||
// in that case. This should only happen on runtime upgrades.
|
||||
let weight_limit = T::BlockWeights::get().max_block
|
||||
let weight_limit = T::BlockWeights::get()
|
||||
.max_block
|
||||
.saturating_sub(System::<T>::block_weight().total())
|
||||
.min(T::DeletionWeightLimit::get());
|
||||
Storage::<T>::process_deletion_queue_batch(weight_limit)
|
||||
@@ -317,14 +316,20 @@ pub mod pallet {
|
||||
dest: <T::Lookup as StaticLookup>::Source,
|
||||
#[pallet::compact] value: BalanceOf<T>,
|
||||
#[pallet::compact] gas_limit: Weight,
|
||||
data: Vec<u8>
|
||||
data: Vec<u8>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
let mut gas_meter = GasMeter::new(gas_limit);
|
||||
let schedule = T::Schedule::get();
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
|
||||
origin, dest, &mut gas_meter, &schedule, value, data, None,
|
||||
origin,
|
||||
dest,
|
||||
&mut gas_meter,
|
||||
&schedule,
|
||||
value,
|
||||
data,
|
||||
None,
|
||||
);
|
||||
gas_meter.into_dispatch_result(result, T::WeightInfo::call())
|
||||
}
|
||||
@@ -374,11 +379,19 @@ pub mod pallet {
|
||||
let code_len = executable.code_len();
|
||||
ensure!(code_len <= T::Schedule::get().limits.code_len, Error::<T>::CodeTooLarge);
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
|
||||
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
|
||||
).map(|(_address, output)| output);
|
||||
origin,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&schedule,
|
||||
endowment,
|
||||
data,
|
||||
&salt,
|
||||
None,
|
||||
)
|
||||
.map(|(_address, output)| output);
|
||||
gas_meter.into_dispatch_result(
|
||||
result,
|
||||
T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024)
|
||||
T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -403,12 +416,18 @@ pub mod pallet {
|
||||
let schedule = T::Schedule::get();
|
||||
let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?;
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
|
||||
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
|
||||
).map(|(_address, output)| output);
|
||||
gas_meter.into_dispatch_result(
|
||||
result,
|
||||
T::WeightInfo::instantiate(salt.len() as u32 / 1024),
|
||||
origin,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&schedule,
|
||||
endowment,
|
||||
data,
|
||||
&salt,
|
||||
None,
|
||||
)
|
||||
.map(|(_address, output)| output);
|
||||
gas_meter
|
||||
.into_dispatch_result(result, T::WeightInfo::instantiate(salt.len() as u32 / 1024))
|
||||
}
|
||||
|
||||
/// Allows block producers to claim a small reward for evicting a contract. If a block
|
||||
@@ -424,44 +443,33 @@ pub mod pallet {
|
||||
pub fn claim_surcharge(
|
||||
origin: OriginFor<T>,
|
||||
dest: T::AccountId,
|
||||
aux_sender: Option<T::AccountId>
|
||||
aux_sender: Option<T::AccountId>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let origin = origin.into();
|
||||
let (signed, rewarded) = match (origin, aux_sender) {
|
||||
(Ok(frame_system::RawOrigin::Signed(account)), None) => {
|
||||
(true, account)
|
||||
},
|
||||
(Ok(frame_system::RawOrigin::None), Some(aux_sender)) => {
|
||||
(false, aux_sender)
|
||||
},
|
||||
(Ok(frame_system::RawOrigin::Signed(account)), None) => (true, account),
|
||||
(Ok(frame_system::RawOrigin::None), Some(aux_sender)) => (false, aux_sender),
|
||||
_ => Err(Error::<T>::InvalidSurchargeClaim)?,
|
||||
};
|
||||
|
||||
// Add some advantage for block producers (who send unsigned extrinsics) by
|
||||
// adding a handicap: for signed extrinsics we use a slightly older block number
|
||||
// for the eviction check. This can be viewed as if we pushed regular users back in past.
|
||||
let handicap = if signed {
|
||||
T::SignedClaimHandicap::get()
|
||||
} else {
|
||||
Zero::zero()
|
||||
};
|
||||
let handicap = if signed { T::SignedClaimHandicap::get() } else { Zero::zero() };
|
||||
|
||||
// If poking the contract has lead to eviction of the contract, give out the rewards.
|
||||
match Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)? {
|
||||
(Some(rent_paid), code_len) => {
|
||||
T::Currency::deposit_into_existing(
|
||||
&rewarded,
|
||||
T::SurchargeReward::get().min(rent_paid),
|
||||
)
|
||||
.map(|_| PostDispatchInfo {
|
||||
actual_weight: Some(T::WeightInfo::claim_surcharge(code_len / 1024)),
|
||||
pays_fee: Pays::No,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
(None, code_len) => Err(Error::<T>::ContractNotEvictable.with_weight(
|
||||
T::WeightInfo::claim_surcharge(code_len / 1024)
|
||||
)),
|
||||
(Some(rent_paid), code_len) => T::Currency::deposit_into_existing(
|
||||
&rewarded,
|
||||
T::SurchargeReward::get().min(rent_paid),
|
||||
)
|
||||
.map(|_| PostDispatchInfo {
|
||||
actual_weight: Some(T::WeightInfo::claim_surcharge(code_len / 1024)),
|
||||
pays_fee: Pays::No,
|
||||
})
|
||||
.map_err(Into::into),
|
||||
(None, code_len) => Err(Error::<T>::ContractNotEvictable
|
||||
.with_weight(T::WeightInfo::claim_surcharge(code_len / 1024))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -638,7 +646,8 @@ pub mod pallet {
|
||||
|
||||
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
||||
#[pallet::storage]
|
||||
pub(crate) type CodeStorage<T: Config> = StorageMap<_, Identity, CodeHash<T>, PrefabWasmModule<T>>;
|
||||
pub(crate) type CodeStorage<T: Config> =
|
||||
StorageMap<_, Identity, CodeHash<T>, PrefabWasmModule<T>>;
|
||||
|
||||
/// The subtrie counter.
|
||||
#[pallet::storage]
|
||||
@@ -648,7 +657,8 @@ pub mod pallet {
|
||||
///
|
||||
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
|
||||
#[pallet::storage]
|
||||
pub(crate) type ContractInfoOf<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, ContractInfo<T>>;
|
||||
pub(crate) type ContractInfoOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, ContractInfo<T>>;
|
||||
|
||||
/// Evicted contracts that await child trie deletion.
|
||||
///
|
||||
@@ -684,13 +694,15 @@ where
|
||||
) -> ContractExecResult {
|
||||
let mut gas_meter = GasMeter::new(gas_limit);
|
||||
let schedule = T::Schedule::get();
|
||||
let mut debug_message = if debug {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut debug_message = if debug { Some(Vec::new()) } else { None };
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
|
||||
origin, dest, &mut gas_meter, &schedule, value, input_data, debug_message.as_mut(),
|
||||
origin,
|
||||
dest,
|
||||
&mut gas_meter,
|
||||
&schedule,
|
||||
value,
|
||||
input_data,
|
||||
debug_message.as_mut(),
|
||||
);
|
||||
ContractExecResult {
|
||||
result: result.map_err(|r| r.error),
|
||||
@@ -734,34 +746,36 @@ where
|
||||
};
|
||||
let executable = match executable {
|
||||
Ok(executable) => executable,
|
||||
Err(error) => return ContractInstantiateResult {
|
||||
result: Err(error.into()),
|
||||
gas_consumed: gas_meter.gas_consumed(),
|
||||
gas_required: gas_meter.gas_required(),
|
||||
debug_message: Vec::new(),
|
||||
}
|
||||
};
|
||||
let mut debug_message = if debug {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
Err(error) =>
|
||||
return ContractInstantiateResult {
|
||||
result: Err(error.into()),
|
||||
gas_consumed: gas_meter.gas_consumed(),
|
||||
gas_required: gas_meter.gas_required(),
|
||||
debug_message: Vec::new(),
|
||||
},
|
||||
};
|
||||
let mut debug_message = if debug { Some(Vec::new()) } else { None };
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
|
||||
origin, executable, &mut gas_meter, &schedule,
|
||||
endowment, data, &salt, debug_message.as_mut(),
|
||||
).and_then(|(account_id, result)| {
|
||||
origin,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&schedule,
|
||||
endowment,
|
||||
data,
|
||||
&salt,
|
||||
debug_message.as_mut(),
|
||||
)
|
||||
.and_then(|(account_id, result)| {
|
||||
let rent_projection = if compute_projection {
|
||||
Some(Rent::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
|
||||
.map_err(|_| <Error<T>>::NewContractNotFunded)?)
|
||||
Some(
|
||||
Rent::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
|
||||
.map_err(|_| <Error<T>>::NewContractNotFunded)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(InstantiateReturnValue {
|
||||
result,
|
||||
account_id,
|
||||
rent_projection,
|
||||
})
|
||||
Ok(InstantiateReturnValue { result, account_id, rent_projection })
|
||||
});
|
||||
ContractInstantiateResult {
|
||||
result: result.map_err(|e| e.error),
|
||||
@@ -800,9 +814,10 @@ where
|
||||
deploying_address: &T::AccountId,
|
||||
code_hash: &CodeHash<T>,
|
||||
salt: &[u8],
|
||||
) -> T::AccountId
|
||||
{
|
||||
let buf: Vec<_> = deploying_address.as_ref().iter()
|
||||
) -> T::AccountId {
|
||||
let buf: Vec<_> = deploying_address
|
||||
.as_ref()
|
||||
.iter()
|
||||
.chain(code_hash.as_ref())
|
||||
.chain(salt)
|
||||
.cloned()
|
||||
@@ -847,7 +862,7 @@ where
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn reinstrument_module(
|
||||
module: &mut PrefabWasmModule<T>,
|
||||
schedule: &Schedule<T>
|
||||
schedule: &Schedule<T>,
|
||||
) -> frame_support::dispatch::DispatchResult {
|
||||
self::wasm::reinstrument(module, schedule)
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{Config, Weight, Pallet};
|
||||
use crate::{Config, Pallet, Weight};
|
||||
use frame_support::{
|
||||
storage::migration,
|
||||
traits::{GetPalletVersion, PalletVersion, PalletInfoAccess, Get},
|
||||
traits::{Get, GetPalletVersion, PalletInfoAccess, PalletVersion},
|
||||
};
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
@@ -32,7 +32,7 @@ pub fn migrate<T: Config>() -> Weight {
|
||||
b"CurrentSchedule",
|
||||
b"",
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
||||
@@ -18,23 +18,23 @@
|
||||
//! A module responsible for computing the right amount of weight and charging it.
|
||||
|
||||
use crate::{
|
||||
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event,
|
||||
TombstoneContractInfo, Config, CodeHash, Error,
|
||||
storage::Storage, wasm::PrefabWasmModule, exec::Executable, gas::GasMeter,
|
||||
exec::Executable, gas::GasMeter, storage::Storage, wasm::PrefabWasmModule, AliveContractInfo,
|
||||
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Pallet,
|
||||
TombstoneContractInfo,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use frame_support::{
|
||||
storage::child,
|
||||
traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},
|
||||
DefaultNoBound,
|
||||
};
|
||||
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::{
|
||||
DispatchError,
|
||||
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
|
||||
DispatchError,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Information about the required deposit and resulting rent.
|
||||
///
|
||||
@@ -83,13 +83,8 @@ where
|
||||
code_size: u32,
|
||||
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
Zero::zero(),
|
||||
&contract,
|
||||
code_size,
|
||||
);
|
||||
let verdict =
|
||||
Self::consider_case(account, current_block_number, Zero::zero(), &contract, code_size);
|
||||
Self::enact_verdict(account, contract, current_block_number, verdict, None)
|
||||
}
|
||||
|
||||
@@ -136,10 +131,14 @@ where
|
||||
.unwrap_or_else(|| <BalanceOf<T>>::zero())
|
||||
.saturating_add(contract.rent_paid);
|
||||
Self::enact_verdict(
|
||||
account, contract, current_block_number, verdict, Some(module),
|
||||
account,
|
||||
contract,
|
||||
current_block_number,
|
||||
verdict,
|
||||
Some(module),
|
||||
)?;
|
||||
Ok((Some(rent_paid), code_len))
|
||||
}
|
||||
},
|
||||
_ => Ok((None, code_len)),
|
||||
}
|
||||
}
|
||||
@@ -155,9 +154,7 @@ where
|
||||
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
|
||||
/// compute the projection. This function is only used for implementation of an RPC method through
|
||||
/// `RuntimeApi` meaning that the changes will be discarded anyway.
|
||||
pub fn compute_projection(
|
||||
account: &T::AccountId,
|
||||
) -> RentProjectionResult<T::BlockNumber> {
|
||||
pub fn compute_projection(account: &T::AccountId) -> RentProjectionResult<T::BlockNumber> {
|
||||
use ContractAccessError::IsTombstone;
|
||||
|
||||
let contract_info = <ContractInfoOf<T>>::get(account);
|
||||
@@ -179,45 +176,42 @@ where
|
||||
|
||||
// We skip the eviction in case one is in order.
|
||||
// Evictions should only be performed by [`try_eviction`].
|
||||
let new_contract_info = Self::enact_verdict(
|
||||
account, alive_contract_info, current_block_number, verdict, None,
|
||||
);
|
||||
let new_contract_info =
|
||||
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict, None);
|
||||
|
||||
// Check what happened after enaction of the verdict.
|
||||
let alive_contract_info = new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
|
||||
let alive_contract_info =
|
||||
new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
|
||||
|
||||
// Compute how much would the fee per block be with the *updated* balance.
|
||||
let total_balance = T::Currency::total_balance(account);
|
||||
let free_balance = T::Currency::free_balance(account);
|
||||
let fee_per_block = Self::fee_per_block(
|
||||
&free_balance, &alive_contract_info, code_size,
|
||||
);
|
||||
let fee_per_block = Self::fee_per_block(&free_balance, &alive_contract_info, code_size);
|
||||
if fee_per_block.is_zero() {
|
||||
return Ok(RentProjection::NoEviction);
|
||||
return Ok(RentProjection::NoEviction)
|
||||
}
|
||||
|
||||
// Then compute how much the contract will sustain under these circumstances.
|
||||
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info).expect(
|
||||
"the contract exists and in the alive state;
|
||||
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info)
|
||||
.expect(
|
||||
"the contract exists and in the alive state;
|
||||
the updated balance must be greater than subsistence deposit;
|
||||
this function doesn't return `None`;
|
||||
qed
|
||||
",
|
||||
);
|
||||
);
|
||||
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
|
||||
Some(blocks_left) => blocks_left,
|
||||
None => {
|
||||
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
|
||||
// there is an overflow. This cannot happen with integers though. Return
|
||||
// `NoEviction` here just in case.
|
||||
return Ok(RentProjection::NoEviction);
|
||||
}
|
||||
return Ok(RentProjection::NoEviction)
|
||||
},
|
||||
};
|
||||
|
||||
let blocks_left = blocks_left.saturated_into::<u32>().into();
|
||||
Ok(RentProjection::EvictionAt(
|
||||
current_block_number + blocks_left,
|
||||
))
|
||||
Ok(RentProjection::EvictionAt(current_block_number + blocks_left))
|
||||
}
|
||||
|
||||
/// Restores the destination account using the origin as prototype.
|
||||
@@ -246,18 +240,15 @@ where
|
||||
let current_block = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err(Error::<T>::InvalidContractOrigin.into());
|
||||
return Err(Error::<T>::InvalidContractOrigin.into())
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or(Error::<T>::InvalidDestinationContract)?;
|
||||
|
||||
let last_write = if !delta.is_empty() {
|
||||
Some(current_block)
|
||||
} else {
|
||||
origin_contract.last_write
|
||||
};
|
||||
let last_write =
|
||||
if !delta.is_empty() { Some(current_block) } else { origin_contract.last_write };
|
||||
|
||||
// Fails if the code hash does not exist on chain
|
||||
E::add_user(code_hash, gas_meter)?;
|
||||
@@ -266,7 +257,8 @@ where
|
||||
// fail later due to tombstones not matching. This is because the restoration
|
||||
// is always called from a contract and therefore in a storage transaction.
|
||||
// The failure of this function will lead to this transaction's rollback.
|
||||
let bytes_taken: u32 = delta.iter()
|
||||
let bytes_taken: u32 = delta
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
let key = blake2_256(key);
|
||||
child::get_raw(&child_trie_info, &key).map(|value| {
|
||||
@@ -284,21 +276,24 @@ where
|
||||
);
|
||||
|
||||
if tombstone != dest_tombstone {
|
||||
return Err(Error::<T>::InvalidTombstone.into());
|
||||
return Err(Error::<T>::InvalidTombstone.into())
|
||||
}
|
||||
|
||||
origin_contract.storage_size -= bytes_taken;
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
E::remove_user(origin_contract.code_hash, gas_meter)?;
|
||||
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
rent_paid: <BalanceOf<T>>::zero(),
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
.. origin_contract
|
||||
}));
|
||||
<ContractInfoOf<T>>::insert(
|
||||
&dest,
|
||||
ContractInfo::Alive(AliveContractInfo::<T> {
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
rent_paid: <BalanceOf<T>>::zero(),
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
..origin_contract
|
||||
}),
|
||||
);
|
||||
|
||||
let origin_free_balance = T::Currency::free_balance(&origin);
|
||||
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
|
||||
@@ -314,42 +309,34 @@ where
|
||||
current_refcount: u32,
|
||||
at_refcount: u32,
|
||||
) -> RentStatus<T> {
|
||||
let calc_share = |refcount: u32| {
|
||||
aggregated_code_size.checked_div(refcount).unwrap_or(0)
|
||||
};
|
||||
let calc_share = |refcount: u32| aggregated_code_size.checked_div(refcount).unwrap_or(0);
|
||||
let current_share = calc_share(current_refcount);
|
||||
let custom_share = calc_share(at_refcount);
|
||||
RentStatus {
|
||||
max_deposit: Self::required_deposit(contract, aggregated_code_size),
|
||||
current_deposit: Self::required_deposit(contract, current_share),
|
||||
custom_refcount_deposit:
|
||||
if at_refcount > 0 {
|
||||
Some(Self::required_deposit(contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
custom_refcount_deposit: if at_refcount > 0 {
|
||||
Some(Self::required_deposit(contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
max_rent: Self::fee_per_block(free_balance, contract, aggregated_code_size),
|
||||
current_rent: Self::fee_per_block(free_balance, contract, current_share),
|
||||
custom_refcount_rent:
|
||||
if at_refcount > 0 {
|
||||
Some(Self::fee_per_block(free_balance, contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
custom_refcount_rent: if at_refcount > 0 {
|
||||
Some(Self::fee_per_block(free_balance, contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
_reserved: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how much deposit is required to not pay rent.
|
||||
fn required_deposit(
|
||||
contract: &AliveContractInfo<T>,
|
||||
code_size_share: u32,
|
||||
) -> BalanceOf<T> {
|
||||
fn required_deposit(contract: &AliveContractInfo<T>, code_size_share: u32) -> BalanceOf<T> {
|
||||
T::DepositPerStorageByte::get()
|
||||
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
|
||||
.saturating_add(
|
||||
T::DepositPerStorageItem::get()
|
||||
.saturating_mul(contract.pair_count.into())
|
||||
T::DepositPerStorageItem::get().saturating_mul(contract.pair_count.into()),
|
||||
)
|
||||
.saturating_add(T::DepositPerContract::get())
|
||||
}
|
||||
@@ -363,8 +350,8 @@ where
|
||||
contract: &AliveContractInfo<T>,
|
||||
code_size_share: u32,
|
||||
) -> BalanceOf<T> {
|
||||
let missing_deposit = Self::required_deposit(contract, code_size_share)
|
||||
.saturating_sub(*free_balance);
|
||||
let missing_deposit =
|
||||
Self::required_deposit(contract, code_size_share).saturating_sub(*free_balance);
|
||||
T::RentFraction::get().mul_ceil(missing_deposit)
|
||||
}
|
||||
|
||||
@@ -383,16 +370,13 @@ where
|
||||
// Reserved balance contributes towards the subsistence threshold to stay consistent
|
||||
// with the existential deposit where the reserved balance is also counted.
|
||||
if *total_balance < subsistence_threshold {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
// However, reserved balance cannot be charged so we need to use the free balance
|
||||
// to calculate the actual budget (which can be 0).
|
||||
let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
|
||||
Some(<BalanceOf<T>>::min(
|
||||
contract.rent_allowance,
|
||||
rent_allowed_to_charge,
|
||||
))
|
||||
Some(<BalanceOf<T>>::min(contract.rent_allowance, rent_allowed_to_charge))
|
||||
}
|
||||
|
||||
/// Consider the case for rent payment of the given account and returns a `Verdict`.
|
||||
@@ -414,7 +398,7 @@ where
|
||||
};
|
||||
if blocks_passed.is_zero() {
|
||||
// Rent has already been paid
|
||||
return Verdict::Exempt;
|
||||
return Verdict::Exempt
|
||||
}
|
||||
|
||||
let total_balance = T::Currency::total_balance(account);
|
||||
@@ -425,7 +409,7 @@ where
|
||||
if fee_per_block.is_zero() {
|
||||
// The rent deposit offset reduced the fee to 0. This means that the contract
|
||||
// gets the rent for free.
|
||||
return Verdict::Exempt;
|
||||
return Verdict::Exempt
|
||||
}
|
||||
|
||||
let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {
|
||||
@@ -443,7 +427,7 @@ where
|
||||
account,
|
||||
);
|
||||
0u32.into()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let dues = fee_per_block
|
||||
@@ -469,18 +453,15 @@ where
|
||||
if insufficient_rent || !can_withdraw_rent {
|
||||
// The contract cannot afford the rent payment and has a balance above the subsistence
|
||||
// threshold, so it leaves a tombstone.
|
||||
let amount = if can_withdraw_rent {
|
||||
Some(OutstandingAmount::new(dues_limited))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Verdict::Evict { amount };
|
||||
let amount =
|
||||
if can_withdraw_rent { Some(OutstandingAmount::new(dues_limited)) } else { None };
|
||||
return Verdict::Evict { amount }
|
||||
}
|
||||
|
||||
return Verdict::Charge {
|
||||
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
|
||||
amount: OutstandingAmount::new(dues_limited),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Enacts the given verdict and returns the updated `ContractInfo`.
|
||||
@@ -511,9 +492,7 @@ where
|
||||
}
|
||||
|
||||
// Note: this operation is heavy.
|
||||
let child_storage_root = child::root(
|
||||
&alive_contract_info.child_trie_info(),
|
||||
);
|
||||
let child_storage_root = child::root(&alive_contract_info.child_trie_info());
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
&child_storage_root[..],
|
||||
@@ -524,11 +503,9 @@ where
|
||||
code.drop_from_storage();
|
||||
<Pallet<T>>::deposit_event(Event::Evicted(account.clone()));
|
||||
Ok(None)
|
||||
}
|
||||
(Verdict::Evict { amount: _ }, None) => {
|
||||
Ok(None)
|
||||
}
|
||||
(Verdict::Exempt, _) => {
|
||||
},
|
||||
(Verdict::Evict { amount: _ }, None) => Ok(None),
|
||||
(Verdict::Exempt, _) => {
|
||||
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
|
||||
deduct_block: current_block_number,
|
||||
..alive_contract_info
|
||||
@@ -546,11 +523,9 @@ where
|
||||
<ContractInfoOf<T>>::insert(account, &contract);
|
||||
amount.withdraw(account);
|
||||
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// The amount to charge.
|
||||
@@ -596,9 +571,7 @@ enum Verdict<T: Config> {
|
||||
Exempt,
|
||||
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
|
||||
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
|
||||
Evict {
|
||||
amount: Option<OutstandingAmount<T>>,
|
||||
},
|
||||
Evict { amount: Option<OutstandingAmount<T>> },
|
||||
/// Everything is OK, we just only take some charge.
|
||||
Charge { amount: OutstandingAmount<T> },
|
||||
}
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
//! This module contains the cost schedule and supporting code that constructs a
|
||||
//! sane default schedule from a `WeightInfo` implementation.
|
||||
|
||||
use crate::{Config, weights::WeightInfo};
|
||||
use crate::{weights::WeightInfo, Config};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{weights::Weight, DefaultNoBound};
|
||||
use pallet_contracts_proc_macro::{ScheduleDebug, WeightDebug};
|
||||
use frame_support::{DefaultNoBound, weights::Weight};
|
||||
use sp_std::{marker::PhantomData, vec::Vec};
|
||||
use codec::{Encode, Decode};
|
||||
use pwasm_utils::{parity_wasm::elements, rules};
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{marker::PhantomData, vec::Vec};
|
||||
|
||||
/// How many API calls are executed in a single batch. The reason for increasing the amount
|
||||
/// of API calls in batches (per benchmark component increase) is so that the linear regression
|
||||
@@ -50,18 +50,18 @@ pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
|
||||
/// fn create_schedule<T: Config>() -> Schedule<T> {
|
||||
/// Schedule {
|
||||
/// limits: Limits {
|
||||
/// globals: 3,
|
||||
/// parameters: 3,
|
||||
/// memory_pages: 16,
|
||||
/// table_size: 3,
|
||||
/// br_table_size: 3,
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// globals: 3,
|
||||
/// parameters: 3,
|
||||
/// memory_pages: 16,
|
||||
/// table_size: 3,
|
||||
/// br_table_size: 3,
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// instruction_weights: InstructionWeights {
|
||||
/// version: 5,
|
||||
/// version: 5,
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// .. Default::default()
|
||||
/// .. Default::default()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@@ -392,11 +392,13 @@ pub struct HostFnWeights<T: Config> {
|
||||
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>
|
||||
pub _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
macro_rules! replace_token {
|
||||
($_in:tt $replacement:tt) => { $replacement };
|
||||
($_in:tt $replacement:tt) => {
|
||||
$replacement
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! call_zero {
|
||||
@@ -420,20 +422,22 @@ macro_rules! cost_batched_args {
|
||||
macro_rules! cost_instr_no_params_with_batch_size {
|
||||
($name:ident, $batch_size:expr) => {
|
||||
(cost_args!($name, 1) / Weight::from($batch_size)) as u32
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_instr_with_batch_size {
|
||||
($name:ident, $num_params:expr, $batch_size:expr) => {
|
||||
cost_instr_no_params_with_batch_size!($name, $batch_size)
|
||||
.saturating_sub((cost_instr_no_params_with_batch_size!(instr_i64const, $batch_size) / 2).saturating_mul($num_params))
|
||||
}
|
||||
cost_instr_no_params_with_batch_size!($name, $batch_size).saturating_sub(
|
||||
(cost_instr_no_params_with_batch_size!(instr_i64const, $batch_size) / 2)
|
||||
.saturating_mul($num_params),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_instr {
|
||||
($name:ident, $num_params:expr) => {
|
||||
cost_instr_with_batch_size!($name, $num_params, INSTR_BENCHMARK_BATCH_SIZE)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_byte_args {
|
||||
@@ -451,25 +455,25 @@ macro_rules! cost_byte_batched_args {
|
||||
macro_rules! cost {
|
||||
($name:ident) => {
|
||||
cost_args!($name, 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_batched {
|
||||
($name:ident) => {
|
||||
cost_batched_args!($name, 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_byte {
|
||||
($name:ident) => {
|
||||
cost_byte_args!($name, 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cost_byte_batched {
|
||||
($name:ident) => {
|
||||
cost_byte_batched_args!($name, 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
@@ -578,7 +582,11 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
random: cost_batched!(seal_random),
|
||||
deposit_event: cost_batched!(seal_deposit_event),
|
||||
deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0),
|
||||
deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1),
|
||||
deposit_event_per_byte: cost_byte_batched_args!(
|
||||
seal_deposit_event_per_topic_and_kb,
|
||||
0,
|
||||
1
|
||||
),
|
||||
debug_message: cost_batched!(seal_debug_message),
|
||||
set_rent_allowance: cost_batched!(seal_set_rent_allowance),
|
||||
set_storage: cost_batched!(seal_set_storage),
|
||||
@@ -588,13 +596,43 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb),
|
||||
transfer: cost_batched!(seal_transfer),
|
||||
call: cost_batched!(seal_call),
|
||||
call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0),
|
||||
call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0),
|
||||
call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1),
|
||||
call_transfer_surcharge: cost_batched_args!(
|
||||
seal_call_per_transfer_input_output_kb,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
),
|
||||
call_per_input_byte: cost_byte_batched_args!(
|
||||
seal_call_per_transfer_input_output_kb,
|
||||
0,
|
||||
1,
|
||||
0
|
||||
),
|
||||
call_per_output_byte: cost_byte_batched_args!(
|
||||
seal_call_per_transfer_input_output_kb,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
),
|
||||
instantiate: cost_batched!(seal_instantiate),
|
||||
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0),
|
||||
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0),
|
||||
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 0, 1),
|
||||
instantiate_per_input_byte: cost_byte_batched_args!(
|
||||
seal_instantiate_per_input_output_salt_kb,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
),
|
||||
instantiate_per_output_byte: cost_byte_batched_args!(
|
||||
seal_instantiate_per_input_output_salt_kb,
|
||||
0,
|
||||
1,
|
||||
0
|
||||
),
|
||||
instantiate_per_salt_byte: cost_byte_batched_args!(
|
||||
seal_instantiate_per_input_output_salt_kb,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
),
|
||||
hash_sha2_256: cost_batched!(seal_hash_sha2_256),
|
||||
hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb),
|
||||
hash_keccak_256: cost_batched!(seal_hash_keccak_256),
|
||||
@@ -625,7 +663,7 @@ impl<T: Config> Schedule<T> {
|
||||
let elements::Type::Function(func) = func;
|
||||
func.params().len() as u32
|
||||
})
|
||||
.collect()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,12 +677,25 @@ impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> {
|
||||
let weight = match *instruction {
|
||||
End | Unreachable | Return | Else => 0,
|
||||
I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const,
|
||||
I32Load(_, _) | I32Load8S(_, _) | I32Load8U(_, _) | I32Load16S(_, _) |
|
||||
I32Load16U(_, _) | I64Load(_, _) | I64Load8S(_, _) | I64Load8U(_, _) |
|
||||
I64Load16S(_, _) | I64Load16U(_, _) | I64Load32S(_, _) | I64Load32U(_, _)
|
||||
=> w.i64load,
|
||||
I32Store(_, _) | I32Store8(_, _) | I32Store16(_, _) | I64Store(_, _) |
|
||||
I64Store8(_, _) | I64Store16(_, _) | I64Store32(_, _) => w.i64store,
|
||||
I32Load(_, _) |
|
||||
I32Load8S(_, _) |
|
||||
I32Load8U(_, _) |
|
||||
I32Load16S(_, _) |
|
||||
I32Load16U(_, _) |
|
||||
I64Load(_, _) |
|
||||
I64Load8S(_, _) |
|
||||
I64Load8U(_, _) |
|
||||
I64Load16S(_, _) |
|
||||
I64Load16U(_, _) |
|
||||
I64Load32S(_, _) |
|
||||
I64Load32U(_, _) => w.i64load,
|
||||
I32Store(_, _) |
|
||||
I32Store8(_, _) |
|
||||
I32Store16(_, _) |
|
||||
I64Store(_, _) |
|
||||
I64Store8(_, _) |
|
||||
I64Store16(_, _) |
|
||||
I64Store32(_, _) => w.i64store,
|
||||
Select => w.select,
|
||||
If(_) => w.r#if,
|
||||
Br(_) => w.br,
|
||||
@@ -658,10 +709,9 @@ impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> {
|
||||
CurrentMemory(_) => w.memory_current,
|
||||
GrowMemory(_) => w.memory_grow,
|
||||
CallIndirect(idx, _) => *self.params.get(idx as usize).unwrap_or(&max_params),
|
||||
BrTable(ref data) =>
|
||||
w.br_table.saturating_add(
|
||||
w.br_table_per_entry.saturating_mul(data.table.len() as u32)
|
||||
),
|
||||
BrTable(ref data) => w
|
||||
.br_table
|
||||
.saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)),
|
||||
I32Clz | I64Clz => w.i64clz,
|
||||
I32Ctz | I64Ctz => w.i64ctz,
|
||||
I32Popcnt | I64Popcnt => w.i64popcnt,
|
||||
@@ -711,8 +761,8 @@ impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::tests::Test;
|
||||
use super::*;
|
||||
use crate::tests::Test;
|
||||
|
||||
#[test]
|
||||
fn print_test_schedule() {
|
||||
|
||||
@@ -19,29 +19,30 @@
|
||||
|
||||
use crate::{
|
||||
exec::{AccountIdOf, StorageKey},
|
||||
BalanceOf, CodeHash, ContractInfoOf, Config, TrieId, DeletionQueue, Error,
|
||||
weights::WeightInfo,
|
||||
BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId,
|
||||
};
|
||||
use codec::{Codec, Encode, Decode};
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::{marker::PhantomData, fmt::Debug};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::{
|
||||
RuntimeDebug,
|
||||
traits::{Bounded, Saturating, Zero, Hash, Member, MaybeSerializeDeserialize},
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
storage::child::{self, KillStorageResult, ChildInfo},
|
||||
storage::child::{self, ChildInfo, KillStorageResult},
|
||||
traits::Get,
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, Hash, MaybeSerializeDeserialize, Member, Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||
|
||||
pub type AliveContractInfo<T> =
|
||||
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
|
||||
pub type TombstoneContractInfo<T> =
|
||||
RawTombstoneContractInfo<<T as frame_system::Config>::Hash, <T as frame_system::Config>::Hashing>;
|
||||
pub type TombstoneContractInfo<T> = RawTombstoneContractInfo<
|
||||
<T as frame_system::Config>::Hash,
|
||||
<T as frame_system::Config>::Hashing,
|
||||
>;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account
|
||||
@@ -126,10 +127,16 @@ pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
|
||||
|
||||
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
|
||||
where
|
||||
H: Member + MaybeSerializeDeserialize+ Debug
|
||||
+ AsRef<[u8]> + AsMut<[u8]> + Copy + Default
|
||||
+ sp_std::hash::Hash + Codec,
|
||||
Hasher: Hash<Output=H>,
|
||||
H: Member
|
||||
+ MaybeSerializeDeserialize
|
||||
+ Debug
|
||||
+ AsRef<[u8]>
|
||||
+ AsMut<[u8]>
|
||||
+ Copy
|
||||
+ Default
|
||||
+ sp_std::hash::Hash
|
||||
+ Codec,
|
||||
Hasher: Hash<Output = H>,
|
||||
{
|
||||
pub fn new(storage_root: &[u8], code_hash: H) -> Self {
|
||||
let mut buf = Vec::new();
|
||||
@@ -156,7 +163,7 @@ pub struct Storage<T>(PhantomData<T>);
|
||||
impl<T> Storage<T>
|
||||
where
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
/// Reads a storage kv pair of a contract.
|
||||
///
|
||||
@@ -187,11 +194,15 @@ where
|
||||
// Update the total number of KV pairs and the number of empty pairs.
|
||||
match (&opt_prev_len, &opt_new_value) {
|
||||
(Some(_), None) => {
|
||||
new_info.pair_count = new_info.pair_count.checked_sub(1)
|
||||
new_info.pair_count = new_info
|
||||
.pair_count
|
||||
.checked_sub(1)
|
||||
.ok_or_else(|| Error::<T>::StorageExhausted)?;
|
||||
},
|
||||
(None, Some(_)) => {
|
||||
new_info.pair_count = new_info.pair_count.checked_add(1)
|
||||
new_info.pair_count = new_info
|
||||
.pair_count
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| Error::<T>::StorageExhausted)?;
|
||||
},
|
||||
(Some(_), Some(_)) => {},
|
||||
@@ -200,10 +211,8 @@ where
|
||||
|
||||
// Update the total storage size.
|
||||
let prev_value_len = opt_prev_len.unwrap_or(0);
|
||||
let new_value_len = opt_new_value
|
||||
.as_ref()
|
||||
.map(|new_value| new_value.len() as u32)
|
||||
.unwrap_or(0);
|
||||
let new_value_len =
|
||||
opt_new_value.as_ref().map(|new_value| new_value.len() as u32).unwrap_or(0);
|
||||
new_info.storage_size = new_info
|
||||
.storage_size
|
||||
.checked_sub(prev_value_len)
|
||||
@@ -230,7 +239,7 @@ where
|
||||
ch: CodeHash<T>,
|
||||
) -> Result<AliveContractInfo<T>, DispatchError> {
|
||||
if <ContractInfoOf<T>>::contains_key(account) {
|
||||
return Err(Error::<T>::DuplicateContract.into());
|
||||
return Err(Error::<T>::DuplicateContract.into())
|
||||
}
|
||||
|
||||
let contract = AliveContractInfo::<T> {
|
||||
@@ -297,19 +306,17 @@ where
|
||||
pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight {
|
||||
let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);
|
||||
if queue_len == 0 {
|
||||
return weight_limit;
|
||||
return weight_limit
|
||||
}
|
||||
|
||||
let (weight_per_key, mut remaining_key_budget) = Self::deletion_budget(
|
||||
queue_len,
|
||||
weight_limit,
|
||||
);
|
||||
let (weight_per_key, mut remaining_key_budget) =
|
||||
Self::deletion_budget(queue_len, weight_limit);
|
||||
|
||||
// We want to check whether we have enough weight to decode the queue before
|
||||
// proceeding. Too little weight for decoding might happen during runtime upgrades
|
||||
// which consume the whole block before the other `on_initialize` blocks are called.
|
||||
if remaining_key_budget == 0 {
|
||||
return weight_limit;
|
||||
return weight_limit
|
||||
}
|
||||
|
||||
let mut queue = <DeletionQueue<T>>::get();
|
||||
@@ -318,10 +325,8 @@ where
|
||||
// Cannot panic due to loop condition
|
||||
let trie = &mut queue[0];
|
||||
let pair_count = trie.pair_count;
|
||||
let outcome = child::kill_storage(
|
||||
&child_trie_info(&trie.trie_id),
|
||||
Some(remaining_key_budget),
|
||||
);
|
||||
let outcome =
|
||||
child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));
|
||||
if pair_count > remaining_key_budget {
|
||||
// Cannot underflow because of the if condition
|
||||
trie.pair_count -= remaining_key_budget;
|
||||
@@ -341,8 +346,8 @@ where
|
||||
KillStorageResult::AllRemoved(_) => (),
|
||||
}
|
||||
}
|
||||
remaining_key_budget = remaining_key_budget
|
||||
.saturating_sub(remaining_key_budget.min(pair_count));
|
||||
remaining_key_budget =
|
||||
remaining_key_budget.saturating_sub(remaining_key_budget.min(pair_count));
|
||||
}
|
||||
|
||||
<DeletionQueue<T>>::put(queue);
|
||||
@@ -352,29 +357,22 @@ where
|
||||
/// This generator uses inner counter for account id and applies the hash over `AccountId +
|
||||
/// accountid_counter`.
|
||||
pub fn generate_trie_id(account_id: &AccountIdOf<T>, seed: u64) -> TrieId {
|
||||
let buf: Vec<_> = account_id.as_ref().iter()
|
||||
.chain(&seed.to_le_bytes())
|
||||
.cloned()
|
||||
.collect();
|
||||
let buf: Vec<_> = account_id.as_ref().iter().chain(&seed.to_le_bytes()).cloned().collect();
|
||||
T::Hashing::hash(&buf).as_ref().into()
|
||||
}
|
||||
|
||||
/// Returns the code hash of the contract specified by `account` ID.
|
||||
#[cfg(test)]
|
||||
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>>
|
||||
{
|
||||
<ContractInfoOf<T>>::get(account)
|
||||
.and_then(|i| i.as_alive().map(|i| i.code_hash))
|
||||
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
||||
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash))
|
||||
}
|
||||
|
||||
/// Fill up the queue in order to exercise the limits during testing.
|
||||
#[cfg(test)]
|
||||
pub fn fill_queue_with_dummies() {
|
||||
let queue: Vec<_> = (0..T::DeletionQueueDepth::get()).map(|_| DeletedContract {
|
||||
pair_count: 0,
|
||||
trie_id: vec![],
|
||||
})
|
||||
.collect();
|
||||
let queue: Vec<_> = (0..T::DeletionQueueDepth::get())
|
||||
.map(|_| DeletedContract { pair_count: 0, trie_id: vec![] })
|
||||
.collect();
|
||||
<DeletionQueue<T>>::put(queue);
|
||||
}
|
||||
}
|
||||
|
||||
+1262
-1576
File diff suppressed because it is too large
Load Diff
@@ -27,16 +27,17 @@
|
||||
//! this guarantees that every instrumented contract code in cache cannot have the version equal to the current one.
|
||||
//! Thus, before executing a contract it should be reinstrument with new schedule.
|
||||
|
||||
use crate::{
|
||||
CodeHash, CodeStorage, PristineCode, Schedule, Config, Error, Weight,
|
||||
wasm::{prepare, PrefabWasmModule}, Pallet as Contracts, Event,
|
||||
gas::{GasMeter, Token},
|
||||
weights::WeightInfo,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use frame_support::dispatch::DispatchError;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use self::private::reinstrument as reinstrument;
|
||||
pub use self::private::reinstrument;
|
||||
use crate::{
|
||||
gas::{GasMeter, Token},
|
||||
wasm::{prepare, PrefabWasmModule},
|
||||
weights::WeightInfo,
|
||||
CodeHash, CodeStorage, Config, Error, Event, Pallet as Contracts, PristineCode, Schedule,
|
||||
Weight,
|
||||
};
|
||||
use frame_support::dispatch::DispatchError;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
|
||||
/// Put the instrumented module in storage.
|
||||
///
|
||||
@@ -44,7 +45,7 @@ pub use self::private::reinstrument as reinstrument;
|
||||
/// under the specified `code_hash`.
|
||||
pub fn store<T: Config>(mut prefab_module: PrefabWasmModule<T>)
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
let code_hash = sp_std::mem::take(&mut prefab_module.code_hash);
|
||||
|
||||
@@ -53,14 +54,12 @@ where
|
||||
if let Some(code) = prefab_module.original_code.take() {
|
||||
<PristineCode<T>>::insert(&code_hash, code);
|
||||
}
|
||||
<CodeStorage<T>>::mutate(&code_hash, |existing| {
|
||||
match existing {
|
||||
Some(module) => increment_64(&mut module.refcount),
|
||||
None => {
|
||||
*existing = Some(prefab_module);
|
||||
Contracts::<T>::deposit_event(Event::CodeStored(code_hash))
|
||||
}
|
||||
}
|
||||
<CodeStorage<T>>::mutate(&code_hash, |existing| match existing {
|
||||
Some(module) => increment_64(&mut module.refcount),
|
||||
None => {
|
||||
*existing = Some(prefab_module);
|
||||
Contracts::<T>::deposit_event(Event::CodeStored(code_hash))
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ where
|
||||
/// Removes the code instead of storing it when the refcount drops to zero.
|
||||
pub fn store_decremented<T: Config>(mut prefab_module: PrefabWasmModule<T>)
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
prefab_module.refcount = prefab_module.refcount.saturating_sub(1);
|
||||
if prefab_module.refcount > 0 {
|
||||
@@ -81,10 +80,12 @@ where
|
||||
}
|
||||
|
||||
/// Increment the refcount of a code in-storage by one.
|
||||
pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
|
||||
-> Result<(), DispatchError>
|
||||
pub fn increment_refcount<T: Config>(
|
||||
code_hash: CodeHash<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<(), DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
gas_meter.charge(CodeToken::UpdateRefcount(estimate_code_size::<T>(&code_hash)?))?;
|
||||
<CodeStorage<T>>::mutate(code_hash, |existing| {
|
||||
@@ -98,10 +99,12 @@ where
|
||||
}
|
||||
|
||||
/// Decrement the refcount of a code in-storage by one and remove the code when it drops to zero.
|
||||
pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
|
||||
-> Result<(), DispatchError>
|
||||
pub fn decrement_refcount<T: Config>(
|
||||
code_hash: CodeHash<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<(), DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
if let Ok(len) = estimate_code_size::<T>(&code_hash) {
|
||||
gas_meter.charge(CodeToken::UpdateRefcount(len))?;
|
||||
@@ -133,7 +136,7 @@ pub fn load<T: Config>(
|
||||
mut reinstrument: Option<(&Schedule<T>, &mut GasMeter<T>)>,
|
||||
) -> Result<PrefabWasmModule<T>, DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
// The reinstrument case coincides with the cases where we need to charge extra
|
||||
// based upon the code size: On-chain execution.
|
||||
@@ -141,8 +144,8 @@ where
|
||||
gas_meter.charge(CodeToken::Load(estimate_code_size::<T>(&code_hash)?))?;
|
||||
}
|
||||
|
||||
let mut prefab_module = <CodeStorage<T>>::get(code_hash)
|
||||
.ok_or_else(|| Error::<T>::CodeNotFound)?;
|
||||
let mut prefab_module =
|
||||
<CodeStorage<T>>::get(code_hash).ok_or_else(|| Error::<T>::CodeNotFound)?;
|
||||
prefab_module.code_hash = code_hash;
|
||||
|
||||
if let Some((schedule, gas_meter)) = reinstrument {
|
||||
@@ -165,7 +168,7 @@ mod private {
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<(), DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
let original_code = <PristineCode<T>>::get(&prefab_module.code_hash)
|
||||
.ok_or_else(|| Error::<T>::CodeNotFound)?;
|
||||
@@ -179,7 +182,7 @@ mod private {
|
||||
/// Finish removal of a code by deleting the pristine code and emitting an event.
|
||||
fn finish_removal<T: Config>(code_hash: CodeHash<T>)
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
<PristineCode<T>>::remove(code_hash);
|
||||
Contracts::<T>::deposit_event(Event::CodeRemoved(code_hash))
|
||||
@@ -190,13 +193,15 @@ where
|
||||
/// We try hard to be infallible here because otherwise more storage transactions would be
|
||||
/// necessary to account for failures in storing code for an already instantiated contract.
|
||||
fn increment_64(refcount: &mut u64) {
|
||||
*refcount = refcount.checked_add(1).expect("
|
||||
*refcount = refcount.checked_add(1).expect(
|
||||
"
|
||||
refcount is 64bit. Generating this overflow would require to store
|
||||
_at least_ 18 exabyte of data assuming that a contract consumes only
|
||||
one byte of data. Any node would run out of storage space before hitting
|
||||
this overflow.
|
||||
qed
|
||||
");
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the size of the instrumented code stored at `code_hash` without loading it.
|
||||
@@ -206,7 +211,7 @@ fn increment_64(refcount: &mut u64) {
|
||||
/// compared to the code size. Additionally, charging too much weight is completely safe.
|
||||
fn estimate_code_size<T: Config>(code_hash: &CodeHash<T>) -> Result<u32, DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
let key = <CodeStorage<T>>::hashed_key_for(code_hash);
|
||||
let mut data = [0u8; 0];
|
||||
@@ -229,7 +234,7 @@ enum CodeToken {
|
||||
impl<T> Token<T> for CodeToken
|
||||
where
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
fn weight(&self) -> Weight {
|
||||
use self::CodeToken::*;
|
||||
@@ -240,9 +245,10 @@ where
|
||||
// the contract.
|
||||
match *self {
|
||||
Instrument(len) => T::WeightInfo::instrument(len / 1024),
|
||||
Load(len) => T::WeightInfo::code_load(len / 1024).saturating_sub(T::WeightInfo::code_load(0)),
|
||||
UpdateRefcount(len) =>
|
||||
T::WeightInfo::code_refcount(len / 1024).saturating_sub(T::WeightInfo::code_refcount(0)),
|
||||
Load(len) =>
|
||||
T::WeightInfo::code_load(len / 1024).saturating_sub(T::WeightInfo::code_load(0)),
|
||||
UpdateRefcount(len) => T::WeightInfo::code_refcount(len / 1024)
|
||||
.saturating_sub(T::WeightInfo::code_refcount(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,14 +255,14 @@ macro_rules! define_env {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
exec::Ext,
|
||||
wasm::{runtime::TrapReason, tests::MockExt, Runtime},
|
||||
Weight,
|
||||
};
|
||||
use pwasm_utils::parity_wasm::elements::{FunctionType, ValueType};
|
||||
use sp_runtime::traits::Zero;
|
||||
use sp_sandbox::{ReturnValue, Value};
|
||||
use crate::{
|
||||
Weight,
|
||||
wasm::{Runtime, runtime::TrapReason, tests::MockExt},
|
||||
exec::Ext,
|
||||
};
|
||||
|
||||
struct TestRuntime {
|
||||
value: u32,
|
||||
@@ -333,16 +333,15 @@ mod tests {
|
||||
Err(TrapReason::Termination)
|
||||
}
|
||||
});
|
||||
let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::Value])
|
||||
-> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> = seal_gas::<MockExt>;
|
||||
let _f: fn(
|
||||
&mut Runtime<MockExt>,
|
||||
&[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> = seal_gas::<MockExt>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_gen_signature() {
|
||||
assert_eq!(
|
||||
gen_signature!((i32)),
|
||||
FunctionType::new(vec![ValueType::I32], vec![]),
|
||||
);
|
||||
assert_eq!(gen_signature!((i32)), FunctionType::new(vec![ValueType::I32], vec![]),);
|
||||
|
||||
assert_eq!(
|
||||
gen_signature!( (i32, u32) -> u32 ),
|
||||
@@ -387,11 +386,11 @@ mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
assert!(
|
||||
Env::can_satisfy(b"seal0", b"seal_gas",&FunctionType::new(vec![ValueType::I32], vec![]))
|
||||
);
|
||||
assert!(
|
||||
!Env::can_satisfy(b"seal0", b"not_exists", &FunctionType::new(vec![], vec![]))
|
||||
);
|
||||
assert!(Env::can_satisfy(
|
||||
b"seal0",
|
||||
b"seal_gas",
|
||||
&FunctionType::new(vec![ValueType::I32], vec![])
|
||||
));
|
||||
assert!(!Env::can_satisfy(b"seal0", b"not_exists", &FunctionType::new(vec![], vec![])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
use super::Runtime;
|
||||
use crate::exec::Ext;
|
||||
|
||||
use sp_sandbox::Value;
|
||||
use pwasm_utils::parity_wasm::elements::{FunctionType, ValueType};
|
||||
use sp_sandbox::Value;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
@@ -67,11 +67,10 @@ impl ConvertibleToWasm for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
pub type HostFunc<E> =
|
||||
fn(
|
||||
&mut Runtime<E>,
|
||||
&[sp_sandbox::Value]
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>;
|
||||
pub type HostFunc<E> = fn(
|
||||
&mut Runtime<E>,
|
||||
&[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>;
|
||||
|
||||
pub trait FunctionImplProvider<E: Ext> {
|
||||
fn impls<F: FnMut(&[u8], &[u8], HostFunc<E>)>(f: &mut F);
|
||||
|
||||
@@ -24,19 +24,19 @@ mod code_cache;
|
||||
mod prepare;
|
||||
mod runtime;
|
||||
|
||||
use crate::{
|
||||
CodeHash, Schedule, Config,
|
||||
wasm::env_def::FunctionImplProvider,
|
||||
exec::{Ext, Executable, ExportedFunction, ExecResult},
|
||||
gas::GasMeter,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::dispatch::DispatchError;
|
||||
pub use self::runtime::{ReturnCode, Runtime, RuntimeCosts};
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use self::code_cache::reinstrument;
|
||||
pub use self::runtime::{ReturnCode, Runtime, RuntimeCosts};
|
||||
use crate::{
|
||||
exec::{ExecResult, Executable, ExportedFunction, Ext},
|
||||
gas::GasMeter,
|
||||
wasm::env_def::FunctionImplProvider,
|
||||
CodeHash, Config, Schedule,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::dispatch::DispatchError;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_std::prelude::*;
|
||||
#[cfg(test)]
|
||||
pub use tests::MockExt;
|
||||
|
||||
@@ -108,12 +108,12 @@ impl ExportedFunction {
|
||||
|
||||
impl<T: Config> PrefabWasmModule<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
/// Create the module by checking and instrumenting `original_code`.
|
||||
pub fn from_code(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
prepare::prepare_contract(original_code, schedule).map_err(Into::into)
|
||||
}
|
||||
@@ -127,7 +127,7 @@ where
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_code_unchecked(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let executable = prepare::benchmarking::prepare_contract(original_code, schedule)
|
||||
.map_err::<DispatchError, _>(Into::into)?;
|
||||
@@ -150,7 +150,7 @@ where
|
||||
|
||||
impl<T: Config> Executable<T> for PrefabWasmModule<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
fn from_storage(
|
||||
code_hash: CodeHash<T>,
|
||||
@@ -168,15 +168,14 @@ where
|
||||
code_cache::store_decremented(self);
|
||||
}
|
||||
|
||||
fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
|
||||
-> Result<(), DispatchError>
|
||||
{
|
||||
fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>) -> Result<(), DispatchError> {
|
||||
code_cache::increment_refcount::<T>(code_hash, gas_meter)
|
||||
}
|
||||
|
||||
fn remove_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
|
||||
-> Result<(), DispatchError>
|
||||
{
|
||||
fn remove_user(
|
||||
code_hash: CodeHash<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
code_cache::decrement_refcount::<T>(code_hash, gas_meter)
|
||||
}
|
||||
|
||||
@@ -187,16 +186,15 @@ where
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
let memory =
|
||||
sp_sandbox::Memory::new(self.initial, Some(self.maximum))
|
||||
.unwrap_or_else(|_| {
|
||||
sp_sandbox::Memory::new(self.initial, Some(self.maximum)).unwrap_or_else(|_| {
|
||||
// unlike `.expect`, explicit panic preserves the source location.
|
||||
// Needed as we can't use `RUST_BACKTRACE` in here.
|
||||
panic!(
|
||||
"exec.prefab_module.initial can't be greater than exec.prefab_module.maximum;
|
||||
panic!(
|
||||
"exec.prefab_module.initial can't be greater than exec.prefab_module.maximum;
|
||||
thus Memory::new must not fail;
|
||||
qed"
|
||||
)
|
||||
});
|
||||
)
|
||||
});
|
||||
|
||||
let mut imports = sp_sandbox::EnvironmentDefinitionBuilder::new();
|
||||
imports.add_memory(self::prepare::IMPORT_MODULE_MEMORY, "memory", memory.clone());
|
||||
@@ -204,11 +202,7 @@ where
|
||||
imports.add_host_func(module, name, func_ptr);
|
||||
});
|
||||
|
||||
let mut runtime = Runtime::new(
|
||||
ext,
|
||||
input_data,
|
||||
memory,
|
||||
);
|
||||
let mut runtime = Runtime::new(ext, input_data, memory);
|
||||
|
||||
// We store before executing so that the code hash is available in the constructor.
|
||||
let code = self.code.clone();
|
||||
@@ -245,31 +239,27 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
CodeHash, BalanceOf, Error, Pallet as Contracts,
|
||||
exec::{
|
||||
Ext, StorageKey, AccountIdOf, Executable, SeedOf, BlockNumberOf,
|
||||
RentParams, ExecError, ErrorOrigin,
|
||||
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, RentParams,
|
||||
SeedOf, StorageKey,
|
||||
},
|
||||
gas::GasMeter,
|
||||
rent::RentStatus,
|
||||
tests::{Test, Call, ALICE, BOB},
|
||||
tests::{Call, Test, ALICE, BOB},
|
||||
BalanceOf, CodeHash, Error, Pallet as Contracts,
|
||||
};
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
};
|
||||
use sp_core::{Bytes, H256};
|
||||
use hex_literal::hex;
|
||||
use sp_runtime::DispatchError;
|
||||
use assert_matches::assert_matches;
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
dispatch::{DispatchResult, DispatchResultWithPostInfo},
|
||||
weights::Weight,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use hex_literal::hex;
|
||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sp_core::{Bytes, H256};
|
||||
use sp_runtime::DispatchError;
|
||||
use std::{borrow::BorrowMut, cell::RefCell, collections::HashMap};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct RestoreEntry {
|
||||
@@ -360,12 +350,7 @@ mod tests {
|
||||
data: Vec<u8>,
|
||||
allows_reentry: bool,
|
||||
) -> Result<ExecReturnValue, ExecError> {
|
||||
self.calls.push(CallEntry {
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
allows_reentry,
|
||||
});
|
||||
self.calls.push(CallEntry { to, value, data, allows_reentry });
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() })
|
||||
}
|
||||
fn instantiate(
|
||||
@@ -385,30 +370,15 @@ mod tests {
|
||||
});
|
||||
Ok((
|
||||
Contracts::<Test>::contract_address(&ALICE, &code_hash, salt),
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(Vec::new()),
|
||||
},
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) },
|
||||
))
|
||||
}
|
||||
fn transfer(
|
||||
&mut self,
|
||||
to: &AccountIdOf<Self::T>,
|
||||
value: u64,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.transfers.push(TransferEntry {
|
||||
to: to.clone(),
|
||||
value,
|
||||
});
|
||||
fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: u64) -> Result<(), DispatchError> {
|
||||
self.transfers.push(TransferEntry { to: to.clone(), value });
|
||||
Ok(())
|
||||
}
|
||||
fn terminate(
|
||||
&mut self,
|
||||
beneficiary: &AccountIdOf<Self::T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.terminations.push(TerminationEntry {
|
||||
beneficiary: beneficiary.clone(),
|
||||
});
|
||||
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
|
||||
self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() });
|
||||
Ok(())
|
||||
}
|
||||
fn restore_to(
|
||||
@@ -418,12 +388,7 @@ mod tests {
|
||||
rent_allowance: u64,
|
||||
delta: Vec<StorageKey>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.restores.push(RestoreEntry {
|
||||
dest,
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
delta,
|
||||
});
|
||||
self.restores.push(RestoreEntry { dest, code_hash, rent_allowance, delta });
|
||||
Ok(())
|
||||
}
|
||||
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
|
||||
@@ -466,8 +431,12 @@ mod tests {
|
||||
fn rent_allowance(&mut self) -> u64 {
|
||||
self.rent_allowance
|
||||
}
|
||||
fn block_number(&self) -> u64 { 121 }
|
||||
fn max_value_size(&self) -> u32 { 16_384 }
|
||||
fn block_number(&self) -> u64 {
|
||||
121
|
||||
}
|
||||
fn max_value_size(&self) -> u32 {
|
||||
16_384
|
||||
}
|
||||
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
|
||||
BalanceOf::<Self::T>::from(1312_u32).saturating_mul(weight.into())
|
||||
}
|
||||
@@ -493,16 +462,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<E: BorrowMut<MockExt>>(
|
||||
wat: &str,
|
||||
input_data: Vec<u8>,
|
||||
mut ext: E,
|
||||
) -> ExecResult
|
||||
{
|
||||
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let executable = PrefabWasmModule::<<MockExt as Ext>::T>::from_code(wasm, &schedule)
|
||||
.unwrap();
|
||||
let executable =
|
||||
PrefabWasmModule::<<MockExt as Ext>::T>::from_code(wasm, &schedule).unwrap();
|
||||
executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data)
|
||||
}
|
||||
|
||||
@@ -543,19 +507,9 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(
|
||||
CODE_TRANSFER,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
));
|
||||
assert_ok!(execute(CODE_TRANSFER, vec![], &mut mock_ext,));
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.transfers,
|
||||
&[TransferEntry {
|
||||
to: ALICE,
|
||||
value: 153,
|
||||
}]
|
||||
);
|
||||
assert_eq!(&mock_ext.transfers, &[TransferEntry { to: ALICE, value: 153 }]);
|
||||
}
|
||||
|
||||
const CODE_CALL: &str = r#"
|
||||
@@ -607,20 +561,11 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_call() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(
|
||||
CODE_CALL,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
));
|
||||
assert_ok!(execute(CODE_CALL, vec![], &mut mock_ext,));
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.calls,
|
||||
&[CallEntry {
|
||||
to: ALICE,
|
||||
value: 6,
|
||||
data: vec![1, 2, 3, 4],
|
||||
allows_reentry: true,
|
||||
}]
|
||||
&[CallEntry { to: ALICE, value: 6, data: vec![1, 2, 3, 4], allows_reentry: true }]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -675,12 +620,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.calls,
|
||||
&[CallEntry {
|
||||
to: ALICE,
|
||||
value: 0x2a,
|
||||
data: input,
|
||||
allows_reentry: false,
|
||||
}]
|
||||
&[CallEntry { to: ALICE, value: 0x2a, data: input, allows_reentry: false }]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -736,12 +676,7 @@ mod tests {
|
||||
assert_eq!(result.data.0, input);
|
||||
assert_eq!(
|
||||
&mock_ext.calls,
|
||||
&[CallEntry {
|
||||
to: ALICE,
|
||||
value: 0x2a,
|
||||
data: input,
|
||||
allows_reentry: true,
|
||||
}]
|
||||
&[CallEntry { to: ALICE, value: 0x2a, data: input, allows_reentry: true }]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -789,12 +724,7 @@ mod tests {
|
||||
assert_eq!(result.data, call_return_data());
|
||||
assert_eq!(
|
||||
&mock_ext.calls,
|
||||
&[CallEntry {
|
||||
to: ALICE,
|
||||
value: 0x2a,
|
||||
data: input,
|
||||
allows_reentry: false,
|
||||
}]
|
||||
&[CallEntry { to: ALICE, value: 0x2a, data: input, allows_reentry: false }]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -857,11 +787,7 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_instantiate() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(
|
||||
CODE_INSTANTIATE,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
));
|
||||
assert_ok!(execute(CODE_INSTANTIATE, vec![], &mut mock_ext,));
|
||||
|
||||
assert_matches!(
|
||||
&mock_ext.instantiates[..],
|
||||
@@ -905,18 +831,9 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_terminate() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
CODE_TERMINATE,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
).unwrap();
|
||||
execute(CODE_TERMINATE, vec![], &mut mock_ext).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.terminations,
|
||||
&[TerminationEntry {
|
||||
beneficiary: ALICE,
|
||||
}]
|
||||
);
|
||||
assert_eq!(&mock_ext.terminations, &[TerminationEntry { beneficiary: ALICE }]);
|
||||
}
|
||||
|
||||
const CODE_TRANSFER_LIMITED_GAS: &str = r#"
|
||||
@@ -967,20 +884,11 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_call_limited_gas() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(
|
||||
&CODE_TRANSFER_LIMITED_GAS,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
));
|
||||
assert_ok!(execute(&CODE_TRANSFER_LIMITED_GAS, vec![], &mut mock_ext,));
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.calls,
|
||||
&[CallEntry {
|
||||
to: ALICE,
|
||||
value: 6,
|
||||
data: vec![1, 2, 3, 4],
|
||||
allows_reentry: true,
|
||||
}]
|
||||
&[CallEntry { to: ALICE, value: 6, data: vec![1, 2, 3, 4], allows_reentry: true }]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1051,20 +959,14 @@ mod tests {
|
||||
#[test]
|
||||
fn get_storage_puts_data_into_buf() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
mock_ext
|
||||
.storage
|
||||
.insert([0x11; 32], [0x22; 32].to_vec());
|
||||
mock_ext.storage.insert([0x11; 32], [0x22; 32].to_vec());
|
||||
|
||||
let output = execute(
|
||||
CODE_GET_STORAGE,
|
||||
vec![],
|
||||
mock_ext,
|
||||
).unwrap();
|
||||
let output = execute(CODE_GET_STORAGE, vec![], mock_ext).unwrap();
|
||||
|
||||
assert_eq!(output, ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes([0x22; 32].to_vec())
|
||||
});
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes([0x22; 32].to_vec()) }
|
||||
);
|
||||
}
|
||||
|
||||
/// calls `seal_caller` and compares the result with the constant 42.
|
||||
@@ -1112,11 +1014,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn caller() {
|
||||
assert_ok!(execute(
|
||||
CODE_CALLER,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_CALLER, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
/// calls `seal_address` and compares the result with the constant 69.
|
||||
@@ -1164,11 +1062,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
assert_ok!(execute(
|
||||
CODE_ADDRESS,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_ADDRESS, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_BALANCE: &str = r#"
|
||||
@@ -1214,11 +1108,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn balance() {
|
||||
assert_ok!(execute(
|
||||
CODE_BALANCE,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_BALANCE, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_GAS_PRICE: &str = r#"
|
||||
@@ -1264,11 +1154,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn gas_price() {
|
||||
assert_ok!(execute(
|
||||
CODE_GAS_PRICE,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_GAS_PRICE, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_GAS_LEFT: &str = r#"
|
||||
@@ -1315,11 +1201,7 @@ mod tests {
|
||||
let mut ext = MockExt::default();
|
||||
let gas_limit = ext.gas_meter.gas_left();
|
||||
|
||||
let output = execute(
|
||||
CODE_GAS_LEFT,
|
||||
vec![],
|
||||
&mut ext,
|
||||
).unwrap();
|
||||
let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap();
|
||||
|
||||
let gas_left = Weight::decode(&mut &*output.data).unwrap();
|
||||
let actual_left = ext.gas_meter.gas_left();
|
||||
@@ -1370,11 +1252,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn value_transferred() {
|
||||
assert_ok!(execute(
|
||||
CODE_VALUE_TRANSFERRED,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_VALUE_TRANSFERRED, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_RETURN_FROM_START_FN: &str = r#"
|
||||
@@ -1403,18 +1281,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn return_from_start_fn() {
|
||||
let output = execute(
|
||||
CODE_RETURN_FROM_START_FN,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output = execute(CODE_RETURN_FROM_START_FN, vec![], MockExt::default()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(vec![1, 2, 3, 4])
|
||||
}
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![1, 2, 3, 4]) }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1461,11 +1332,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn now() {
|
||||
assert_ok!(execute(
|
||||
CODE_TIMESTAMP_NOW,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_TIMESTAMP_NOW, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_MINIMUM_BALANCE: &str = r#"
|
||||
@@ -1510,11 +1377,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn minimum_balance() {
|
||||
assert_ok!(execute(
|
||||
CODE_MINIMUM_BALANCE,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_TOMBSTONE_DEPOSIT: &str = r#"
|
||||
@@ -1559,11 +1422,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tombstone_deposit() {
|
||||
assert_ok!(execute(
|
||||
CODE_TOMBSTONE_DEPOSIT,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
));
|
||||
assert_ok!(execute(CODE_TOMBSTONE_DEPOSIT, vec![], MockExt::default(),));
|
||||
}
|
||||
|
||||
const CODE_RANDOM: &str = r#"
|
||||
@@ -1622,11 +1481,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn random() {
|
||||
let output = execute(
|
||||
CODE_RANDOM,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output = execute(CODE_RANDOM, vec![], MockExt::default()).unwrap();
|
||||
|
||||
// The mock ext just returns the same data that was passed as the subject.
|
||||
assert_eq!(
|
||||
@@ -1697,26 +1552,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn random_v1() {
|
||||
let output = execute(
|
||||
CODE_RANDOM_V1,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output = execute(CODE_RANDOM_V1, vec![], MockExt::default()).unwrap();
|
||||
|
||||
// The mock ext just returns the same data that was passed as the subject.
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes((
|
||||
data: Bytes(
|
||||
(
|
||||
hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"),
|
||||
42u64,
|
||||
).encode()),
|
||||
)
|
||||
.encode()
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const CODE_DEPOSIT_EVENT: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32)))
|
||||
@@ -1743,16 +1596,15 @@ mod tests {
|
||||
#[test]
|
||||
fn deposit_event() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(
|
||||
CODE_DEPOSIT_EVENT,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
));
|
||||
assert_ok!(execute(CODE_DEPOSIT_EVENT, vec![], &mut mock_ext,));
|
||||
|
||||
assert_eq!(mock_ext.events, vec![
|
||||
(vec![H256::repeat_byte(0x33)],
|
||||
vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00])
|
||||
]);
|
||||
assert_eq!(
|
||||
mock_ext.events,
|
||||
vec![(
|
||||
vec![H256::repeat_byte(0x33)],
|
||||
vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00]
|
||||
)]
|
||||
);
|
||||
|
||||
assert!(mock_ext.gas_meter.gas_left() > 0);
|
||||
}
|
||||
@@ -1788,11 +1640,7 @@ mod tests {
|
||||
#[test]
|
||||
fn deposit_event_max_topics() {
|
||||
assert_eq!(
|
||||
execute(
|
||||
CODE_DEPOSIT_EVENT_MAX_TOPICS,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
),
|
||||
execute(CODE_DEPOSIT_EVENT_MAX_TOPICS, vec![], MockExt::default(),),
|
||||
Err(ExecError {
|
||||
error: Error::<Test>::TooManyTopics.into(),
|
||||
origin: ErrorOrigin::Caller,
|
||||
@@ -1830,11 +1678,7 @@ mod tests {
|
||||
#[test]
|
||||
fn deposit_event_duplicates() {
|
||||
assert_eq!(
|
||||
execute(
|
||||
CODE_DEPOSIT_EVENT_DUPLICATES,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
),
|
||||
execute(CODE_DEPOSIT_EVENT_DUPLICATES, vec![], MockExt::default(),),
|
||||
Err(ExecError {
|
||||
error: Error::<Test>::DuplicateTopics.into(),
|
||||
origin: ErrorOrigin::Caller,
|
||||
@@ -1887,11 +1731,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn block_number() {
|
||||
let _ = execute(
|
||||
CODE_BLOCK_NUMBER,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let _ = execute(CODE_BLOCK_NUMBER, vec![], MockExt::default()).unwrap();
|
||||
}
|
||||
|
||||
const CODE_RETURN_WITH_DATA: &str = r#"
|
||||
@@ -1932,27 +1772,32 @@ mod tests {
|
||||
CODE_RETURN_WITH_DATA,
|
||||
hex!("00000000445566778899").to_vec(),
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(hex!("445566778899").to_vec()),
|
||||
});
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(hex!("445566778899").to_vec()),
|
||||
}
|
||||
);
|
||||
assert!(output.is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_with_revert_status() {
|
||||
let output = execute(
|
||||
CODE_RETURN_WITH_DATA,
|
||||
hex!("010000005566778899").to_vec(),
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output =
|
||||
execute(CODE_RETURN_WITH_DATA, hex!("010000005566778899").to_vec(), MockExt::default())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, ExecReturnValue {
|
||||
flags: ReturnFlags::REVERT,
|
||||
data: Bytes(hex!("5566778899").to_vec()),
|
||||
});
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::REVERT,
|
||||
data: Bytes(hex!("5566778899").to_vec()),
|
||||
}
|
||||
);
|
||||
assert!(!output.is_success());
|
||||
}
|
||||
|
||||
@@ -1975,11 +1820,7 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_out_of_bounds_access() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_OUT_OF_BOUNDS_ACCESS,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
);
|
||||
let result = execute(CODE_OUT_OF_BOUNDS_ACCESS, vec![], &mut mock_ext);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
@@ -2009,11 +1850,7 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_decode_length_ignored() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_DECODE_FAILURE,
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
);
|
||||
let result = execute(CODE_DECODE_FAILURE, vec![], &mut mock_ext);
|
||||
// AccountID implements `MaxEncodeLen` and therefore the supplied length is
|
||||
// no longer needed nor used to determine how much is read from contract memory.
|
||||
assert_ok!(result);
|
||||
@@ -2051,17 +1888,11 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
let output = execute(
|
||||
CODE_RENT_PARAMS,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output = execute(CODE_RENT_PARAMS, vec![], MockExt::default()).unwrap();
|
||||
let rent_params = Bytes(<RentParams<Test>>::default().encode());
|
||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn rent_status_works() {
|
||||
@@ -2095,11 +1926,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
let output = execute(
|
||||
CODE_RENT_STATUS,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let output = execute(CODE_RENT_STATUS, vec![], MockExt::default()).unwrap();
|
||||
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
|
||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
|
||||
}
|
||||
@@ -2126,11 +1953,7 @@ mod tests {
|
||||
)
|
||||
"#;
|
||||
let mut ext = MockExt::default();
|
||||
execute(
|
||||
CODE_DEBUG_MESSAGE,
|
||||
vec![],
|
||||
&mut ext,
|
||||
).unwrap();
|
||||
execute(CODE_DEBUG_MESSAGE, vec![], &mut ext).unwrap();
|
||||
|
||||
assert_eq!(std::str::from_utf8(&ext.debug_buffer).unwrap(), "Hello World!");
|
||||
}
|
||||
@@ -2157,11 +1980,7 @@ mod tests {
|
||||
)
|
||||
"#;
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_DEBUG_MESSAGE_FAIL,
|
||||
vec![],
|
||||
&mut ext,
|
||||
);
|
||||
let result = execute(CODE_DEBUG_MESSAGE_FAIL, vec![], &mut ext);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ExecError {
|
||||
@@ -2213,15 +2032,8 @@ mod tests {
|
||||
use std::convert::TryInto;
|
||||
let call = Call::System(frame_system::Call::remark(b"Hello World".to_vec()));
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_CALL_RUNTIME,
|
||||
call.encode(),
|
||||
&mut ext,
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
*ext.runtime_calls.borrow(),
|
||||
vec![call],
|
||||
);
|
||||
let result = execute(CODE_CALL_RUNTIME, call.encode(), &mut ext).unwrap();
|
||||
assert_eq!(*ext.runtime_calls.borrow(), vec![call],);
|
||||
// 0 = ReturnCode::Success
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||
}
|
||||
@@ -2230,11 +2042,7 @@ mod tests {
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn call_runtime_panics_on_invalid_call() {
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_CALL_RUNTIME,
|
||||
vec![0x42],
|
||||
&mut ext,
|
||||
);
|
||||
let result = execute(CODE_CALL_RUNTIME, vec![0x42], &mut ext);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ExecError {
|
||||
@@ -2242,9 +2050,6 @@ mod tests {
|
||||
origin: ErrorOrigin::Caller,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
*ext.runtime_calls.borrow(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(*ext.runtime_calls.borrow(), vec![],);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
//! from a module.
|
||||
|
||||
use crate::{
|
||||
Schedule, Config,
|
||||
chain_extension::ChainExtension,
|
||||
wasm::{PrefabWasmModule, env_def::ImportSatisfyCheck},
|
||||
wasm::{env_def::ImportSatisfyCheck, PrefabWasmModule},
|
||||
Config, Schedule,
|
||||
};
|
||||
use pwasm_utils::parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType};
|
||||
use pwasm_utils::parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType};
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
@@ -43,10 +43,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
///
|
||||
/// Returns `Err` if the `original_code` couldn't be decoded or
|
||||
/// if it contains an invalid module.
|
||||
fn new(
|
||||
original_code: &[u8],
|
||||
schedule: &'a Schedule<T>,
|
||||
) -> Result<Self, &'static str> {
|
||||
fn new(original_code: &[u8], schedule: &'a Schedule<T>) -> Result<Self, &'static str> {
|
||||
use wasmi_validation::{validate_module, PlainValidator};
|
||||
|
||||
let module =
|
||||
@@ -57,10 +54,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
// Return a `ContractModule` instance with
|
||||
// __valid__ module.
|
||||
Ok(ContractModule {
|
||||
module,
|
||||
schedule,
|
||||
})
|
||||
Ok(ContractModule { module, schedule })
|
||||
}
|
||||
|
||||
/// Ensures that module doesn't declare internal memories.
|
||||
@@ -69,11 +63,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
/// Memory section contains declarations of internal linear memories, so if we find one
|
||||
/// we reject such a module.
|
||||
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
|
||||
if self.module
|
||||
.memory_section()
|
||||
.map_or(false, |ms| ms.entries().len() > 0)
|
||||
{
|
||||
return Err("module declares internal memory");
|
||||
if self.module.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
|
||||
return Err("module declares internal memory")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -84,7 +75,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
// In Wasm MVP spec, there may be at most one table declared. Double check this
|
||||
// explicitly just in case the Wasm version changes.
|
||||
if table_section.entries().len() > 1 {
|
||||
return Err("multiple tables declared");
|
||||
return Err("multiple tables declared")
|
||||
}
|
||||
if let Some(table_type) = table_section.entries().first() {
|
||||
// Check the table's initial size as there is no instruction or environment function
|
||||
@@ -102,7 +93,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
let code_section = if let Some(type_section) = self.module.code_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
};
|
||||
for instr in code_section.bodies().iter().flat_map(|body| body.code().elements()) {
|
||||
use self::elements::Instruction::BrTable;
|
||||
@@ -131,7 +122,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
match global.global_type().content_type() {
|
||||
ValueType::F32 | ValueType::F64 =>
|
||||
return Err("use of floating point type in globals is forbidden"),
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +133,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
match local.value_type() {
|
||||
ValueType::F32 | ValueType::F64 =>
|
||||
return Err("use of floating point type in locals is forbidden"),
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,11 +147,13 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
for value_type in func_type.params().iter().chain(return_type) {
|
||||
match value_type {
|
||||
ValueType::F32 | ValueType::F64 =>
|
||||
return Err("use of floating point type in function types is forbidden"),
|
||||
_ => {}
|
||||
return Err(
|
||||
"use of floating point type in function types is forbidden",
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,12 +166,12 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
let type_section = if let Some(type_section) = self.module.type_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
for Type::Function(func) in type_section.types() {
|
||||
if func.params().len() > limit as usize {
|
||||
return Err("Use of a function type with too many parameters.");
|
||||
return Err("Use of a function type with too many parameters.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,26 +180,18 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module);
|
||||
let contract_module = pwasm_utils::inject_gas_counter(
|
||||
self.module,
|
||||
&gas_rules,
|
||||
"seal0",
|
||||
).map_err(|_| "gas instrumentation failed")?;
|
||||
Ok(ContractModule {
|
||||
module: contract_module,
|
||||
schedule: self.schedule,
|
||||
})
|
||||
let contract_module = pwasm_utils::inject_gas_counter(self.module, &gas_rules, "seal0")
|
||||
.map_err(|_| "gas instrumentation failed")?;
|
||||
Ok(ContractModule { module: contract_module, schedule: self.schedule })
|
||||
}
|
||||
|
||||
fn inject_stack_height_metering(self) -> Result<Self, &'static str> {
|
||||
let contract_module =
|
||||
pwasm_utils::stack_height
|
||||
::inject_limiter(self.module, self.schedule.limits.stack_height)
|
||||
.map_err(|_| "stack height instrumentation failed")?;
|
||||
Ok(ContractModule {
|
||||
module: contract_module,
|
||||
schedule: self.schedule,
|
||||
})
|
||||
let contract_module = pwasm_utils::stack_height::inject_limiter(
|
||||
self.module,
|
||||
self.schedule.limits.stack_height,
|
||||
)
|
||||
.map_err(|_| "stack height instrumentation failed")?;
|
||||
Ok(ContractModule { module: contract_module, schedule: self.schedule })
|
||||
}
|
||||
|
||||
/// Check that the module has required exported functions. For now
|
||||
@@ -223,14 +208,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
let module = &self.module;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let export_entries = module
|
||||
.export_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[]);
|
||||
let func_entries = module
|
||||
.function_section()
|
||||
.map(|fs| fs.entries())
|
||||
.unwrap_or(&[]);
|
||||
let export_entries = module.export_section().map(|is| is.entries()).unwrap_or(&[]);
|
||||
let func_entries = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]);
|
||||
|
||||
// Function index space consists of imported function following by
|
||||
// declared functions. Calculate the total number of imported functions so
|
||||
@@ -240,11 +219,9 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
match *entry.external() {
|
||||
External::Function(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
.filter(|entry| match *entry.external() {
|
||||
External::Function(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.count();
|
||||
|
||||
@@ -267,32 +244,32 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
Some(fn_idx) => fn_idx,
|
||||
None => {
|
||||
// Underflow here means fn_idx points to imported function which we don't allow!
|
||||
return Err("entry point points to an imported function");
|
||||
}
|
||||
return Err("entry point points to an imported function")
|
||||
},
|
||||
};
|
||||
|
||||
// Then check the signature.
|
||||
// Both "call" and "deploy" has a () -> () function type.
|
||||
// We still support () -> (i32) for backwards compatibility.
|
||||
let func_ty_idx = func_entries.get(fn_idx as usize)
|
||||
let func_ty_idx = func_entries
|
||||
.get(fn_idx as usize)
|
||||
.ok_or_else(|| "export refers to non-existent function")?
|
||||
.type_ref();
|
||||
let Type::Function(ref func_ty) = types
|
||||
.get(func_ty_idx as usize)
|
||||
.ok_or_else(|| "function has a non-existent type")?;
|
||||
if !(
|
||||
func_ty.params().is_empty() &&
|
||||
(func_ty.results().is_empty() || func_ty.results() == [ValueType::I32])
|
||||
) {
|
||||
return Err("entry point has wrong signature");
|
||||
if !(func_ty.params().is_empty() &&
|
||||
(func_ty.results().is_empty() || func_ty.results() == [ValueType::I32]))
|
||||
{
|
||||
return Err("entry point has wrong signature")
|
||||
}
|
||||
}
|
||||
|
||||
if !deploy_found {
|
||||
return Err("deploy function isn't exported");
|
||||
return Err("deploy function isn't exported")
|
||||
}
|
||||
if !call_found {
|
||||
return Err("call function isn't exported");
|
||||
return Err("call function isn't exported")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -306,16 +283,14 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
/// their signatures.
|
||||
/// - if there is a memory import, returns it's descriptor
|
||||
/// `import_fn_banlist`: list of function names that are disallowed to be imported
|
||||
fn scan_imports<C: ImportSatisfyCheck>(&self, import_fn_banlist: &[&[u8]])
|
||||
-> Result<Option<&MemoryType>, &'static str>
|
||||
{
|
||||
fn scan_imports<C: ImportSatisfyCheck>(
|
||||
&self,
|
||||
import_fn_banlist: &[&[u8]],
|
||||
) -> Result<Option<&MemoryType>, &'static str> {
|
||||
let module = &self.module;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let import_entries = module
|
||||
.import_section()
|
||||
.map(|is| is.entries())
|
||||
.unwrap_or(&[]);
|
||||
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
|
||||
|
||||
let mut imported_mem_type = None;
|
||||
|
||||
@@ -326,7 +301,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
&External::Function(ref type_idx) => type_idx,
|
||||
&External::Memory(ref memory_type) => {
|
||||
if import.module() != IMPORT_MODULE_MEMORY {
|
||||
return Err("Invalid module for imported memory");
|
||||
return Err("Invalid module for imported memory")
|
||||
}
|
||||
if import.field() != "memory" {
|
||||
return Err("Memory import must have the field name 'memory'")
|
||||
@@ -335,8 +310,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
return Err("Multiple memory imports defined")
|
||||
}
|
||||
imported_mem_type = Some(memory_type);
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
let Type::Function(ref func_ty) = types
|
||||
@@ -346,48 +321,44 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
if !T::ChainExtension::enabled() &&
|
||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
||||
{
|
||||
return Err("module uses chain extensions but chain extensions are disabled");
|
||||
return Err("module uses chain extensions but chain extensions are disabled")
|
||||
}
|
||||
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f)
|
||||
|| !C::can_satisfy(
|
||||
import.module().as_bytes(), import.field().as_bytes(), func_ty,
|
||||
)
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) ||
|
||||
!C::can_satisfy(import.module().as_bytes(), import.field().as_bytes(), func_ty)
|
||||
{
|
||||
return Err("module imports a non-existent function");
|
||||
return Err("module imports a non-existent function")
|
||||
}
|
||||
}
|
||||
Ok(imported_mem_type)
|
||||
}
|
||||
|
||||
fn into_wasm_code(self) -> Result<Vec<u8>, &'static str> {
|
||||
elements::serialize(self.module)
|
||||
.map_err(|_| "error serializing instrumented module")
|
||||
elements::serialize(self.module).map_err(|_| "error serializing instrumented module")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_memory_limits<T: Config>(module: Option<&MemoryType>, schedule: &Schedule<T>)
|
||||
-> Result<(u32, u32), &'static str>
|
||||
{
|
||||
fn get_memory_limits<T: Config>(
|
||||
module: Option<&MemoryType>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<(u32, u32), &'static str> {
|
||||
if let Some(memory_type) = module {
|
||||
// Inspect the module to extract the initial and maximum page count.
|
||||
let limits = memory_type.limits();
|
||||
match (limits.initial(), limits.maximum()) {
|
||||
(initial, Some(maximum)) if initial > maximum => {
|
||||
(initial, Some(maximum)) if initial > maximum =>
|
||||
return Err(
|
||||
"Requested initial number of pages should not exceed the requested maximum",
|
||||
);
|
||||
}
|
||||
(_, Some(maximum)) if maximum > schedule.limits.memory_pages => {
|
||||
return Err("Maximum number of pages should not exceed the configured maximum.");
|
||||
}
|
||||
),
|
||||
(_, Some(maximum)) if maximum > schedule.limits.memory_pages =>
|
||||
return Err("Maximum number of pages should not exceed the configured maximum."),
|
||||
(initial, Some(maximum)) => Ok((initial, maximum)),
|
||||
(_, None) => {
|
||||
// Maximum number of pages should be always declared.
|
||||
// This isn't a hard requirement and can be treated as a maximum set
|
||||
// to configured maximum.
|
||||
return Err("Maximum number of pages should be always declared.");
|
||||
}
|
||||
return Err("Maximum number of pages should be always declared.")
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// If none memory imported then just crate an empty placeholder.
|
||||
@@ -411,10 +382,8 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits = get_memory_limits(
|
||||
contract_module.scan_imports::<C>(&disallowed_imports)?,
|
||||
schedule
|
||||
)?;
|
||||
let memory_limits =
|
||||
get_memory_limits(contract_module.scan_imports::<C>(&disallowed_imports)?, schedule)?;
|
||||
|
||||
let code = contract_module
|
||||
.inject_gas_metering()?
|
||||
@@ -428,10 +397,8 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<PrefabWasmModule<T>, &'static str> {
|
||||
let (code, (initial, maximum)) = check_and_instrument::<C, T>(
|
||||
original_code.as_ref(),
|
||||
schedule,
|
||||
)?;
|
||||
let (code, (initial, maximum)) =
|
||||
check_and_instrument::<C, T>(original_code.as_ref(), schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial,
|
||||
@@ -483,8 +450,7 @@ pub fn reinstrument_contract<T: Config>(
|
||||
/// in production code.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking {
|
||||
use super::*;
|
||||
use super::elements::FunctionType;
|
||||
use super::{elements::FunctionType, *};
|
||||
|
||||
impl ImportSatisfyCheck for () {
|
||||
fn can_satisfy(_module: &[u8], _name: &[u8], _func_type: &FunctionType) -> bool {
|
||||
@@ -493,9 +459,10 @@ pub mod benchmarking {
|
||||
}
|
||||
|
||||
/// Prepare function that neither checks nor instruments the passed in code.
|
||||
pub fn prepare_contract<T: Config>(original_code: Vec<u8>, schedule: &Schedule<T>)
|
||||
-> Result<PrefabWasmModule<T>, &'static str>
|
||||
{
|
||||
pub fn prepare_contract<T: Config>(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<PrefabWasmModule<T>, &'static str> {
|
||||
let contract_module = ContractModule::new(&original_code, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
@@ -566,7 +533,8 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
prepare_test!(no_floats,
|
||||
prepare_test!(
|
||||
no_floats,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
@@ -585,7 +553,8 @@ mod tests {
|
||||
mod functions {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(param_number_valid,
|
||||
prepare_test!(
|
||||
param_number_valid,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -596,7 +565,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(param_number_invalid,
|
||||
prepare_test!(
|
||||
param_number_invalid,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -612,7 +582,8 @@ mod tests {
|
||||
mod globals {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(global_number_valid,
|
||||
prepare_test!(
|
||||
global_number_valid,
|
||||
r#"
|
||||
(module
|
||||
(global i64 (i64.const 0))
|
||||
@@ -625,7 +596,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(global_number_too_high,
|
||||
prepare_test!(
|
||||
global_number_too_high,
|
||||
r#"
|
||||
(module
|
||||
(global i64 (i64.const 0))
|
||||
@@ -643,7 +615,8 @@ mod tests {
|
||||
mod memories {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(memory_with_one_page,
|
||||
prepare_test!(
|
||||
memory_with_one_page,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 1))
|
||||
@@ -655,7 +628,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(internal_memory_declaration,
|
||||
prepare_test!(
|
||||
internal_memory_declaration,
|
||||
r#"
|
||||
(module
|
||||
(memory 1 1)
|
||||
@@ -667,7 +641,8 @@ mod tests {
|
||||
Err("module declares internal memory")
|
||||
);
|
||||
|
||||
prepare_test!(no_memory_import,
|
||||
prepare_test!(
|
||||
no_memory_import,
|
||||
r#"
|
||||
(module
|
||||
;; no memory imported
|
||||
@@ -678,7 +653,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(initial_exceeds_maximum,
|
||||
prepare_test!(
|
||||
initial_exceeds_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 16 1))
|
||||
@@ -690,7 +666,8 @@ mod tests {
|
||||
Err("Module is not valid")
|
||||
);
|
||||
|
||||
prepare_test!(no_maximum,
|
||||
prepare_test!(
|
||||
no_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1))
|
||||
@@ -702,7 +679,8 @@ mod tests {
|
||||
Err("Maximum number of pages should be always declared.")
|
||||
);
|
||||
|
||||
prepare_test!(requested_maximum_valid,
|
||||
prepare_test!(
|
||||
requested_maximum_valid,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 16))
|
||||
@@ -714,7 +692,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(requested_maximum_exceeds_configured_maximum,
|
||||
prepare_test!(
|
||||
requested_maximum_exceeds_configured_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 17))
|
||||
@@ -726,7 +705,8 @@ mod tests {
|
||||
Err("Maximum number of pages should not exceed the configured maximum.")
|
||||
);
|
||||
|
||||
prepare_test!(field_name_not_memory,
|
||||
prepare_test!(
|
||||
field_name_not_memory,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "forgetit" (memory 1 1))
|
||||
@@ -738,7 +718,8 @@ mod tests {
|
||||
Err("Memory import must have the field name 'memory'")
|
||||
);
|
||||
|
||||
prepare_test!(multiple_memory_imports,
|
||||
prepare_test!(
|
||||
multiple_memory_imports,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 1))
|
||||
@@ -751,7 +732,8 @@ mod tests {
|
||||
Err("Module is not valid")
|
||||
);
|
||||
|
||||
prepare_test!(table_import,
|
||||
prepare_test!(
|
||||
table_import,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "table" (table 1 anyfunc))
|
||||
@@ -763,7 +745,8 @@ mod tests {
|
||||
Err("Cannot import tables")
|
||||
);
|
||||
|
||||
prepare_test!(global_import,
|
||||
prepare_test!(
|
||||
global_import,
|
||||
r#"
|
||||
(module
|
||||
(global $g (import "seal0" "global") i32)
|
||||
@@ -778,7 +761,8 @@ mod tests {
|
||||
mod tables {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(no_tables,
|
||||
prepare_test!(
|
||||
no_tables,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -788,7 +772,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(table_valid_size,
|
||||
prepare_test!(
|
||||
table_valid_size,
|
||||
r#"
|
||||
(module
|
||||
(table 3 funcref)
|
||||
@@ -800,7 +785,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(table_too_big,
|
||||
prepare_test!(
|
||||
table_too_big,
|
||||
r#"
|
||||
(module
|
||||
(table 4 funcref)
|
||||
@@ -811,7 +797,8 @@ mod tests {
|
||||
Err("table exceeds maximum size allowed")
|
||||
);
|
||||
|
||||
prepare_test!(br_table_valid_size,
|
||||
prepare_test!(
|
||||
br_table_valid_size,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -825,7 +812,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(br_table_too_big,
|
||||
prepare_test!(
|
||||
br_table_too_big,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -842,7 +830,8 @@ mod tests {
|
||||
mod imports {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(can_import_legit_function,
|
||||
prepare_test!(
|
||||
can_import_legit_function,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "nop" (func (param i64)))
|
||||
@@ -856,7 +845,8 @@ mod tests {
|
||||
|
||||
// even though gas is defined the contract can't import it since
|
||||
// it is an implementation defined.
|
||||
prepare_test!(can_not_import_gas_function,
|
||||
prepare_test!(
|
||||
can_not_import_gas_function,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "gas" (func (param i32)))
|
||||
@@ -869,7 +859,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// memory is in "env" and not in "seal0"
|
||||
prepare_test!(memory_not_in_seal0,
|
||||
prepare_test!(
|
||||
memory_not_in_seal0,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "memory" (memory 1 1))
|
||||
@@ -882,7 +873,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// memory is in "env" and not in some arbitrary module
|
||||
prepare_test!(memory_not_in_arbitrary_module,
|
||||
prepare_test!(
|
||||
memory_not_in_arbitrary_module,
|
||||
r#"
|
||||
(module
|
||||
(import "any_module" "memory" (memory 1 1))
|
||||
@@ -894,7 +886,8 @@ mod tests {
|
||||
Err("Invalid module for imported memory")
|
||||
);
|
||||
|
||||
prepare_test!(function_in_other_module_works,
|
||||
prepare_test!(
|
||||
function_in_other_module_works,
|
||||
r#"
|
||||
(module
|
||||
(import "seal1" "nop" (func (param i32)))
|
||||
@@ -907,7 +900,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// wrong signature
|
||||
prepare_test!(wrong_signature,
|
||||
prepare_test!(
|
||||
wrong_signature,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "gas" (func (param i64)))
|
||||
@@ -919,7 +913,8 @@ mod tests {
|
||||
Err("module imports a non-existent function")
|
||||
);
|
||||
|
||||
prepare_test!(unknown_func_name,
|
||||
prepare_test!(
|
||||
unknown_func_name,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "unknown_func" (func))
|
||||
@@ -935,7 +930,8 @@ mod tests {
|
||||
mod entrypoints {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(it_works,
|
||||
prepare_test!(
|
||||
it_works,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -945,7 +941,8 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(omit_deploy,
|
||||
prepare_test!(
|
||||
omit_deploy,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -954,7 +951,8 @@ mod tests {
|
||||
Err("deploy function isn't exported")
|
||||
);
|
||||
|
||||
prepare_test!(omit_call,
|
||||
prepare_test!(
|
||||
omit_call,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
@@ -964,7 +962,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// Try to use imported function as an entry point.
|
||||
prepare_test!(try_sneak_export_as_entrypoint,
|
||||
prepare_test!(
|
||||
try_sneak_export_as_entrypoint,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "panic" (func))
|
||||
@@ -978,7 +977,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// Try to use imported function as an entry point.
|
||||
prepare_test!(try_sneak_export_as_global,
|
||||
prepare_test!(
|
||||
try_sneak_export_as_global,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
@@ -988,7 +988,8 @@ mod tests {
|
||||
Err("expected a function")
|
||||
);
|
||||
|
||||
prepare_test!(wrong_signature,
|
||||
prepare_test!(
|
||||
wrong_signature,
|
||||
r#"
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
@@ -998,7 +999,8 @@ mod tests {
|
||||
Err("entry point has wrong signature")
|
||||
);
|
||||
|
||||
prepare_test!(unknown_exports,
|
||||
prepare_test!(
|
||||
unknown_exports,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
@@ -1009,7 +1011,8 @@ mod tests {
|
||||
Err("unknown export: expecting only deploy and call functions")
|
||||
);
|
||||
|
||||
prepare_test!(global_float,
|
||||
prepare_test!(
|
||||
global_float,
|
||||
r#"
|
||||
(module
|
||||
(global $x f32 (f32.const 0))
|
||||
@@ -1020,7 +1023,8 @@ mod tests {
|
||||
Err("use of floating point type in globals is forbidden")
|
||||
);
|
||||
|
||||
prepare_test!(local_float,
|
||||
prepare_test!(
|
||||
local_float,
|
||||
r#"
|
||||
(module
|
||||
(func $foo (local f32))
|
||||
@@ -1031,7 +1035,8 @@ mod tests {
|
||||
Err("use of floating point type in locals is forbidden")
|
||||
);
|
||||
|
||||
prepare_test!(param_float,
|
||||
prepare_test!(
|
||||
param_float,
|
||||
r#"
|
||||
(module
|
||||
(func $foo (param f32))
|
||||
@@ -1042,7 +1047,8 @@ mod tests {
|
||||
Err("use of floating point type in function types is forbidden")
|
||||
);
|
||||
|
||||
prepare_test!(result_float,
|
||||
prepare_test!(
|
||||
result_float,
|
||||
r#"
|
||||
(module
|
||||
(func $foo (result f32) (f32.const 0))
|
||||
|
||||
@@ -18,25 +18,20 @@
|
||||
//! Environment definition of the wasm smart-contract runtime.
|
||||
|
||||
use crate::{
|
||||
Config, CodeHash, BalanceOf, Error,
|
||||
exec::{Ext, StorageKey, TopicOf, ExecResult, ExecError},
|
||||
gas::{Token, ChargedAmount},
|
||||
wasm::env_def::ConvertibleToWasm,
|
||||
exec::{ExecError, ExecResult, Ext, StorageKey, TopicOf},
|
||||
gas::{ChargedAmount, Token},
|
||||
schedule::HostFnWeights,
|
||||
wasm::env_def::ConvertibleToWasm,
|
||||
BalanceOf, CodeHash, Config, Error,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use pwasm_utils::parity_wasm::elements::ValueType;
|
||||
use frame_support::{dispatch::DispatchError, ensure, weights::Weight};
|
||||
use sp_std::prelude::*;
|
||||
use codec::{Decode, DecodeAll, Encode, MaxEncodedLen};
|
||||
use sp_core::{Bytes, crypto::UncheckedFrom};
|
||||
use sp_io::hashing::{
|
||||
keccak_256,
|
||||
blake2_256,
|
||||
blake2_128,
|
||||
sha2_256,
|
||||
};
|
||||
use frame_support::{dispatch::DispatchError, ensure, weights::Weight};
|
||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||
use pwasm_utils::parity_wasm::elements::ValueType;
|
||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Every error that can be returned to a contract when it calls any of the host functions.
|
||||
///
|
||||
@@ -178,7 +173,7 @@ pub enum RuntimeCosts {
|
||||
/// Weight of calling `seal_random`. It includes the weight for copying the subject.
|
||||
Random,
|
||||
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
|
||||
DepositEvent{num_topic: u32, len: u32},
|
||||
DepositEvent { num_topic: u32, len: u32 },
|
||||
/// Weight of calling `seal_debug_message`.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DebugMessage,
|
||||
@@ -203,7 +198,7 @@ pub enum RuntimeCosts {
|
||||
/// Weight of calling `seal_instantiate` for the given input and salt without output weight.
|
||||
/// This includes the transfer as an instantiate without a value will always be below
|
||||
/// the existential deposit and is disregarded as corner case.
|
||||
InstantiateBase{input_data_len: u32, salt_len: u32},
|
||||
InstantiateBase { input_data_len: u32, salt_len: u32 },
|
||||
/// Weight of output received through `seal_instantiate` for the given size.
|
||||
InstantiateCopyOut(u32),
|
||||
/// Weight of calling `seal_hash_sha_256` for the given input size.
|
||||
@@ -228,7 +223,7 @@ impl RuntimeCosts {
|
||||
fn token<T>(&self, s: &HostFnWeights<T>) -> RuntimeToken
|
||||
where
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
use self::RuntimeCosts::*;
|
||||
let weight = match *self {
|
||||
@@ -246,40 +241,44 @@ impl RuntimeCosts {
|
||||
WeightToFee => s.weight_to_fee,
|
||||
InputBase => s.input,
|
||||
InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()),
|
||||
Return(len) => s.r#return
|
||||
.saturating_add(s.return_per_byte.saturating_mul(len.into())),
|
||||
Return(len) => s.r#return.saturating_add(s.return_per_byte.saturating_mul(len.into())),
|
||||
Terminate => s.terminate,
|
||||
RestoreTo(delta) => s.restore_to
|
||||
.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
|
||||
RestoreTo(delta) =>
|
||||
s.restore_to.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
|
||||
Random => s.random,
|
||||
DepositEvent{num_topic, len} => s.deposit_event
|
||||
DepositEvent { num_topic, len } => s
|
||||
.deposit_event
|
||||
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
|
||||
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DebugMessage => s.debug_message,
|
||||
SetRentAllowance => s.set_rent_allowance,
|
||||
SetStorage(len) => s.set_storage
|
||||
.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
|
||||
SetStorage(len) =>
|
||||
s.set_storage.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
|
||||
ClearStorage => s.clear_storage,
|
||||
GetStorageBase => s.get_storage,
|
||||
GetStorageCopyOut(len) => s.get_storage_per_byte.saturating_mul(len.into()),
|
||||
Transfer => s.transfer,
|
||||
CallBase(len) => s.call
|
||||
.saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
|
||||
CallBase(len) =>
|
||||
s.call.saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
|
||||
CallSurchargeTransfer => s.call_transfer_surcharge,
|
||||
CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()),
|
||||
InstantiateBase{input_data_len, salt_len} => s.instantiate
|
||||
InstantiateBase { input_data_len, salt_len } => s
|
||||
.instantiate
|
||||
.saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into()))
|
||||
.saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())),
|
||||
InstantiateCopyOut(len) => s.instantiate_per_output_byte
|
||||
.saturating_mul(len.into()),
|
||||
HashSha256(len) => s.hash_sha2_256
|
||||
InstantiateCopyOut(len) => s.instantiate_per_output_byte.saturating_mul(len.into()),
|
||||
HashSha256(len) => s
|
||||
.hash_sha2_256
|
||||
.saturating_add(s.hash_sha2_256_per_byte.saturating_mul(len.into())),
|
||||
HashKeccak256(len) => s.hash_keccak_256
|
||||
HashKeccak256(len) => s
|
||||
.hash_keccak_256
|
||||
.saturating_add(s.hash_keccak_256_per_byte.saturating_mul(len.into())),
|
||||
HashBlake256(len) => s.hash_blake2_256
|
||||
HashBlake256(len) => s
|
||||
.hash_blake2_256
|
||||
.saturating_add(s.hash_blake2_256_per_byte.saturating_mul(len.into())),
|
||||
HashBlake128(len) => s.hash_blake2_128
|
||||
HashBlake128(len) => s
|
||||
.hash_blake2_128
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
ChainExtension(amount) => amount,
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
@@ -306,7 +305,7 @@ struct RuntimeToken {
|
||||
impl<T> Token<T> for RuntimeToken
|
||||
where
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
@@ -373,19 +372,10 @@ impl<'a, E> Runtime<'a, E>
|
||||
where
|
||||
E: Ext + 'a,
|
||||
<E::T as frame_system::Config>::AccountId:
|
||||
UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>
|
||||
UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(
|
||||
ext: &'a mut E,
|
||||
input_data: Vec<u8>,
|
||||
memory: sp_sandbox::Memory,
|
||||
) -> Self {
|
||||
Runtime {
|
||||
ext,
|
||||
input_data: Some(input_data),
|
||||
memory,
|
||||
trap_reason: None,
|
||||
}
|
||||
pub fn new(ext: &'a mut E, input_data: Vec<u8>, memory: sp_sandbox::Memory) -> Self {
|
||||
Runtime { ext, input_data: Some(input_data), memory, trap_reason: None }
|
||||
}
|
||||
|
||||
/// Converts the sandbox result and the runtime state into the execution outcome.
|
||||
@@ -401,27 +391,15 @@ where
|
||||
if let Some(trap_reason) = self.trap_reason {
|
||||
return match trap_reason {
|
||||
// The trap was the result of the execution `return` host function.
|
||||
TrapReason::Return(ReturnData{ flags, data }) => {
|
||||
let flags = ReturnFlags::from_bits(flags).ok_or_else(||
|
||||
"used reserved bit in return flags"
|
||||
)?;
|
||||
Ok(ExecReturnValue {
|
||||
flags,
|
||||
data: Bytes(data),
|
||||
})
|
||||
},
|
||||
TrapReason::Termination => {
|
||||
Ok(ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(Vec::new()),
|
||||
})
|
||||
},
|
||||
TrapReason::Restoration => {
|
||||
Ok(ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Bytes(Vec::new()),
|
||||
})
|
||||
TrapReason::Return(ReturnData { flags, data }) => {
|
||||
let flags = ReturnFlags::from_bits(flags)
|
||||
.ok_or_else(|| "used reserved bit in return flags")?;
|
||||
Ok(ExecReturnValue { flags, data: Bytes(data) })
|
||||
},
|
||||
TrapReason::Termination =>
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
|
||||
TrapReason::Restoration =>
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
|
||||
TrapReason::SupervisorError(error) => Err(error)?,
|
||||
}
|
||||
}
|
||||
@@ -429,9 +407,7 @@ where
|
||||
// Check the exact type of the error.
|
||||
match sandbox_result {
|
||||
// No traps were generated. Proceed normally.
|
||||
Ok(_) => {
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) })
|
||||
}
|
||||
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
|
||||
// `Error::Module` is returned only if instantiation or linking failed (i.e.
|
||||
// wasm binary tried to import a function that is not provided by the host).
|
||||
// This shouldn't happen because validation process ought to reject such binaries.
|
||||
@@ -441,7 +417,7 @@ where
|
||||
Err(sp_sandbox::Error::Module) => Err("validation error")?,
|
||||
// Any other kind of a trap should result in a failure.
|
||||
Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
|
||||
Err(Error::<E::T>::ContractTrapped)?
|
||||
Err(Error::<E::T>::ContractTrapped)?,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,12 +460,11 @@ where
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
pub fn read_sandbox_memory(&self, ptr: u32, len: u32)
|
||||
-> Result<Vec<u8>, DispatchError>
|
||||
{
|
||||
pub fn read_sandbox_memory(&self, ptr: u32, len: u32) -> Result<Vec<u8>, DispatchError> {
|
||||
ensure!(len <= self.ext.schedule().limits.max_memory_size(), Error::<E::T>::OutOfBounds);
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
self.memory.get(ptr, buf.as_mut_slice())
|
||||
self.memory
|
||||
.get(ptr, buf.as_mut_slice())
|
||||
.map_err(|_| Error::<E::T>::OutOfBounds)?;
|
||||
Ok(buf)
|
||||
}
|
||||
@@ -499,9 +474,11 @@ where
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
pub fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
|
||||
-> Result<(), DispatchError>
|
||||
{
|
||||
pub fn read_sandbox_memory_into_buf(
|
||||
&self,
|
||||
ptr: u32,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(), DispatchError> {
|
||||
self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
|
||||
}
|
||||
|
||||
@@ -511,9 +488,10 @@ where
|
||||
///
|
||||
/// The weight of reading a fixed value is included in the overall weight of any
|
||||
/// contract callable function.
|
||||
pub fn read_sandbox_memory_as<D: Decode + MaxEncodedLen>(&self, ptr: u32)
|
||||
-> Result<D, DispatchError>
|
||||
{
|
||||
pub fn read_sandbox_memory_as<D: Decode + MaxEncodedLen>(
|
||||
&self,
|
||||
ptr: u32,
|
||||
) -> Result<D, DispatchError> {
|
||||
let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?;
|
||||
let decoded = D::decode_all(&mut &buf[..])
|
||||
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
|
||||
@@ -531,9 +509,11 @@ where
|
||||
///
|
||||
/// There must be an extra benchmark for determining the influence of `len` with
|
||||
/// regard to the overall weight.
|
||||
pub fn read_sandbox_memory_as_unbounded<D: Decode>(&self, ptr: u32, len: u32)
|
||||
-> Result<D, DispatchError>
|
||||
{
|
||||
pub fn read_sandbox_memory_as_unbounded<D: Decode>(
|
||||
&self,
|
||||
ptr: u32,
|
||||
len: u32,
|
||||
) -> Result<D, DispatchError> {
|
||||
let buf = self.read_sandbox_memory(ptr, len)?;
|
||||
let decoded = D::decode_all(&mut &buf[..])
|
||||
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
|
||||
@@ -566,10 +546,9 @@ where
|
||||
buf: &[u8],
|
||||
allow_skip: bool,
|
||||
create_token: impl FnOnce(u32) -> Option<RuntimeCosts>,
|
||||
) -> Result<(), DispatchError>
|
||||
{
|
||||
) -> Result<(), DispatchError> {
|
||||
if allow_skip && out_ptr == u32::MAX {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let buf_len = buf.len() as u32;
|
||||
@@ -583,10 +562,10 @@ where
|
||||
self.charge_gas(costs)?;
|
||||
}
|
||||
|
||||
self.memory.set(out_ptr, buf).and_then(|_| {
|
||||
self.memory.set(out_len_ptr, &buf_len.encode())
|
||||
})
|
||||
.map_err(|_| Error::<E::T>::OutOfBounds)?;
|
||||
self.memory
|
||||
.set(out_ptr, buf)
|
||||
.and_then(|_| self.memory.set(out_len_ptr, &buf_len.encode()))
|
||||
.map_err(|_| Error::<E::T>::OutOfBounds)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -650,7 +629,7 @@ where
|
||||
x if x == not_funded => Ok(NewContractNotFunded),
|
||||
x if x == no_code => Ok(CodeNotFound),
|
||||
x if (x == not_found || x == is_tombstone || x == rent_not_paid) => Ok(NotCallable),
|
||||
err => Err(err)
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,7 +644,7 @@ where
|
||||
|
||||
match (error, origin) {
|
||||
(_, Callee) => Ok(ReturnCode::CalleeTrapped),
|
||||
(err, _) => Self::err_into_return_code(err)
|
||||
(err, _) => Self::err_into_return_code(err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,9 +657,8 @@ where
|
||||
input_data_ptr: u32,
|
||||
input_data_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32
|
||||
) -> Result<ReturnCode, TrapReason>
|
||||
{
|
||||
output_len_ptr: u32,
|
||||
) -> Result<ReturnCode, TrapReason> {
|
||||
self.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
|
||||
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||
self.read_sandbox_memory_as(callee_ptr)?;
|
||||
@@ -696,9 +674,8 @@ where
|
||||
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
||||
}
|
||||
let ext = &mut self.ext;
|
||||
let call_outcome = ext.call(
|
||||
gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY),
|
||||
);
|
||||
let call_outcome =
|
||||
ext.call(gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY));
|
||||
|
||||
// `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to
|
||||
// a halt anyways without anymore code being executed.
|
||||
@@ -707,7 +684,7 @@ where
|
||||
return Err(TrapReason::Return(ReturnData {
|
||||
flags: return_value.flags.bits(),
|
||||
data: return_value.data.0,
|
||||
}));
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,10 +708,9 @@ where
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32,
|
||||
salt_ptr: u32,
|
||||
salt_len: u32
|
||||
) -> Result<ReturnCode, TrapReason>
|
||||
{
|
||||
self.charge_gas(RuntimeCosts::InstantiateBase {input_data_len, salt_len})?;
|
||||
salt_len: u32,
|
||||
) -> Result<ReturnCode, TrapReason> {
|
||||
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> = self.read_sandbox_memory_as(code_hash_ptr)?;
|
||||
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
|
||||
let input_data = self.read_sandbox_memory(input_data_ptr, input_data_len)?;
|
||||
@@ -743,7 +719,11 @@ where
|
||||
if let Ok((address, output)) = &instantiate_outcome {
|
||||
if !output.flags.contains(ReturnFlags::REVERT) {
|
||||
self.write_sandbox_output(
|
||||
address_ptr, address_len_ptr, &address.encode(), true, already_charged,
|
||||
address_ptr,
|
||||
address_len_ptr,
|
||||
&address.encode(),
|
||||
true,
|
||||
already_charged,
|
||||
)?;
|
||||
}
|
||||
self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
|
||||
@@ -767,13 +747,12 @@ where
|
||||
code_hash_ptr: u32,
|
||||
rent_allowance_ptr: u32,
|
||||
delta_ptr: u32,
|
||||
delta_count: u32
|
||||
delta_count: u32,
|
||||
) -> Result<(), TrapReason> {
|
||||
self.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
|
||||
let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||
self.read_sandbox_memory_as(dest_ptr)?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> =
|
||||
self.read_sandbox_memory_as(code_hash_ptr)?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> = self.read_sandbox_memory_as(code_hash_ptr)?;
|
||||
let rent_allowance: BalanceOf<<E as Ext>::T> =
|
||||
self.read_sandbox_memory_as(rent_allowance_ptr)?;
|
||||
let delta = {
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_benchmarking::{benchmarks, account, whitelist_account, impl_benchmark_test_suite};
|
||||
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelist_account};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::{Currency, Get, EnsureOrigin, OnInitialize, UnfilteredDispatchable, schedule::DispatchTime},
|
||||
traits::{
|
||||
schedule::DispatchTime, Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable,
|
||||
},
|
||||
};
|
||||
use frame_system::{RawOrigin, Pallet as System, self};
|
||||
use sp_runtime::traits::{Bounded, One, BadOrigin};
|
||||
use frame_system::{Pallet as System, RawOrigin};
|
||||
use sp_runtime::traits::{BadOrigin, Bounded, One};
|
||||
|
||||
use crate::Pallet as Democracy;
|
||||
|
||||
@@ -49,11 +51,7 @@ fn add_proposal<T: Config>(n: u32) -> Result<T::Hash, &'static str> {
|
||||
let value = T::MinimumDeposit::get();
|
||||
let proposal_hash: T::Hash = T::Hashing::hash_of(&n);
|
||||
|
||||
Democracy::<T>::propose(
|
||||
RawOrigin::Signed(other).into(),
|
||||
proposal_hash,
|
||||
value.into(),
|
||||
)?;
|
||||
Democracy::<T>::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?;
|
||||
|
||||
Ok(proposal_hash)
|
||||
}
|
||||
@@ -76,20 +74,15 @@ fn add_referendum<T: Config>(n: u32) -> Result<ReferendumIndex, &'static str> {
|
||||
63,
|
||||
frame_system::RawOrigin::Root.into(),
|
||||
Call::enact_proposal(proposal_hash, referendum_index).into(),
|
||||
).map_err(|_| "failed to schedule named")?;
|
||||
)
|
||||
.map_err(|_| "failed to schedule named")?;
|
||||
Ok(referendum_index)
|
||||
}
|
||||
|
||||
fn account_vote<T: Config>(b: BalanceOf<T>) -> AccountVote<BalanceOf<T>> {
|
||||
let v = Vote {
|
||||
aye: true,
|
||||
conviction: Conviction::Locked1x,
|
||||
};
|
||||
let v = Vote { aye: true, conviction: Conviction::Locked1x };
|
||||
|
||||
AccountVote::Standard {
|
||||
vote: v,
|
||||
balance: b,
|
||||
}
|
||||
AccountVote::Standard { vote: v, balance: b }
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
@@ -224,8 +217,8 @@ benchmarks! {
|
||||
// Place our proposal in the external queue, too.
|
||||
let hash = T::Hashing::hash_of(&0);
|
||||
assert_ok!(
|
||||
Democracy::<T>::external_propose(T::ExternalOrigin::successful_origin(), hash.clone())
|
||||
);
|
||||
Democracy::<T>::external_propose(T::ExternalOrigin::successful_origin(), hash.clone())
|
||||
);
|
||||
|
||||
// Add a referendum of our proposal.
|
||||
let referendum_index = add_referendum::<T>(0)?;
|
||||
@@ -237,9 +230,9 @@ benchmarks! {
|
||||
verify {
|
||||
// Referendum has been canceled
|
||||
assert_noop!(
|
||||
Democracy::<T>::referendum_status(referendum_index),
|
||||
Error::<T>::ReferendumInvalid
|
||||
);
|
||||
Democracy::<T>::referendum_status(referendum_index),
|
||||
Error::<T>::ReferendumInvalid
|
||||
);
|
||||
}
|
||||
|
||||
// Worst case scenario, we external propose a previously blacklisted proposal
|
||||
@@ -785,9 +778,4 @@ benchmarks! {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Democracy,
|
||||
crate::tests::new_test_ext(),
|
||||
crate::tests::Test,
|
||||
);
|
||||
impl_benchmark_test_suite!(Democracy, crate::tests::new_test_ext(), crate::tests::Test,);
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
|
||||
//! The conviction datatype.
|
||||
|
||||
use sp_std::{result::Result, convert::TryFrom};
|
||||
use sp_runtime::{RuntimeDebug, traits::{Zero, Bounded, CheckedMul, CheckedDiv}};
|
||||
use codec::{Encode, Decode};
|
||||
use crate::types::Delegations;
|
||||
use codec::{Decode, Encode};
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, CheckedDiv, CheckedMul, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{convert::TryFrom, result::Result};
|
||||
|
||||
/// A value denoting the strength of conviction of a vote.
|
||||
#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)]
|
||||
@@ -93,9 +96,10 @@ impl Conviction {
|
||||
}
|
||||
|
||||
/// The votes of a voter of the given `balance` with our conviction.
|
||||
pub fn votes<
|
||||
B: From<u8> + Zero + Copy + CheckedMul + CheckedDiv + Bounded
|
||||
>(self, capital: B) -> Delegations<B> {
|
||||
pub fn votes<B: From<u8> + Zero + Copy + CheckedMul + CheckedDiv + Bounded>(
|
||||
self,
|
||||
capital: B,
|
||||
) -> Delegations<B> {
|
||||
let votes = match self {
|
||||
Conviction::None => capital.checked_div(&10u8.into()).unwrap_or_else(Zero::zero),
|
||||
x => capital.checked_mul(&u8::from(x).into()).unwrap_or_else(B::max_value),
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
//! This call can only be made by the `ExternalMajorityOrigin`.
|
||||
//!
|
||||
//! - `external_propose_majority` - Schedules a proposal to become a majority-carries
|
||||
//! referendum once it is legal for an externally proposed referendum.
|
||||
//! referendum once it is legal for an externally proposed referendum.
|
||||
//!
|
||||
//! #### External Default Origin
|
||||
//!
|
||||
@@ -149,34 +149,36 @@
|
||||
//! - `cancel_queued` - Cancels a proposal that is queued for enactment.
|
||||
//! - `clear_public_proposal` - Removes all public proposals.
|
||||
|
||||
#![recursion_limit="128"]
|
||||
#![recursion_limit = "128"]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::{
|
||||
DispatchResult, DispatchError, ArithmeticError, RuntimeDebug,
|
||||
traits::{Zero, Hash, Dispatchable, Saturating, Bounded},
|
||||
};
|
||||
use codec::{Encode, Decode, Input};
|
||||
use codec::{Decode, Encode, Input};
|
||||
use frame_support::{
|
||||
ensure, weights::Weight,
|
||||
ensure,
|
||||
traits::{
|
||||
Currency, ReservableCurrency, LockableCurrency, WithdrawReasons, LockIdentifier, Get,
|
||||
OnUnbalanced, BalanceStatus, schedule::{Named as ScheduleNamed, DispatchTime},
|
||||
schedule::{DispatchTime, Named as ScheduleNamed},
|
||||
BalanceStatus, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced,
|
||||
ReservableCurrency, WithdrawReasons,
|
||||
},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, Dispatchable, Hash, Saturating, Zero},
|
||||
ArithmeticError, DispatchError, DispatchResult, RuntimeDebug,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod vote_threshold;
|
||||
mod vote;
|
||||
mod conviction;
|
||||
mod types;
|
||||
mod vote;
|
||||
mod vote_threshold;
|
||||
pub mod weights;
|
||||
pub use weights::WeightInfo;
|
||||
pub use vote_threshold::{Approved, VoteThreshold};
|
||||
pub use vote::{Vote, AccountVote, Voting};
|
||||
pub use conviction::Conviction;
|
||||
pub use types::{ReferendumInfo, ReferendumStatus, Tally, UnvoteScope, Delegations};
|
||||
pub use pallet::*;
|
||||
pub use types::{Delegations, ReferendumInfo, ReferendumStatus, Tally, UnvoteScope};
|
||||
pub use vote::{AccountVote, Vote, Voting};
|
||||
pub use vote_threshold::{Approved, VoteThreshold};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -197,9 +199,11 @@ pub type PropIndex = u32;
|
||||
/// A referendum index.
|
||||
pub type ReferendumIndex = u32;
|
||||
|
||||
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::NegativeImbalance;
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug)]
|
||||
pub enum PreimageStatus<AccountId, Balance, BlockNumber> {
|
||||
@@ -235,13 +239,16 @@ enum Releases {
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use sp_runtime::DispatchResult;
|
||||
use frame_support::{
|
||||
pallet_prelude::*, Parameter,
|
||||
weights::{DispatchClass, Pays}, traits::EnsureOrigin, dispatch::DispatchResultWithPostInfo,
|
||||
};
|
||||
use frame_system::{pallet_prelude::*, ensure_signed, ensure_root};
|
||||
use super::*;
|
||||
use frame_support::{
|
||||
dispatch::DispatchResultWithPostInfo,
|
||||
pallet_prelude::*,
|
||||
traits::EnsureOrigin,
|
||||
weights::{DispatchClass, Pays},
|
||||
Parameter,
|
||||
};
|
||||
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
|
||||
use sp_runtime::DispatchResult;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
@@ -249,12 +256,12 @@ pub mod pallet {
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + Sized {
|
||||
type Proposal: Parameter + Dispatchable<Origin=Self::Origin> + From<Call<Self>>;
|
||||
type Proposal: Parameter + Dispatchable<Origin = Self::Origin> + From<Call<Self>>;
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
||||
|
||||
/// Currency type for this pallet.
|
||||
type Currency: ReservableCurrency<Self::AccountId>
|
||||
+ LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
|
||||
+ LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
|
||||
|
||||
/// The minimum period of locking and the period between a proposal being approved and enacted.
|
||||
///
|
||||
@@ -323,7 +330,7 @@ pub mod pallet {
|
||||
///
|
||||
/// The number of Vetoers for a proposal must be small, extrinsics are weighted according to
|
||||
/// [MAX_VETOERS](./const.MAX_VETOERS.html)
|
||||
type VetoOrigin: EnsureOrigin<Self::Origin, Success=Self::AccountId>;
|
||||
type VetoOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
|
||||
|
||||
/// Period in blocks where an external proposal may not be re-submitted after being vetoed.
|
||||
#[pallet::constant]
|
||||
@@ -334,7 +341,7 @@ pub mod pallet {
|
||||
type PreimageByteDeposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// An origin that can provide a preimage using operational extrinsics.
|
||||
type OperationalPreimageOrigin: EnsureOrigin<Self::Origin, Success=Self::AccountId>;
|
||||
type OperationalPreimageOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;
|
||||
|
||||
/// Handler for the unbalanced reduction when slashing a preimage deposit.
|
||||
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||
@@ -370,18 +377,16 @@ pub mod pallet {
|
||||
/// The public proposals. Unsorted. The second item is the proposal's hash.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn public_props)]
|
||||
pub type PublicProps<T: Config> = StorageValue<_, Vec<(PropIndex, T::Hash, T::AccountId)>, ValueQuery>;
|
||||
pub type PublicProps<T: Config> =
|
||||
StorageValue<_, Vec<(PropIndex, T::Hash, T::AccountId)>, ValueQuery>;
|
||||
|
||||
/// Those who have locked a deposit.
|
||||
///
|
||||
/// TWOX-NOTE: Safe, as increasing integer keys are safe.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn deposit_of)]
|
||||
pub type DepositOf<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, PropIndex,
|
||||
(Vec<T::AccountId>, BalanceOf<T>),
|
||||
>;
|
||||
pub type DepositOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, PropIndex, (Vec<T::AccountId>, BalanceOf<T>)>;
|
||||
|
||||
/// Map of hashes to the proposal preimage, along with who registered it and their deposit.
|
||||
/// The block number is the block at which it was deposited.
|
||||
@@ -390,7 +395,8 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
pub type Preimages<T: Config> = StorageMap<
|
||||
_,
|
||||
Identity, T::Hash,
|
||||
Identity,
|
||||
T::Hash,
|
||||
PreimageStatus<T::AccountId, BalanceOf<T>, T::BlockNumber>,
|
||||
>;
|
||||
|
||||
@@ -412,7 +418,8 @@ pub mod pallet {
|
||||
#[pallet::getter(fn referendum_info)]
|
||||
pub type ReferendumInfoOf<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, ReferendumIndex,
|
||||
Twox64Concat,
|
||||
ReferendumIndex,
|
||||
ReferendumInfo<T::BlockNumber, T::Hash, BalanceOf<T>>,
|
||||
>;
|
||||
|
||||
@@ -422,7 +429,9 @@ pub mod pallet {
|
||||
/// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway.
|
||||
#[pallet::storage]
|
||||
pub type VotingOf<T: Config> = StorageMap<
|
||||
_, Twox64Concat, T::AccountId,
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Voting<BalanceOf<T>, T::AccountId, T::BlockNumber>,
|
||||
ValueQuery,
|
||||
>;
|
||||
@@ -452,7 +461,8 @@ pub mod pallet {
|
||||
/// A record of who vetoed what. Maps proposal hash to a possible existent block number
|
||||
/// (until when it may not be resubmitted) and who vetoed it.
|
||||
#[pallet::storage]
|
||||
pub type Blacklist<T: Config> = StorageMap<_, Identity, T::Hash, (T::BlockNumber, Vec<T::AccountId>)>;
|
||||
pub type Blacklist<T: Config> =
|
||||
StorageMap<_, Identity, T::Hash, (T::BlockNumber, Vec<T::AccountId>)>;
|
||||
|
||||
/// Record of all proposals that have been subject to emergency cancellation.
|
||||
#[pallet::storage]
|
||||
@@ -472,9 +482,7 @@ pub mod pallet {
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig {
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
GenesisConfig { _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,11 +692,10 @@ pub mod pallet {
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
let seconds = Self::len_of_deposit_of(proposal)
|
||||
.ok_or_else(|| Error::<T>::ProposalMissing)?;
|
||||
let seconds =
|
||||
Self::len_of_deposit_of(proposal).ok_or_else(|| Error::<T>::ProposalMissing)?;
|
||||
ensure!(seconds <= seconds_upper_bound, Error::<T>::WrongUpperBound);
|
||||
let mut deposit = Self::deposit_of(proposal)
|
||||
.ok_or(Error::<T>::ProposalMissing)?;
|
||||
let mut deposit = Self::deposit_of(proposal).ok_or(Error::<T>::ProposalMissing)?;
|
||||
T::Currency::reserve(&who, deposit.1)?;
|
||||
deposit.0.push(who);
|
||||
<DepositOf<T>>::insert(proposal, deposit);
|
||||
@@ -726,7 +733,10 @@ pub mod pallet {
|
||||
///
|
||||
/// Weight: `O(1)`.
|
||||
#[pallet::weight((T::WeightInfo::emergency_cancel(), DispatchClass::Operational))]
|
||||
pub fn emergency_cancel(origin: OriginFor<T>, ref_index: ReferendumIndex) -> DispatchResult {
|
||||
pub fn emergency_cancel(
|
||||
origin: OriginFor<T>,
|
||||
ref_index: ReferendumIndex,
|
||||
) -> DispatchResult {
|
||||
T::CancellationOrigin::ensure_origin(origin)?;
|
||||
|
||||
let status = Self::referendum_status(ref_index)?;
|
||||
@@ -842,8 +852,8 @@ pub mod pallet {
|
||||
ensure!(T::InstantAllowed::get(), Error::<T>::InstantNotAllowed);
|
||||
}
|
||||
|
||||
let (e_proposal_hash, threshold) = <NextExternal<T>>::get()
|
||||
.ok_or(Error::<T>::ProposalMissing)?;
|
||||
let (e_proposal_hash, threshold) =
|
||||
<NextExternal<T>>::get().ok_or(Error::<T>::ProposalMissing)?;
|
||||
ensure!(
|
||||
threshold != VoteThreshold::SuperMajorityApprove,
|
||||
Error::<T>::NotSimpleMajority,
|
||||
@@ -875,11 +885,10 @@ pub mod pallet {
|
||||
Err(Error::<T>::NoProposal)?;
|
||||
}
|
||||
|
||||
let mut existing_vetoers = <Blacklist<T>>::get(&proposal_hash)
|
||||
.map(|pair| pair.1)
|
||||
.unwrap_or_else(Vec::new);
|
||||
let insert_position = existing_vetoers.binary_search(&who)
|
||||
.err().ok_or(Error::<T>::AlreadyVetoed)?;
|
||||
let mut existing_vetoers =
|
||||
<Blacklist<T>>::get(&proposal_hash).map(|pair| pair.1).unwrap_or_else(Vec::new);
|
||||
let insert_position =
|
||||
existing_vetoers.binary_search(&who).err().ok_or(Error::<T>::AlreadyVetoed)?;
|
||||
|
||||
existing_vetoers.insert(insert_position, who.clone());
|
||||
let until = <frame_system::Pallet<T>>::block_number() + T::CooloffPeriod::get();
|
||||
@@ -949,7 +958,7 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
to: T::AccountId,
|
||||
conviction: Conviction,
|
||||
balance: BalanceOf<T>
|
||||
balance: BalanceOf<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let votes = Self::try_delegate(who, to, conviction, balance)?;
|
||||
@@ -1089,10 +1098,11 @@ pub mod pallet {
|
||||
|
||||
let (provider, deposit, since, expiry) = <Preimages<T>>::get(&proposal_hash)
|
||||
.and_then(|m| match m {
|
||||
PreimageStatus::Available { provider, deposit, since, expiry, .. }
|
||||
=> Some((provider, deposit, since, expiry)),
|
||||
PreimageStatus::Available { provider, deposit, since, expiry, .. } =>
|
||||
Some((provider, deposit, since, expiry)),
|
||||
_ => None,
|
||||
}).ok_or(Error::<T>::PreimageMissing)?;
|
||||
})
|
||||
.ok_or(Error::<T>::PreimageMissing)?;
|
||||
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
let (voting, enactment) = (T::VotingPeriod::get(), T::EnactmentPeriod::get());
|
||||
@@ -1100,7 +1110,8 @@ pub mod pallet {
|
||||
ensure!(now >= since + voting + additional, Error::<T>::TooEarly);
|
||||
ensure!(expiry.map_or(true, |e| now > e), Error::<T>::Imminent);
|
||||
|
||||
let res = T::Currency::repatriate_reserved(&provider, &who, deposit, BalanceStatus::Free);
|
||||
let res =
|
||||
T::Currency::repatriate_reserved(&provider, &who, deposit, BalanceStatus::Free);
|
||||
debug_assert!(res.is_ok());
|
||||
<Preimages<T>>::remove(&proposal_hash);
|
||||
Self::deposit_event(Event::<T>::PreimageReaped(proposal_hash, provider, deposit, who));
|
||||
@@ -1211,7 +1222,8 @@ pub mod pallet {
|
||||
/// Weight: `O(p)` (though as this is an high-privilege dispatch, we assume it has a
|
||||
/// reasonable value).
|
||||
#[pallet::weight((T::WeightInfo::blacklist(T::MaxProposals::get()), DispatchClass::Operational))]
|
||||
pub fn blacklist(origin: OriginFor<T>,
|
||||
pub fn blacklist(
|
||||
origin: OriginFor<T>,
|
||||
proposal_hash: T::Hash,
|
||||
maybe_ref_index: Option<ReferendumIndex>,
|
||||
) -> DispatchResult {
|
||||
@@ -1288,7 +1300,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Get all referenda ready for tally at block `n`.
|
||||
pub fn maturing_referenda_at(
|
||||
n: T::BlockNumber
|
||||
n: T::BlockNumber,
|
||||
) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>)> {
|
||||
let next = Self::lowest_unbaked();
|
||||
let last = Self::referendum_count();
|
||||
@@ -1299,7 +1311,8 @@ impl<T: Config> Pallet<T> {
|
||||
n: T::BlockNumber,
|
||||
range: core::ops::Range<PropIndex>,
|
||||
) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>)> {
|
||||
range.into_iter()
|
||||
range
|
||||
.into_iter()
|
||||
.map(|i| (i, Self::referendum_info(i)))
|
||||
.filter_map(|(i, maybe_info)| match maybe_info {
|
||||
Some(ReferendumInfo::Ongoing(status)) => Some((i, status)),
|
||||
@@ -1315,13 +1328,13 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn internal_start_referendum(
|
||||
proposal_hash: T::Hash,
|
||||
threshold: VoteThreshold,
|
||||
delay: T::BlockNumber
|
||||
delay: T::BlockNumber,
|
||||
) -> ReferendumIndex {
|
||||
<Pallet<T>>::inject_referendum(
|
||||
<frame_system::Pallet<T>>::block_number() + T::VotingPeriod::get(),
|
||||
proposal_hash,
|
||||
threshold,
|
||||
delay
|
||||
delay,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1334,25 +1347,28 @@ impl<T: Config> Pallet<T> {
|
||||
// private.
|
||||
|
||||
/// Ok if the given referendum is active, Err otherwise
|
||||
fn ensure_ongoing(r: ReferendumInfo<T::BlockNumber, T::Hash, BalanceOf<T>>)
|
||||
-> Result<ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>, DispatchError>
|
||||
{
|
||||
fn ensure_ongoing(
|
||||
r: ReferendumInfo<T::BlockNumber, T::Hash, BalanceOf<T>>,
|
||||
) -> Result<ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>, DispatchError> {
|
||||
match r {
|
||||
ReferendumInfo::Ongoing(s) => Ok(s),
|
||||
_ => Err(Error::<T>::ReferendumInvalid.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn referendum_status(ref_index: ReferendumIndex)
|
||||
-> Result<ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>, DispatchError>
|
||||
{
|
||||
let info = ReferendumInfoOf::<T>::get(ref_index)
|
||||
.ok_or(Error::<T>::ReferendumInvalid)?;
|
||||
fn referendum_status(
|
||||
ref_index: ReferendumIndex,
|
||||
) -> Result<ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>, DispatchError> {
|
||||
let info = ReferendumInfoOf::<T>::get(ref_index).ok_or(Error::<T>::ReferendumInvalid)?;
|
||||
Self::ensure_ongoing(info)
|
||||
}
|
||||
|
||||
/// Actually enact a vote, if legit.
|
||||
fn try_vote(who: &T::AccountId, ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>>) -> DispatchResult {
|
||||
fn try_vote(
|
||||
who: &T::AccountId,
|
||||
ref_index: ReferendumIndex,
|
||||
vote: AccountVote<BalanceOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let mut status = Self::referendum_status(ref_index)?;
|
||||
ensure!(vote.balance() <= T::Currency::free_balance(who), Error::<T>::InsufficientFunds);
|
||||
VotingOf::<T>::try_mutate(who, |voting| -> DispatchResult {
|
||||
@@ -1365,11 +1381,14 @@ impl<T: Config> Pallet<T> {
|
||||
status.tally.reduce(approve, *delegations);
|
||||
}
|
||||
votes[i].1 = vote;
|
||||
}
|
||||
},
|
||||
Err(i) => {
|
||||
ensure!(votes.len() as u32 <= T::MaxVotes::get(), Error::<T>::MaxVotesReached);
|
||||
ensure!(
|
||||
votes.len() as u32 <= T::MaxVotes::get(),
|
||||
Error::<T>::MaxVotesReached
|
||||
);
|
||||
votes.insert(i, (ref_index, vote));
|
||||
}
|
||||
},
|
||||
}
|
||||
// Shouldn't be possible to fail, but we handle it gracefully.
|
||||
status.tally.add(vote).ok_or(ArithmeticError::Overflow)?;
|
||||
@@ -1383,12 +1402,7 @@ impl<T: Config> Pallet<T> {
|
||||
})?;
|
||||
// Extend the lock to `balance` (rather than setting it) since we don't know what other
|
||||
// votes are in place.
|
||||
T::Currency::extend_lock(
|
||||
DEMOCRACY_ID,
|
||||
who,
|
||||
vote.balance(),
|
||||
WithdrawReasons::TRANSFER
|
||||
);
|
||||
T::Currency::extend_lock(DEMOCRACY_ID, who, vote.balance(), WithdrawReasons::TRANSFER);
|
||||
ReferendumInfoOf::<T>::insert(ref_index, ReferendumInfo::Ongoing(status));
|
||||
Ok(())
|
||||
}
|
||||
@@ -1399,11 +1413,17 @@ impl<T: Config> Pallet<T> {
|
||||
/// - The referendum has finished and the voter's lock period is up.
|
||||
///
|
||||
/// This will generally be combined with a call to `unlock`.
|
||||
fn try_remove_vote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult {
|
||||
fn try_remove_vote(
|
||||
who: &T::AccountId,
|
||||
ref_index: ReferendumIndex,
|
||||
scope: UnvoteScope,
|
||||
) -> DispatchResult {
|
||||
let info = ReferendumInfoOf::<T>::get(ref_index);
|
||||
VotingOf::<T>::try_mutate(who, |voting| -> DispatchResult {
|
||||
if let Voting::Direct { ref mut votes, delegations, ref mut prior } = voting {
|
||||
let i = votes.binary_search_by_key(&ref_index, |i| i.0).map_err(|_| Error::<T>::NotVoter)?;
|
||||
let i = votes
|
||||
.binary_search_by_key(&ref_index, |i| i.0)
|
||||
.map_err(|_| Error::<T>::NotVoter)?;
|
||||
match info {
|
||||
Some(ReferendumInfo::Ongoing(mut status)) => {
|
||||
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
|
||||
@@ -1413,17 +1433,20 @@ impl<T: Config> Pallet<T> {
|
||||
status.tally.reduce(approve, *delegations);
|
||||
}
|
||||
ReferendumInfoOf::<T>::insert(ref_index, ReferendumInfo::Ongoing(status));
|
||||
}
|
||||
Some(ReferendumInfo::Finished{end, approved}) =>
|
||||
},
|
||||
Some(ReferendumInfo::Finished { end, approved }) =>
|
||||
if let Some((lock_periods, balance)) = votes[i].1.locked_if(approved) {
|
||||
let unlock_at = end + T::EnactmentPeriod::get() * lock_periods.into();
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
if now < unlock_at {
|
||||
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
|
||||
ensure!(
|
||||
matches!(scope, UnvoteScope::Any),
|
||||
Error::<T>::NoPermission
|
||||
);
|
||||
prior.accumulate(unlock_at, balance)
|
||||
}
|
||||
},
|
||||
None => {} // Referendum was cancelled.
|
||||
None => {}, // Referendum was cancelled.
|
||||
}
|
||||
votes.remove(i);
|
||||
}
|
||||
@@ -1444,15 +1467,15 @@ impl<T: Config> Pallet<T> {
|
||||
*delegations = delegations.saturating_add(amount);
|
||||
for &(ref_index, account_vote) in votes.iter() {
|
||||
if let AccountVote::Standard { vote, .. } = account_vote {
|
||||
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info|
|
||||
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info| {
|
||||
if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info {
|
||||
status.tally.increase(vote.aye, amount);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
votes.len() as u32
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1463,20 +1486,20 @@ impl<T: Config> Pallet<T> {
|
||||
// We don't support second level delegating, so we don't need to do anything more.
|
||||
*delegations = delegations.saturating_sub(amount);
|
||||
1
|
||||
}
|
||||
},
|
||||
Voting::Direct { votes, delegations, .. } => {
|
||||
*delegations = delegations.saturating_sub(amount);
|
||||
for &(ref_index, account_vote) in votes.iter() {
|
||||
if let AccountVote::Standard { vote, .. } = account_vote {
|
||||
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info|
|
||||
ReferendumInfoOf::<T>::mutate(ref_index, |maybe_info| {
|
||||
if let Some(ReferendumInfo::Ongoing(ref mut status)) = maybe_info {
|
||||
status.tally.reduce(vote.aye, amount);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
votes.len() as u32
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1505,22 +1528,17 @@ impl<T: Config> Pallet<T> {
|
||||
// remove any delegation votes to our current target.
|
||||
Self::reduce_upstream_delegation(&target, conviction.votes(balance));
|
||||
voting.set_common(delegations, prior);
|
||||
}
|
||||
},
|
||||
Voting::Direct { votes, delegations, prior } => {
|
||||
// here we just ensure that we're currently idling with no votes recorded.
|
||||
ensure!(votes.is_empty(), Error::<T>::VotesExist);
|
||||
voting.set_common(delegations, prior);
|
||||
}
|
||||
},
|
||||
}
|
||||
let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance));
|
||||
// Extend the lock to `balance` (rather than setting it) since we don't know what other
|
||||
// votes are in place.
|
||||
T::Currency::extend_lock(
|
||||
DEMOCRACY_ID,
|
||||
&who,
|
||||
balance,
|
||||
WithdrawReasons::TRANSFER
|
||||
);
|
||||
T::Currency::extend_lock(DEMOCRACY_ID, &who, balance, WithdrawReasons::TRANSFER);
|
||||
Ok(votes)
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::Delegated(who, target));
|
||||
@@ -1535,25 +1553,18 @@ impl<T: Config> Pallet<T> {
|
||||
let mut old = Voting::default();
|
||||
sp_std::mem::swap(&mut old, voting);
|
||||
match old {
|
||||
Voting::Delegating {
|
||||
balance,
|
||||
target,
|
||||
conviction,
|
||||
delegations,
|
||||
mut prior,
|
||||
} => {
|
||||
Voting::Delegating { balance, target, conviction, delegations, mut prior } => {
|
||||
// remove any delegation votes to our current target.
|
||||
let votes = Self::reduce_upstream_delegation(&target, conviction.votes(balance));
|
||||
let votes =
|
||||
Self::reduce_upstream_delegation(&target, conviction.votes(balance));
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
let lock_periods = conviction.lock_periods().into();
|
||||
prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance);
|
||||
voting.set_common(delegations, prior);
|
||||
|
||||
Ok(votes)
|
||||
}
|
||||
Voting::Direct { .. } => {
|
||||
Err(Error::<T>::NotDelegating.into())
|
||||
}
|
||||
},
|
||||
Voting::Direct { .. } => Err(Error::<T>::NotDelegating.into()),
|
||||
}
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::Undelegated(who));
|
||||
@@ -1583,7 +1594,8 @@ impl<T: Config> Pallet<T> {
|
||||
) -> ReferendumIndex {
|
||||
let ref_index = Self::referendum_count();
|
||||
ReferendumCount::<T>::put(ref_index + 1);
|
||||
let status = ReferendumStatus { end, proposal_hash, threshold, delay, tally: Default::default() };
|
||||
let status =
|
||||
ReferendumStatus { end, proposal_hash, threshold, delay, tally: Default::default() };
|
||||
let item = ReferendumInfo::Ongoing(status);
|
||||
<ReferendumInfoOf<T>>::insert(ref_index, item);
|
||||
Self::deposit_event(Event::<T>::Started(ref_index, threshold));
|
||||
@@ -1596,7 +1608,8 @@ impl<T: Config> Pallet<T> {
|
||||
Self::launch_public(now).or_else(|_| Self::launch_external(now))
|
||||
} else {
|
||||
Self::launch_external(now).or_else(|_| Self::launch_public(now))
|
||||
}.map_err(|_| Error::<T>::NoneWaiting.into())
|
||||
}
|
||||
.map_err(|_| Error::<T>::NoneWaiting.into())
|
||||
}
|
||||
|
||||
/// Table the waiting external proposal for a vote, if there is one.
|
||||
@@ -1654,8 +1667,10 @@ impl<T: Config> Pallet<T> {
|
||||
debug_assert!(err_amount.is_zero());
|
||||
Self::deposit_event(Event::<T>::PreimageUsed(proposal_hash, provider, deposit));
|
||||
|
||||
let res = proposal.dispatch(frame_system::RawOrigin::Root.into())
|
||||
.map(|_| ()).map_err(|e| e.error);
|
||||
let res = proposal
|
||||
.dispatch(frame_system::RawOrigin::Root.into())
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.error);
|
||||
Self::deposit_event(Event::<T>::Executed(index, res));
|
||||
|
||||
Ok(())
|
||||
@@ -1685,10 +1700,14 @@ impl<T: Config> Pallet<T> {
|
||||
} else {
|
||||
let when = now + status.delay;
|
||||
// Note that we need the preimage now.
|
||||
Preimages::<T>::mutate_exists(&status.proposal_hash, |maybe_pre| match *maybe_pre {
|
||||
Some(PreimageStatus::Available { ref mut expiry, .. }) => *expiry = Some(when),
|
||||
ref mut a => *a = Some(PreimageStatus::Missing(when)),
|
||||
});
|
||||
Preimages::<T>::mutate_exists(
|
||||
&status.proposal_hash,
|
||||
|maybe_pre| match *maybe_pre {
|
||||
Some(PreimageStatus::Available { ref mut expiry, .. }) =>
|
||||
*expiry = Some(when),
|
||||
ref mut a => *a = Some(PreimageStatus::Missing(when)),
|
||||
},
|
||||
);
|
||||
|
||||
if T::Scheduler::schedule_named(
|
||||
(DEMOCRACY_ID, index).encode(),
|
||||
@@ -1697,7 +1716,9 @@ impl<T: Config> Pallet<T> {
|
||||
63,
|
||||
frame_system::RawOrigin::Root.into(),
|
||||
Call::enact_proposal(status.proposal_hash, index).into(),
|
||||
).is_err() {
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
frame_support::print("LOGIC ERROR: bake_referendum/schedule_named failed");
|
||||
}
|
||||
}
|
||||
@@ -1762,7 +1783,8 @@ impl<T: Config> Pallet<T> {
|
||||
// To decode the enum variant we only need the first byte.
|
||||
let mut buf = [0u8; 1];
|
||||
let key = <Preimages<T>>::hashed_key_for(proposal_hash);
|
||||
let bytes = sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::<T>::NotImminent)?;
|
||||
let bytes =
|
||||
sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::<T>::NotImminent)?;
|
||||
// The value may be smaller that 1 byte.
|
||||
let mut input = &buf[0..buf.len().min(bytes as usize)];
|
||||
|
||||
@@ -1772,7 +1794,7 @@ impl<T: Config> Pallet<T> {
|
||||
_ => {
|
||||
sp_runtime::print("Failed to decode `PreimageStatus` variant");
|
||||
Err(Error::<T>::NotImminent.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1790,7 +1812,8 @@ impl<T: Config> Pallet<T> {
|
||||
// * at most 5 bytes to decode a `Compact<u32>`
|
||||
let mut buf = [0u8; 6];
|
||||
let key = <Preimages<T>>::hashed_key_for(proposal_hash);
|
||||
let bytes = sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::<T>::PreimageMissing)?;
|
||||
let bytes =
|
||||
sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::<T>::PreimageMissing)?;
|
||||
// The value may be smaller that 6 bytes.
|
||||
let mut input = &buf[0..buf.len().min(bytes as usize)];
|
||||
|
||||
@@ -1799,15 +1822,17 @@ impl<T: Config> Pallet<T> {
|
||||
Ok(0) => return Err(Error::<T>::PreimageMissing.into()),
|
||||
_ => {
|
||||
sp_runtime::print("Failed to decode `PreimageStatus` variant");
|
||||
return Err(Error::<T>::PreimageMissing.into());
|
||||
}
|
||||
return Err(Error::<T>::PreimageMissing.into())
|
||||
},
|
||||
}
|
||||
|
||||
// Decode the length of the vector.
|
||||
let len = codec::Compact::<u32>::decode(&mut input).map_err(|_| {
|
||||
sp_runtime::print("Failed to decode `PreimageStatus` variant");
|
||||
DispatchError::from(Error::<T>::PreimageMissing)
|
||||
})?.0;
|
||||
let len = codec::Compact::<u32>::decode(&mut input)
|
||||
.map_err(|_| {
|
||||
sp_runtime::print("Failed to decode `PreimageStatus` variant");
|
||||
DispatchError::from(Error::<T>::PreimageMissing)
|
||||
})?
|
||||
.0;
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
@@ -1837,7 +1862,10 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
|
||||
// See `note_imminent_preimage`
|
||||
fn note_imminent_preimage_inner(who: T::AccountId, encoded_proposal: Vec<u8>) -> DispatchResult {
|
||||
fn note_imminent_preimage_inner(
|
||||
who: T::AccountId,
|
||||
encoded_proposal: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
let proposal_hash = T::Hashing::hash(&encoded_proposal[..]);
|
||||
Self::check_pre_image_is_missing(proposal_hash)?;
|
||||
let status = Preimages::<T>::get(&proposal_hash).ok_or(Error::<T>::NotImminent)?;
|
||||
@@ -1873,6 +1901,6 @@ fn decode_compact_u32_at(key: &[u8]) -> Option<u32> {
|
||||
sp_runtime::print("Failed to decode compact u32 at:");
|
||||
sp_runtime::print(key);
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use crate as pallet_democracy;
|
||||
use super::*;
|
||||
use crate as pallet_democracy;
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, parameter_types, ord_parameter_types,
|
||||
traits::{SortedMembers, OnInitialize, Filter, GenesisBuild},
|
||||
assert_noop, assert_ok, ord_parameter_types, parameter_types,
|
||||
traits::{Filter, GenesisBuild, OnInitialize, SortedMembers},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::{EnsureRoot, EnsureSignedBy};
|
||||
use pallet_balances::{BalanceLock, Error as BalancesError};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup, BadOrigin},
|
||||
testing::Header, Perbill,
|
||||
testing::Header,
|
||||
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
use pallet_balances::{BalanceLock, Error as BalancesError};
|
||||
use frame_system::{EnsureSignedBy, EnsureRoot};
|
||||
|
||||
mod cancellation;
|
||||
mod decoders;
|
||||
mod delegation;
|
||||
mod external_proposing;
|
||||
mod fast_tracking;
|
||||
@@ -42,7 +44,6 @@ mod preimage;
|
||||
mod public_proposals;
|
||||
mod scheduling;
|
||||
mod voting;
|
||||
mod decoders;
|
||||
|
||||
const AYE: Vote = Vote { aye: true, conviction: Conviction::None };
|
||||
const NAY: Vote = Vote { aye: false, conviction: Conviction::None };
|
||||
@@ -194,10 +195,14 @@ impl Config for Test {
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test>{
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
pallet_democracy::GenesisConfig::<Test>::default().assimilate_storage(&mut t).unwrap();
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pallet_democracy::GenesisConfig::<Test>::default()
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
@@ -246,19 +251,11 @@ fn set_balance_proposal_hash_and_note(value: u64) -> H256 {
|
||||
}
|
||||
|
||||
fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult {
|
||||
Democracy::propose(
|
||||
Origin::signed(who),
|
||||
set_balance_proposal_hash(value),
|
||||
delay,
|
||||
)
|
||||
Democracy::propose(Origin::signed(who), set_balance_proposal_hash(value), delay)
|
||||
}
|
||||
|
||||
fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchResult {
|
||||
Democracy::propose(
|
||||
Origin::signed(who),
|
||||
set_balance_proposal_hash_and_note(value),
|
||||
delay,
|
||||
)
|
||||
Democracy::propose(Origin::signed(who), set_balance_proposal_hash_and_note(value), delay)
|
||||
}
|
||||
|
||||
fn next_block() {
|
||||
|
||||
@@ -26,7 +26,7 @@ fn cancel_referendum_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::cancel_referendum(Origin::root(), r.into()));
|
||||
@@ -67,7 +67,7 @@ fn emergency_cancel_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
2
|
||||
2,
|
||||
);
|
||||
assert!(Democracy::referendum_status(r).is_ok());
|
||||
|
||||
@@ -81,7 +81,7 @@ fn emergency_cancel_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
2
|
||||
2,
|
||||
);
|
||||
assert!(Democracy::referendum_status(r).is_ok());
|
||||
assert_noop!(
|
||||
|
||||
@@ -66,7 +66,7 @@ fn pre_image() {
|
||||
assert_noop!(Democracy::check_pre_image_is_missing(key), Error::<Test>::NotImminent);
|
||||
|
||||
for l in vec![0, 10, 100, 1000u32] {
|
||||
let available = PreimageStatus::Available{
|
||||
let available = PreimageStatus::Available {
|
||||
data: (0..l).map(|i| i as u8).collect(),
|
||||
provider: 0,
|
||||
deposit: 0,
|
||||
@@ -76,8 +76,10 @@ fn pre_image() {
|
||||
|
||||
Preimages::<Test>::insert(key, available);
|
||||
assert_eq!(Democracy::pre_image_data_len(key), Ok(l));
|
||||
assert_noop!(Democracy::check_pre_image_is_missing(key),
|
||||
Error::<Test>::DuplicatePreimage);
|
||||
assert_noop!(
|
||||
Democracy::check_pre_image_is_missing(key),
|
||||
Error::<Test>::DuplicatePreimage
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,17 +34,17 @@ fn veto_external_works() {
|
||||
// cancelled.
|
||||
assert!(!<NextExternal<Test>>::exists());
|
||||
// fails - same proposal can't be resubmitted.
|
||||
assert_noop!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash(2),
|
||||
), Error::<Test>::ProposalBlacklisted);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
|
||||
fast_forward_to(1);
|
||||
// fails as we're still in cooloff period.
|
||||
assert_noop!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash(2),
|
||||
), Error::<Test>::ProposalBlacklisted);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
|
||||
fast_forward_to(2);
|
||||
// works; as we're out of the cooloff period.
|
||||
@@ -67,10 +67,10 @@ fn veto_external_works() {
|
||||
|
||||
fast_forward_to(3);
|
||||
// same proposal fails as we're still in cooloff
|
||||
assert_noop!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash(2),
|
||||
), Error::<Test>::ProposalBlacklisted);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(2),),
|
||||
Error::<Test>::ProposalBlacklisted
|
||||
);
|
||||
// different proposal works fine.
|
||||
assert_ok!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
@@ -96,10 +96,7 @@ fn external_blacklisting_should_work() {
|
||||
assert_noop!(Democracy::referendum_status(0), Error::<Test>::ReferendumInvalid);
|
||||
|
||||
assert_noop!(
|
||||
Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
),
|
||||
Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash_and_note(2),),
|
||||
Error::<Test>::ProposalBlacklisted,
|
||||
);
|
||||
});
|
||||
@@ -110,20 +107,17 @@ fn external_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(
|
||||
Origin::signed(1),
|
||||
set_balance_proposal_hash(2),
|
||||
),
|
||||
Democracy::external_propose(Origin::signed(1), set_balance_proposal_hash(2),),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
));
|
||||
assert_noop!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash(1),
|
||||
), Error::<Test>::DuplicateProposal);
|
||||
assert_noop!(
|
||||
Democracy::external_propose(Origin::signed(2), set_balance_proposal_hash(1),),
|
||||
Error::<Test>::DuplicateProposal
|
||||
);
|
||||
fast_forward_to(2);
|
||||
assert_eq!(
|
||||
Democracy::referendum_status(0),
|
||||
@@ -143,10 +137,7 @@ fn external_majority_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(
|
||||
Democracy::external_propose_majority(
|
||||
Origin::signed(1),
|
||||
set_balance_proposal_hash(2)
|
||||
),
|
||||
Democracy::external_propose_majority(Origin::signed(1), set_balance_proposal_hash(2)),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
@@ -172,10 +163,7 @@ fn external_default_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(
|
||||
Democracy::external_propose_default(
|
||||
Origin::signed(3),
|
||||
set_balance_proposal_hash(2)
|
||||
),
|
||||
Democracy::external_propose_default(Origin::signed(3), set_balance_proposal_hash(2)),
|
||||
BadOrigin,
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_default(
|
||||
@@ -196,7 +184,6 @@ fn external_default_referendum_works() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn external_and_public_interleaving_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -222,9 +209,9 @@ fn external_and_public_interleaving_works() {
|
||||
);
|
||||
// replenish external
|
||||
assert_ok!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(3),
|
||||
));
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(3),
|
||||
));
|
||||
|
||||
fast_forward_to(4);
|
||||
|
||||
@@ -256,9 +243,9 @@ fn external_and_public_interleaving_works() {
|
||||
);
|
||||
// replenish external
|
||||
assert_ok!(Democracy::external_propose(
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(5),
|
||||
));
|
||||
Origin::signed(2),
|
||||
set_balance_proposal_hash_and_note(5),
|
||||
));
|
||||
|
||||
fast_forward_to(8);
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ fn fast_track_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
let h = set_balance_proposal_hash_and_note(2);
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::<Test>::ProposalMissing);
|
||||
assert_noop!(
|
||||
Democracy::fast_track(Origin::signed(5), h, 3, 2),
|
||||
Error::<Test>::ProposalMissing
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
Origin::signed(3),
|
||||
set_balance_proposal_hash_and_note(2)
|
||||
@@ -49,14 +52,20 @@ fn instant_referendum_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
let h = set_balance_proposal_hash_and_note(2);
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), Error::<Test>::ProposalMissing);
|
||||
assert_noop!(
|
||||
Democracy::fast_track(Origin::signed(5), h, 3, 2),
|
||||
Error::<Test>::ProposalMissing
|
||||
);
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
Origin::signed(3),
|
||||
set_balance_proposal_hash_and_note(2)
|
||||
));
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), BadOrigin);
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(5), h, 1, 0), BadOrigin);
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(6), h, 1, 0), Error::<Test>::InstantNotAllowed);
|
||||
assert_noop!(
|
||||
Democracy::fast_track(Origin::signed(6), h, 1, 0),
|
||||
Error::<Test>::InstantNotAllowed
|
||||
);
|
||||
INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true);
|
||||
assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0));
|
||||
assert_eq!(
|
||||
|
||||
@@ -23,23 +23,19 @@ use std::convert::TryFrom;
|
||||
fn aye(x: u8, balance: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard {
|
||||
vote: Vote { aye: true, conviction: Conviction::try_from(x).unwrap() },
|
||||
balance
|
||||
balance,
|
||||
}
|
||||
}
|
||||
|
||||
fn nay(x: u8, balance: u64) -> AccountVote<u64> {
|
||||
AccountVote::Standard {
|
||||
vote: Vote { aye: false, conviction: Conviction::try_from(x).unwrap() },
|
||||
balance
|
||||
balance,
|
||||
}
|
||||
}
|
||||
|
||||
fn the_lock(amount: u64) -> BalanceLock<u64> {
|
||||
BalanceLock {
|
||||
id: DEMOCRACY_ID,
|
||||
amount,
|
||||
reasons: pallet_balances::Reasons::Misc,
|
||||
}
|
||||
BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc }
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -50,7 +46,7 @@ fn lock_voting_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20)));
|
||||
@@ -74,7 +70,10 @@ fn lock_voting_should_work() {
|
||||
assert_ok!(Democracy::unlock(Origin::signed(2), 5));
|
||||
|
||||
// 2, 3, 4 got their way with the vote, so they cannot be reaped by others.
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 2, r), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 2, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
// However, they can be unvoted by the owner, though it will make no difference to the lock.
|
||||
assert_ok!(Democracy::remove_vote(Origin::signed(2), r));
|
||||
assert_ok!(Democracy::unlock(Origin::signed(2), 2));
|
||||
@@ -86,10 +85,12 @@ fn lock_voting_should_work() {
|
||||
assert_eq!(Balances::locks(5), vec![]);
|
||||
assert_eq!(Balances::free_balance(42), 2);
|
||||
|
||||
|
||||
fast_forward_to(5);
|
||||
// No change yet...
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 4, r), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 4, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(Origin::signed(1), 4));
|
||||
assert_eq!(Balances::locks(4), vec![the_lock(40)]);
|
||||
fast_forward_to(6);
|
||||
@@ -99,7 +100,10 @@ fn lock_voting_should_work() {
|
||||
assert_eq!(Balances::locks(4), vec![]);
|
||||
|
||||
fast_forward_to(9);
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 3, r), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 3, r),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(Origin::signed(1), 3));
|
||||
assert_eq!(Balances::locks(3), vec![the_lock(30)]);
|
||||
fast_forward_to(10);
|
||||
@@ -145,7 +149,7 @@ fn lock_voting_should_work_with_delegation() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, nay(5, 10)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, aye(4, 20)));
|
||||
@@ -168,7 +172,7 @@ fn setup_three_referenda() -> (u32, u32, u32) {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SimpleMajority,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r1, aye(4, 10)));
|
||||
|
||||
@@ -176,7 +180,7 @@ fn setup_three_referenda() -> (u32, u32, u32) {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SimpleMajority,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r2, aye(3, 20)));
|
||||
|
||||
@@ -184,7 +188,7 @@ fn setup_three_referenda() -> (u32, u32, u32) {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SimpleMajority,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r3, aye(2, 50)));
|
||||
|
||||
@@ -202,7 +206,10 @@ fn prior_lockvotes_should_be_enforced() {
|
||||
// r.2 locked 50 until #6.
|
||||
|
||||
fast_forward_to(5);
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.2), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 5, r.2),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(Origin::signed(5), 5));
|
||||
assert_eq!(Balances::locks(5), vec![the_lock(50)]);
|
||||
fast_forward_to(6);
|
||||
@@ -210,7 +217,10 @@ fn prior_lockvotes_should_be_enforced() {
|
||||
assert_ok!(Democracy::unlock(Origin::signed(5), 5));
|
||||
assert_eq!(Balances::locks(5), vec![the_lock(20)]);
|
||||
fast_forward_to(9);
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.1), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 5, r.1),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(Origin::signed(5), 5));
|
||||
assert_eq!(Balances::locks(5), vec![the_lock(20)]);
|
||||
fast_forward_to(10);
|
||||
@@ -218,7 +228,10 @@ fn prior_lockvotes_should_be_enforced() {
|
||||
assert_ok!(Democracy::unlock(Origin::signed(5), 5));
|
||||
assert_eq!(Balances::locks(5), vec![the_lock(10)]);
|
||||
fast_forward_to(17);
|
||||
assert_noop!(Democracy::remove_other_vote(Origin::signed(1), 5, r.0), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Democracy::remove_other_vote(Origin::signed(1), 5, r.0),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Democracy::unlock(Origin::signed(5), 5));
|
||||
assert_eq!(Balances::locks(5), vec![the_lock(10)]);
|
||||
fast_forward_to(18);
|
||||
@@ -296,7 +309,7 @@ fn locks_should_persist_from_voting_to_delegation() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SimpleMajority,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r, aye(4, 10)));
|
||||
fast_forward_to(2);
|
||||
|
||||
@@ -26,7 +26,7 @@ fn missing_preimage_should_fail() {
|
||||
2,
|
||||
set_balance_proposal_hash(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
|
||||
@@ -43,8 +43,11 @@ fn preimage_deposit_should_be_required_and_returned() {
|
||||
// fee of 100 is too much.
|
||||
PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 100);
|
||||
assert_noop!(
|
||||
if operational { Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500]) }
|
||||
else { Democracy::note_preimage(Origin::signed(6), vec![0; 500]) },
|
||||
if operational {
|
||||
Democracy::note_preimage_operational(Origin::signed(6), vec![0; 500])
|
||||
} else {
|
||||
Democracy::note_preimage(Origin::signed(6), vec![0; 500])
|
||||
},
|
||||
BalancesError::<Test, _>::InsufficientBalance,
|
||||
);
|
||||
// fee of 1 is reasonable.
|
||||
@@ -53,7 +56,7 @@ fn preimage_deposit_should_be_required_and_returned() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
|
||||
@@ -72,10 +75,11 @@ fn preimage_deposit_should_be_required_and_returned() {
|
||||
fn preimage_deposit_should_be_reapable_earlier_by_owner() {
|
||||
new_test_ext_execute_with_cond(|operational| {
|
||||
PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1);
|
||||
assert_ok!(
|
||||
if operational { Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) }
|
||||
else { Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) }
|
||||
);
|
||||
assert_ok!(if operational {
|
||||
Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2))
|
||||
} else {
|
||||
Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))
|
||||
});
|
||||
|
||||
assert_eq!(Balances::reserved_balance(6), 12);
|
||||
|
||||
@@ -85,7 +89,11 @@ fn preimage_deposit_should_be_reapable_earlier_by_owner() {
|
||||
Error::<Test>::TooEarly
|
||||
);
|
||||
next_block();
|
||||
assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::MAX));
|
||||
assert_ok!(Democracy::reap_preimage(
|
||||
Origin::signed(6),
|
||||
set_balance_proposal_hash(2),
|
||||
u32::MAX
|
||||
));
|
||||
|
||||
assert_eq!(Balances::free_balance(6), 60);
|
||||
assert_eq!(Balances::reserved_balance(6), 0);
|
||||
@@ -96,27 +104,32 @@ fn preimage_deposit_should_be_reapable_earlier_by_owner() {
|
||||
fn preimage_deposit_should_be_reapable() {
|
||||
new_test_ext_execute_with_cond(|operational| {
|
||||
assert_noop!(
|
||||
Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX),
|
||||
Error::<Test>::PreimageMissing
|
||||
);
|
||||
Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX),
|
||||
Error::<Test>::PreimageMissing
|
||||
);
|
||||
|
||||
PREIMAGE_BYTE_DEPOSIT.with(|v| *v.borrow_mut() = 1);
|
||||
assert_ok!(
|
||||
if operational { Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2)) }
|
||||
else { Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2)) }
|
||||
);
|
||||
assert_ok!(if operational {
|
||||
Democracy::note_preimage_operational(Origin::signed(6), set_balance_proposal(2))
|
||||
} else {
|
||||
Democracy::note_preimage(Origin::signed(6), set_balance_proposal(2))
|
||||
});
|
||||
assert_eq!(Balances::reserved_balance(6), 12);
|
||||
|
||||
next_block();
|
||||
next_block();
|
||||
next_block();
|
||||
assert_noop!(
|
||||
Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX),
|
||||
Error::<Test>::TooEarly
|
||||
);
|
||||
Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX),
|
||||
Error::<Test>::TooEarly
|
||||
);
|
||||
|
||||
next_block();
|
||||
assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::MAX));
|
||||
assert_ok!(Democracy::reap_preimage(
|
||||
Origin::signed(5),
|
||||
set_balance_proposal_hash(2),
|
||||
u32::MAX
|
||||
));
|
||||
assert_eq!(Balances::reserved_balance(6), 0);
|
||||
assert_eq!(Balances::free_balance(6), 48);
|
||||
assert_eq!(Balances::free_balance(5), 62);
|
||||
@@ -132,13 +145,19 @@ fn noting_imminent_preimage_for_free_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
1
|
||||
1,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
|
||||
assert_noop!(
|
||||
if operational { Democracy::note_imminent_preimage_operational(Origin::signed(6), set_balance_proposal(2)) }
|
||||
else { Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2)) },
|
||||
if operational {
|
||||
Democracy::note_imminent_preimage_operational(
|
||||
Origin::signed(6),
|
||||
set_balance_proposal(2),
|
||||
)
|
||||
} else {
|
||||
Democracy::note_imminent_preimage(Origin::signed(6), set_balance_proposal(2))
|
||||
},
|
||||
Error::<Test>::NotImminent
|
||||
);
|
||||
|
||||
@@ -161,7 +180,10 @@ fn reaping_imminent_preimage_should_fail() {
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
next_block();
|
||||
next_block();
|
||||
assert_noop!(Democracy::reap_preimage(Origin::signed(6), h, u32::MAX), Error::<Test>::Imminent);
|
||||
assert_noop!(
|
||||
Democracy::reap_preimage(Origin::signed(6), h, u32::MAX),
|
||||
Error::<Test>::Imminent
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,7 +196,7 @@ fn note_imminent_preimage_can_only_be_successful_once() {
|
||||
2,
|
||||
set_balance_proposal_hash(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
1
|
||||
1,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
next_block();
|
||||
|
||||
@@ -89,10 +89,7 @@ fn poor_seconder_should_not_work() {
|
||||
fn invalid_seconds_upper_bound_should_not_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(propose_set_balance_and_note(1, 2, 5));
|
||||
assert_noop!(
|
||||
Democracy::second(Origin::signed(2), 0, 0),
|
||||
Error::<Test>::WrongUpperBound
|
||||
);
|
||||
assert_noop!(Democracy::second(Origin::signed(2), 0, 0), Error::<Test>::WrongUpperBound);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ fn simple_passing_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
@@ -43,7 +43,7 @@ fn simple_failing_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, nay(1)));
|
||||
assert_eq!(tally(r), Tally { ayes: 0, nays: 1, turnout: 10 });
|
||||
@@ -62,13 +62,13 @@ fn ooo_inject_referendums_should_work() {
|
||||
3,
|
||||
set_balance_proposal_hash_and_note(3),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
let r2 = Democracy::inject_referendum(
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r2, aye(1)));
|
||||
@@ -92,7 +92,7 @@ fn delayed_enactment_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
1
|
||||
1,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)));
|
||||
|
||||
@@ -23,7 +23,10 @@ use super::*;
|
||||
fn overvoting_should_fail() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let r = begin_referendum();
|
||||
assert_noop!(Democracy::vote(Origin::signed(1), r, aye(2)), Error::<Test>::InsufficientFunds);
|
||||
assert_noop!(
|
||||
Democracy::vote(Origin::signed(1), r, aye(2)),
|
||||
Error::<Test>::InsufficientFunds
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +105,7 @@ fn controversial_voting_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, big_aye(1)));
|
||||
@@ -128,7 +131,7 @@ fn controversial_low_turnout_voting_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(6), r, big_aye(6)));
|
||||
@@ -152,7 +155,7 @@ fn passing_low_turnout_voting_should_work() {
|
||||
2,
|
||||
set_balance_proposal_hash_and_note(2),
|
||||
VoteThreshold::SuperMajorityApprove,
|
||||
0
|
||||
0,
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(4), r, big_aye(4)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(5), r, big_nay(5)));
|
||||
|
||||
@@ -17,29 +17,31 @@
|
||||
|
||||
//! Miscellaneous additional datatypes.
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::traits::{Zero, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, Saturating};
|
||||
use crate::{Vote, VoteThreshold, AccountVote, Conviction};
|
||||
use crate::{AccountVote, Conviction, Vote, VoteThreshold};
|
||||
use codec::{Decode, Encode};
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// Info regarding an ongoing referendum.
|
||||
#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct Tally<Balance> {
|
||||
/// The number of aye votes, expressed in terms of post-conviction lock-vote.
|
||||
pub (crate) ayes: Balance,
|
||||
pub(crate) ayes: Balance,
|
||||
/// The number of nay votes, expressed in terms of post-conviction lock-vote.
|
||||
pub (crate) nays: Balance,
|
||||
pub(crate) nays: Balance,
|
||||
/// The amount of funds currently expressing its opinion. Pre-conviction.
|
||||
pub (crate) turnout: Balance,
|
||||
pub(crate) turnout: Balance,
|
||||
}
|
||||
|
||||
/// Amount of votes and capital placed in delegation for an account.
|
||||
#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct Delegations<Balance> {
|
||||
/// The number of votes (this is post-conviction).
|
||||
pub (crate) votes: Balance,
|
||||
pub(crate) votes: Balance,
|
||||
/// The amount of raw capital, used for the turnout.
|
||||
pub (crate) capital: Balance,
|
||||
pub(crate) capital: Balance,
|
||||
}
|
||||
|
||||
impl<Balance: Saturating> Saturating for Delegations<Balance> {
|
||||
@@ -65,22 +67,24 @@ impl<Balance: Saturating> Saturating for Delegations<Balance> {
|
||||
}
|
||||
|
||||
fn saturating_pow(self, exp: usize) -> Self {
|
||||
Self {
|
||||
votes: self.votes.saturating_pow(exp),
|
||||
capital: self.capital.saturating_pow(exp),
|
||||
}
|
||||
Self { votes: self.votes.saturating_pow(exp), capital: self.capital.saturating_pow(exp) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: From<u8> + Zero + Copy + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + Bounded +
|
||||
Saturating
|
||||
> Tally<Balance> {
|
||||
Balance: From<u8>
|
||||
+ Zero
|
||||
+ Copy
|
||||
+ CheckedAdd
|
||||
+ CheckedSub
|
||||
+ CheckedMul
|
||||
+ CheckedDiv
|
||||
+ Bounded
|
||||
+ Saturating,
|
||||
> Tally<Balance>
|
||||
{
|
||||
/// Create a new tally.
|
||||
pub fn new(
|
||||
vote: Vote,
|
||||
balance: Balance,
|
||||
) -> Self {
|
||||
pub fn new(vote: Vote, balance: Balance) -> Self {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
Self {
|
||||
ayes: if vote.aye { votes } else { Zero::zero() },
|
||||
@@ -90,10 +94,7 @@ impl<
|
||||
}
|
||||
|
||||
/// Add an account's vote into the tally.
|
||||
pub fn add(
|
||||
&mut self,
|
||||
vote: AccountVote<Balance>,
|
||||
) -> Option<()> {
|
||||
pub fn add(&mut self, vote: AccountVote<Balance>) -> Option<()> {
|
||||
match vote {
|
||||
AccountVote::Standard { vote, balance } => {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
@@ -102,23 +103,20 @@ impl<
|
||||
true => self.ayes = self.ayes.checked_add(&votes)?,
|
||||
false => self.nays = self.nays.checked_add(&votes)?,
|
||||
}
|
||||
}
|
||||
},
|
||||
AccountVote::Split { aye, nay } => {
|
||||
let aye = Conviction::None.votes(aye);
|
||||
let nay = Conviction::None.votes(nay);
|
||||
self.turnout = self.turnout.checked_add(&aye.capital)?.checked_add(&nay.capital)?;
|
||||
self.ayes = self.ayes.checked_add(&aye.votes)?;
|
||||
self.nays = self.nays.checked_add(&nay.votes)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Remove an account's vote from the tally.
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
vote: AccountVote<Balance>,
|
||||
) -> Option<()> {
|
||||
pub fn remove(&mut self, vote: AccountVote<Balance>) -> Option<()> {
|
||||
match vote {
|
||||
AccountVote::Standard { vote, balance } => {
|
||||
let Delegations { votes, capital } = vote.conviction.votes(balance);
|
||||
@@ -127,14 +125,14 @@ impl<
|
||||
true => self.ayes = self.ayes.checked_sub(&votes)?,
|
||||
false => self.nays = self.nays.checked_sub(&votes)?,
|
||||
}
|
||||
}
|
||||
},
|
||||
AccountVote::Split { aye, nay } => {
|
||||
let aye = Conviction::None.votes(aye);
|
||||
let nay = Conviction::None.votes(nay);
|
||||
self.turnout = self.turnout.checked_sub(&aye.capital)?.checked_sub(&nay.capital)?;
|
||||
self.ayes = self.ayes.checked_sub(&aye.votes)?;
|
||||
self.nays = self.nays.checked_sub(&nay.votes)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
@@ -164,15 +162,15 @@ impl<
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ReferendumStatus<BlockNumber, Hash, Balance> {
|
||||
/// When voting on this referendum will end.
|
||||
pub (crate) end: BlockNumber,
|
||||
pub(crate) end: BlockNumber,
|
||||
/// The hash of the proposal being voted on.
|
||||
pub (crate) proposal_hash: Hash,
|
||||
pub(crate) proposal_hash: Hash,
|
||||
/// The thresholding mechanism to determine whether it passed.
|
||||
pub (crate) threshold: VoteThreshold,
|
||||
pub(crate) threshold: VoteThreshold,
|
||||
/// The delay (in blocks) to wait after a successful referendum before deploying.
|
||||
pub (crate) delay: BlockNumber,
|
||||
pub(crate) delay: BlockNumber,
|
||||
/// The current tally of votes in this referendum.
|
||||
pub (crate) tally: Tally<Balance>,
|
||||
pub(crate) tally: Tally<Balance>,
|
||||
}
|
||||
|
||||
/// Info regarding a referendum, present or past.
|
||||
@@ -181,7 +179,7 @@ pub enum ReferendumInfo<BlockNumber, Hash, Balance> {
|
||||
/// Referendum is happening, the arg is the block number at which it will end.
|
||||
Ongoing(ReferendumStatus<BlockNumber, Hash, Balance>),
|
||||
/// Referendum finished at `end`, and has been `approved` or rejected.
|
||||
Finished{approved: bool, end: BlockNumber},
|
||||
Finished { approved: bool, end: BlockNumber },
|
||||
}
|
||||
|
||||
impl<BlockNumber, Hash, Balance: Default> ReferendumInfo<BlockNumber, Hash, Balance> {
|
||||
@@ -192,7 +190,7 @@ impl<BlockNumber, Hash, Balance: Default> ReferendumInfo<BlockNumber, Hash, Bala
|
||||
threshold: VoteThreshold,
|
||||
delay: BlockNumber,
|
||||
) -> Self {
|
||||
let s = ReferendumStatus{ end, proposal_hash, threshold, delay, tally: Tally::default() };
|
||||
let s = ReferendumStatus { end, proposal_hash, threshold, delay, tally: Tally::default() };
|
||||
ReferendumInfo::Ongoing(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
|
||||
//! The vote datatype.
|
||||
|
||||
use sp_std::{prelude::*, result::Result, convert::TryFrom};
|
||||
use codec::{Encode, EncodeLike, Decode, Output, Input};
|
||||
use sp_runtime::{RuntimeDebug, traits::{Saturating, Zero}};
|
||||
use crate::{Conviction, ReferendumIndex, Delegations};
|
||||
use crate::{Conviction, Delegations, ReferendumIndex};
|
||||
use codec::{Decode, Encode, EncodeLike, Input, Output};
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{convert::TryFrom, prelude::*, result::Result};
|
||||
|
||||
/// A number of lock periods, plus a vote, one way or the other.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)]
|
||||
@@ -136,7 +139,9 @@ pub enum Voting<Balance, AccountId, BlockNumber> {
|
||||
},
|
||||
}
|
||||
|
||||
impl<Balance: Default, AccountId, BlockNumber: Zero> Default for Voting<Balance, AccountId, BlockNumber> {
|
||||
impl<Balance: Default, AccountId, BlockNumber: Zero> Default
|
||||
for Voting<Balance, AccountId, BlockNumber>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Voting::Direct {
|
||||
votes: Vec::new(),
|
||||
@@ -146,31 +151,30 @@ impl<Balance: Default, AccountId, BlockNumber: Zero> Default for Voting<Balance,
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: Saturating + Ord + Zero + Copy,
|
||||
BlockNumber: Ord + Copy + Zero,
|
||||
AccountId,
|
||||
> Voting<Balance, AccountId, BlockNumber> {
|
||||
impl<Balance: Saturating + Ord + Zero + Copy, BlockNumber: Ord + Copy + Zero, AccountId>
|
||||
Voting<Balance, AccountId, BlockNumber>
|
||||
{
|
||||
pub fn rejig(&mut self, now: BlockNumber) {
|
||||
match self {
|
||||
Voting::Direct { prior, .. } => prior,
|
||||
Voting::Delegating { prior, .. } => prior,
|
||||
}.rejig(now);
|
||||
}
|
||||
.rejig(now);
|
||||
}
|
||||
|
||||
/// The amount of this account's balance that much currently be locked due to voting.
|
||||
pub fn locked_balance(&self) -> Balance {
|
||||
match self {
|
||||
Voting::Direct { votes, prior, .. } => votes.iter()
|
||||
.map(|i| i.1.balance())
|
||||
.fold(prior.locked(), |a, i| a.max(i)),
|
||||
Voting::Direct { votes, prior, .. } =>
|
||||
votes.iter().map(|i| i.1.balance()).fold(prior.locked(), |a, i| a.max(i)),
|
||||
Voting::Delegating { balance, .. } => *balance,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_common(&mut self,
|
||||
pub fn set_common(
|
||||
&mut self,
|
||||
delegations: Delegations<Balance>,
|
||||
prior: PriorLock<BlockNumber, Balance>
|
||||
prior: PriorLock<BlockNumber, Balance>,
|
||||
) {
|
||||
let (d, p) = match self {
|
||||
Voting::Direct { ref mut delegations, ref mut prior, .. } => (delegations, prior),
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
//! Voting thresholds.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_runtime::traits::{Zero, IntegerSquareRoot};
|
||||
use sp_std::ops::{Add, Mul, Div, Rem};
|
||||
use crate::Tally;
|
||||
use codec::{Decode, Encode};
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_runtime::traits::{IntegerSquareRoot, Zero};
|
||||
use sp_std::ops::{Add, Div, Mul, Rem};
|
||||
|
||||
/// A means of determining if a vote is past pass threshold.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug)]
|
||||
@@ -43,25 +43,32 @@ pub trait Approved<Balance> {
|
||||
}
|
||||
|
||||
/// Return `true` iff `n1 / d1 < n2 / d2`. `d1` and `d2` may not be zero.
|
||||
fn compare_rationals<T: Zero + Mul<T, Output = T> + Div<T, Output = T> + Rem<T, Output = T> + Ord + Copy>(mut n1: T, mut d1: T, mut n2: T, mut d2: T) -> bool {
|
||||
fn compare_rationals<
|
||||
T: Zero + Mul<T, Output = T> + Div<T, Output = T> + Rem<T, Output = T> + Ord + Copy,
|
||||
>(
|
||||
mut n1: T,
|
||||
mut d1: T,
|
||||
mut n2: T,
|
||||
mut d2: T,
|
||||
) -> bool {
|
||||
// Uses a continued fractional representation for a non-overflowing compare.
|
||||
// Detailed at https://janmr.com/blog/2014/05/comparing-rational-numbers-without-overflow/.
|
||||
loop {
|
||||
let q1 = n1 / d1;
|
||||
let q2 = n2 / d2;
|
||||
if q1 < q2 {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
if q2 < q1 {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
let r1 = n1 % d1;
|
||||
let r2 = n2 % d2;
|
||||
if r2.is_zero() {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if r1.is_zero() {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
n1 = d2;
|
||||
n2 = d1;
|
||||
@@ -71,14 +78,22 @@ fn compare_rationals<T: Zero + Mul<T, Output = T> + Div<T, Output = T> + Rem<T,
|
||||
}
|
||||
|
||||
impl<
|
||||
Balance: IntegerSquareRoot + Zero + Ord + Add<Balance, Output = Balance>
|
||||
+ Mul<Balance, Output = Balance> + Div<Balance, Output = Balance>
|
||||
+ Rem<Balance, Output = Balance> + Copy,
|
||||
> Approved<Balance> for VoteThreshold {
|
||||
Balance: IntegerSquareRoot
|
||||
+ Zero
|
||||
+ Ord
|
||||
+ Add<Balance, Output = Balance>
|
||||
+ Mul<Balance, Output = Balance>
|
||||
+ Div<Balance, Output = Balance>
|
||||
+ Rem<Balance, Output = Balance>
|
||||
+ Copy,
|
||||
> Approved<Balance> for VoteThreshold
|
||||
{
|
||||
fn approved(&self, tally: Tally<Balance>, electorate: Balance) -> bool {
|
||||
let sqrt_voters = tally.turnout.integer_sqrt();
|
||||
let sqrt_electorate = electorate.integer_sqrt();
|
||||
if sqrt_voters.is_zero() { return false; }
|
||||
if sqrt_voters.is_zero() {
|
||||
return false
|
||||
}
|
||||
match *self {
|
||||
VoteThreshold::SuperMajorityApprove =>
|
||||
compare_rationals(tally.nays, sqrt_voters, tally.ayes, sqrt_electorate),
|
||||
@@ -95,7 +110,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn should_work() {
|
||||
assert!(!VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 60, nays: 50, turnout: 110}, 210));
|
||||
assert!(VoteThreshold::SuperMajorityApprove.approved(Tally{ayes: 100, nays: 50, turnout: 150}, 210));
|
||||
assert!(!VoteThreshold::SuperMajorityApprove
|
||||
.approved(Tally { ayes: 60, nays: 50, turnout: 110 }, 210));
|
||||
assert!(VoteThreshold::SuperMajorityApprove
|
||||
.approved(Tally { ayes: 100, nays: 50, turnout: 150 }, 210));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! Two phase election pallet benchmarking.
|
||||
|
||||
use super::*;
|
||||
use crate::{Pallet as MultiPhase, unsigned::IndexAssignmentOf};
|
||||
use crate::{unsigned::IndexAssignmentOf, Pallet as MultiPhase};
|
||||
use frame_benchmarking::{account, impl_benchmark_test_suite};
|
||||
use frame_support::{assert_ok, traits::Hooks};
|
||||
use frame_system::RawOrigin;
|
||||
@@ -53,8 +53,9 @@ fn solution_with_size<T: Config>(
|
||||
let stake: VoteWeight = ed.max(One::one()).saturating_mul(100);
|
||||
|
||||
// first generates random targets.
|
||||
let targets: Vec<T::AccountId> =
|
||||
(0..size.targets).map(|i| frame_benchmarking::account("Targets", i, SEED)).collect();
|
||||
let targets: Vec<T::AccountId> = (0..size.targets)
|
||||
.map(|i| frame_benchmarking::account("Targets", i, SEED))
|
||||
.collect();
|
||||
|
||||
let mut rng = SmallRng::seed_from_u64(SEED.into());
|
||||
|
||||
@@ -80,8 +81,11 @@ fn solution_with_size<T: Config>(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// rest of the voters. They can only vote for non-winners.
|
||||
let non_winners =
|
||||
targets.iter().filter(|t| !winners.contains(t)).cloned().collect::<Vec<T::AccountId>>();
|
||||
let non_winners = targets
|
||||
.iter()
|
||||
.filter(|t| !winners.contains(t))
|
||||
.cloned()
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
let rest_voters = (active_voters_count..size.voters)
|
||||
.map(|i| {
|
||||
let votes = (&non_winners)
|
||||
@@ -147,14 +151,22 @@ fn set_up_data_provider<T: Config>(v: u32, t: u32) {
|
||||
// number of votes in snapshot.
|
||||
|
||||
T::DataProvider::clear();
|
||||
log!(info, "setting up with voters = {} [degree = {}], targets = {}", v, T::DataProvider::MAXIMUM_VOTES_PER_VOTER, t);
|
||||
log!(
|
||||
info,
|
||||
"setting up with voters = {} [degree = {}], targets = {}",
|
||||
v,
|
||||
T::DataProvider::MAXIMUM_VOTES_PER_VOTER,
|
||||
t
|
||||
);
|
||||
|
||||
// fill targets.
|
||||
let mut targets = (0..t).map(|i| {
|
||||
let target = frame_benchmarking::account::<T::AccountId>("Target", i, SEED);
|
||||
T::DataProvider::add_target(target.clone());
|
||||
target
|
||||
}).collect::<Vec<_>>();
|
||||
let mut targets = (0..t)
|
||||
.map(|i| {
|
||||
let target = frame_benchmarking::account::<T::AccountId>("Target", i, SEED);
|
||||
T::DataProvider::add_target(target.clone());
|
||||
target
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// we should always have enough voters to fill.
|
||||
assert!(targets.len() > T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
|
||||
targets.truncate(T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
//! Some helper functions/macros for this crate.
|
||||
|
||||
use super::{Config, VoteWeight, CompactVoterIndexOf, CompactTargetIndexOf};
|
||||
use super::{CompactTargetIndexOf, CompactVoterIndexOf, Config, VoteWeight};
|
||||
use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, prelude::*};
|
||||
|
||||
#[macro_export]
|
||||
@@ -58,7 +58,9 @@ pub fn voter_index_fn<T: Config>(
|
||||
cache: &BTreeMap<T::AccountId, usize>,
|
||||
) -> impl Fn(&T::AccountId) -> Option<CompactVoterIndexOf<T>> + '_ {
|
||||
move |who| {
|
||||
cache.get(who).and_then(|i| <usize as TryInto<CompactVoterIndexOf<T>>>::try_into(*i).ok())
|
||||
cache
|
||||
.get(who)
|
||||
.and_then(|i| <usize as TryInto<CompactVoterIndexOf<T>>>::try_into(*i).ok())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +72,9 @@ pub fn voter_index_fn_owned<T: Config>(
|
||||
cache: BTreeMap<T::AccountId, usize>,
|
||||
) -> impl Fn(&T::AccountId) -> Option<CompactVoterIndexOf<T>> {
|
||||
move |who| {
|
||||
cache.get(who).and_then(|i| <usize as TryInto<CompactVoterIndexOf<T>>>::try_into(*i).ok())
|
||||
cache
|
||||
.get(who)
|
||||
.and_then(|i| <usize as TryInto<CompactVoterIndexOf<T>>>::try_into(*i).ok())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +177,11 @@ pub fn stake_of_fn_linear<T: Config>(
|
||||
snapshot: &Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)>,
|
||||
) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
|
||||
move |who| {
|
||||
snapshot.iter().find(|(x, _, _)| x == who).map(|(_, x, _)| *x).unwrap_or_default()
|
||||
snapshot
|
||||
.iter()
|
||||
.find(|(x, _, _)| x == who)
|
||||
.map(|(_, x, _)| *x)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
//!
|
||||
//! ### Signed Phase
|
||||
//!
|
||||
//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A
|
||||
//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A
|
||||
//! deposit is reserved, based on the size of the solution, for the cost of keeping this solution
|
||||
//! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A
|
||||
//! maximum of `pallet::Config::MaxSignedSubmissions` solutions are stored. The queue is always
|
||||
@@ -228,34 +228,31 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_election_provider_support::{onchain, ElectionDataProvider, ElectionProvider};
|
||||
use frame_support::{
|
||||
dispatch::DispatchResultWithPostInfo,
|
||||
ensure,
|
||||
traits::{Currency, Get, ReservableCurrency, OnUnbalanced},
|
||||
traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::{ensure_none, offchain::SendTransactionTypes};
|
||||
use frame_election_provider_support::{ElectionDataProvider, ElectionProvider, onchain};
|
||||
use sp_arithmetic::{
|
||||
traits::{CheckedAdd, Zero},
|
||||
UpperOf,
|
||||
};
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, CompactSolution, ElectionScore,
|
||||
EvaluateSupport, PerThing128, Supports, VoteWeight,
|
||||
assignment_ratio_to_staked_normalized, CompactSolution, ElectionScore, EvaluateSupport,
|
||||
PerThing128, Supports, VoteWeight,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::Bounded,
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||
TransactionValidityError, ValidTransaction,
|
||||
},
|
||||
DispatchError, PerThing, Perbill, RuntimeDebug, SaturatedConversion,
|
||||
traits::Bounded,
|
||||
};
|
||||
use sp_std::{
|
||||
convert::TryInto,
|
||||
prelude::*,
|
||||
};
|
||||
use sp_arithmetic::{
|
||||
UpperOf,
|
||||
traits::{Zero, CheckedAdd},
|
||||
};
|
||||
use sp_std::{convert::TryInto, prelude::*};
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
mod benchmarking;
|
||||
@@ -562,7 +559,9 @@ pub mod pallet {
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + SendTransactionTypes<Call<Self>> {
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event> + TryInto<Event<Self>>;
|
||||
type Event: From<Event<Self>>
|
||||
+ IsType<<Self as frame_system::Config>::Event>
|
||||
+ TryInto<Event<Self>>;
|
||||
|
||||
/// Currency type.
|
||||
type Currency: ReservableCurrency<Self::AccountId> + Currency<Self::AccountId>;
|
||||
@@ -701,21 +700,22 @@ pub mod pallet {
|
||||
Ok(snap_weight) => {
|
||||
log!(info, "Starting signed phase round {}.", Self::round());
|
||||
T::WeightInfo::on_initialize_open_signed().saturating_add(snap_weight)
|
||||
}
|
||||
},
|
||||
Err(why) => {
|
||||
// Not much we can do about this at this point.
|
||||
log!(warn, "failed to open signed phase due to {:?}", why);
|
||||
T::WeightInfo::on_initialize_nothing()
|
||||
// NOTE: ^^ The trait specifies that this is a noop in terms of weight
|
||||
// in case of error.
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Phase::Signed | Phase::Off
|
||||
if remaining <= unsigned_deadline && remaining > Zero::zero() =>
|
||||
{
|
||||
// our needs vary according to whether or not the unsigned phase follows a signed phase
|
||||
let (need_snapshot, enabled, signed_weight) = if current_phase == Phase::Signed {
|
||||
let (need_snapshot, enabled, signed_weight) = if current_phase == Phase::Signed
|
||||
{
|
||||
// there was previously a signed phase: close the signed phase, no need for snapshot.
|
||||
//
|
||||
// Notes:
|
||||
@@ -744,14 +744,14 @@ pub mod pallet {
|
||||
};
|
||||
|
||||
base_weight.saturating_add(snap_weight).saturating_add(signed_weight)
|
||||
}
|
||||
},
|
||||
Err(why) => {
|
||||
// Not much we can do about this at this point.
|
||||
log!(warn, "failed to open unsigned phase due to {:?}", why);
|
||||
T::WeightInfo::on_initialize_nothing()
|
||||
// NOTE: ^^ The trait specifies that this is a noop in terms of weight
|
||||
// in case of error.
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => T::WeightInfo::on_initialize_nothing(),
|
||||
@@ -759,15 +759,16 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
fn offchain_worker(now: T::BlockNumber) {
|
||||
use sp_runtime::offchain::storage_lock::{StorageLock, BlockAndTime};
|
||||
use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock};
|
||||
|
||||
// Create a lock with the maximum deadline of number of blocks in the unsigned phase.
|
||||
// This should only come useful in an **abrupt** termination of execution, otherwise the
|
||||
// guard will be dropped upon successful execution.
|
||||
let mut lock = StorageLock::<BlockAndTime<frame_system::Pallet::<T>>>::with_block_deadline(
|
||||
unsigned::OFFCHAIN_LOCK,
|
||||
T::UnsignedPhase::get().saturated_into(),
|
||||
);
|
||||
let mut lock =
|
||||
StorageLock::<BlockAndTime<frame_system::Pallet<T>>>::with_block_deadline(
|
||||
unsigned::OFFCHAIN_LOCK,
|
||||
T::UnsignedPhase::get().saturated_into(),
|
||||
);
|
||||
|
||||
match lock.try_lock() {
|
||||
Ok(_guard) => {
|
||||
@@ -775,7 +776,7 @@ pub mod pallet {
|
||||
},
|
||||
Err(deadline) => {
|
||||
log!(debug, "offchain worker lock not released, deadline is {:?}", deadline);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -857,8 +858,7 @@ pub mod pallet {
|
||||
witness: SolutionOrSnapshotSize,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
let error_message =
|
||||
"Invalid unsigned submission must produce invalid block and \
|
||||
let error_message = "Invalid unsigned submission must produce invalid block and \
|
||||
deprive validator from their authoring reward.";
|
||||
|
||||
// Check score being an improvement, phase, and desired targets.
|
||||
@@ -921,11 +921,8 @@ pub mod pallet {
|
||||
// Note: we don't `rotate_round` at this point; the next call to
|
||||
// `ElectionProvider::elect` will succeed and take care of that.
|
||||
|
||||
let solution = ReadySolution {
|
||||
supports,
|
||||
score: [0, 0, 0],
|
||||
compute: ElectionCompute::Emergency,
|
||||
};
|
||||
let solution =
|
||||
ReadySolution { supports, score: [0, 0, 0], compute: ElectionCompute::Emergency };
|
||||
|
||||
<QueuedSolution<T>>::put(solution);
|
||||
Ok(())
|
||||
@@ -954,7 +951,8 @@ pub mod pallet {
|
||||
|
||||
// ensure witness data is correct.
|
||||
ensure!(
|
||||
num_signed_submissions >= <SignedSubmissions<T>>::decode_len().unwrap_or_default() as u32,
|
||||
num_signed_submissions >=
|
||||
<SignedSubmissions<T>>::decode_len().unwrap_or_default() as u32,
|
||||
Error::<T>::SignedInvalidWitness,
|
||||
);
|
||||
|
||||
@@ -989,8 +987,7 @@ pub mod pallet {
|
||||
};
|
||||
|
||||
// collect deposit. Thereafter, the function cannot fail.
|
||||
T::Currency::reserve(&who, deposit)
|
||||
.map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
|
||||
T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
|
||||
|
||||
let ejected_a_solution = maybe_removed.is_some();
|
||||
// if we had to remove the weakest solution, unreserve its deposit
|
||||
@@ -1068,10 +1065,8 @@ pub mod pallet {
|
||||
if let Call::submit_unsigned(solution, _) = call {
|
||||
// Discard solution not coming from the local OCW.
|
||||
match source {
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }
|
||||
_ => {
|
||||
return InvalidTransaction::Call.into();
|
||||
}
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
|
||||
_ => return InvalidTransaction::Call.into(),
|
||||
}
|
||||
|
||||
let _ = Self::unsigned_pre_dispatch_checks(solution)
|
||||
@@ -1084,9 +1079,8 @@ pub mod pallet {
|
||||
ValidTransaction::with_tag_prefix("OffchainElection")
|
||||
// The higher the score[0], the better a solution is.
|
||||
.priority(
|
||||
T::MinerTxPriority::get().saturating_add(
|
||||
solution.score[0].saturated_into()
|
||||
),
|
||||
T::MinerTxPriority::get()
|
||||
.saturating_add(solution.score[0].saturated_into()),
|
||||
)
|
||||
// Used to deduplicate unsigned solutions: each validator should produce one
|
||||
// solution per round at most, and solutions are not propagate.
|
||||
@@ -1219,20 +1213,18 @@ impl<T: Config> Pallet<T> {
|
||||
match current_phase {
|
||||
Phase::Unsigned((true, opened)) if opened == now => {
|
||||
// Mine a new solution, cache it, and attempt to submit it
|
||||
let initial_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
|
||||
Self::mine_check_save_submit()
|
||||
});
|
||||
let initial_output = Self::ensure_offchain_repeat_frequency(now)
|
||||
.and_then(|_| Self::mine_check_save_submit());
|
||||
log!(debug, "initial offchain thread output: {:?}", initial_output);
|
||||
}
|
||||
},
|
||||
Phase::Unsigned((true, opened)) if opened < now => {
|
||||
// Try and resubmit the cached solution, and recompute ONLY if it is not
|
||||
// feasible.
|
||||
let resubmit_output = Self::ensure_offchain_repeat_frequency(now).and_then(|_| {
|
||||
Self::restore_or_compute_then_maybe_submit()
|
||||
});
|
||||
let resubmit_output = Self::ensure_offchain_repeat_frequency(now)
|
||||
.and_then(|_| Self::restore_or_compute_then_maybe_submit());
|
||||
log!(debug, "resubmit offchain thread output: {:?}", resubmit_output);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
// After election finalization, clear OCW solution storage.
|
||||
@@ -1242,9 +1234,7 @@ impl<T: Config> Pallet<T> {
|
||||
let local_event = <T as Config>::Event::from(event_record.event);
|
||||
local_event.try_into().ok()
|
||||
})
|
||||
.any(|event| {
|
||||
matches!(event, Event::ElectionFinalized(_))
|
||||
})
|
||||
.any(|event| matches!(event, Event::ElectionFinalized(_)))
|
||||
{
|
||||
unsigned::kill_ocw_solution::<T>();
|
||||
}
|
||||
@@ -1308,14 +1298,12 @@ impl<T: Config> Pallet<T> {
|
||||
// Defensive-only.
|
||||
if targets.len() > target_limit || voters.len() > voter_limit {
|
||||
debug_assert!(false, "Snapshot limit has not been respected.");
|
||||
return Err(ElectionError::DataProvider("Snapshot too big for submission."));
|
||||
return Err(ElectionError::DataProvider("Snapshot too big for submission."))
|
||||
}
|
||||
|
||||
// Only write snapshot if all existed.
|
||||
let metadata = SolutionOrSnapshotSize {
|
||||
voters: voters.len() as u32,
|
||||
targets: targets.len() as u32,
|
||||
};
|
||||
let metadata =
|
||||
SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
|
||||
log!(debug, "creating a snapshot with metadata {:?}", metadata);
|
||||
|
||||
<SnapshotMetadata<T>>::put(metadata);
|
||||
@@ -1335,7 +1323,10 @@ impl<T: Config> Pallet<T> {
|
||||
debug_assert!(buffer.len() == size && size == buffer.capacity());
|
||||
|
||||
sp_io::storage::set(&<Snapshot<T>>::hashed_key(), &buffer);
|
||||
Ok(w1.saturating_add(w2).saturating_add(w3).saturating_add(T::DbWeight::get().writes(3)))
|
||||
Ok(w1
|
||||
.saturating_add(w2)
|
||||
.saturating_add(w3)
|
||||
.saturating_add(T::DbWeight::get().writes(3)))
|
||||
}
|
||||
|
||||
/// Kill everything created by [`Pallet::create_snapshot`].
|
||||
@@ -1369,9 +1360,9 @@ impl<T: Config> Pallet<T> {
|
||||
// Ensure that the solution's score can pass absolute min-score.
|
||||
let submitted_score = solution.score.clone();
|
||||
ensure!(
|
||||
Self::minimum_untrusted_score().map_or(true, |min_score|
|
||||
Self::minimum_untrusted_score().map_or(true, |min_score| {
|
||||
sp_npos_elections::is_score_better(submitted_score, min_score, Perbill::zero())
|
||||
),
|
||||
}),
|
||||
FeasibilityError::UntrustedScoreTooLow
|
||||
);
|
||||
|
||||
@@ -1418,7 +1409,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// Check that all of the targets are valid based on the snapshot.
|
||||
if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) {
|
||||
return Err(FeasibilityError::InvalidVote);
|
||||
return Err(FeasibilityError::InvalidVote)
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@@ -1494,8 +1485,13 @@ impl<T: Config> Pallet<T> {
|
||||
.fold(Zero::zero(), |acc, next| acc + next.voters.len() as u32);
|
||||
Ok((
|
||||
supports,
|
||||
T::WeightInfo::elect_queued(metadata.voters, metadata.targets, active_voters, desired),
|
||||
compute
|
||||
T::WeightInfo::elect_queued(
|
||||
metadata.voters,
|
||||
metadata.targets,
|
||||
active_voters,
|
||||
desired,
|
||||
),
|
||||
compute,
|
||||
))
|
||||
},
|
||||
)
|
||||
@@ -1526,12 +1522,12 @@ impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for Pallet<T> {
|
||||
// All went okay, put sign to be Off, clean snapshot, etc.
|
||||
Self::rotate_round();
|
||||
Ok((supports, weight))
|
||||
}
|
||||
},
|
||||
Err(why) => {
|
||||
log!(error, "Entering emergency mode: {:?}", why);
|
||||
<CurrentPhase<T>>::put(Phase::Emergency);
|
||||
Err(why)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1553,11 +1549,9 @@ mod feasibility_check {
|
||||
//! that is invalid, but gets through the system as valid.
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
mock::{
|
||||
MultiPhase, Runtime, roll_to, TargetIndex, raw_solution, EpochLength, UnsignedPhase,
|
||||
SignedPhase, VoterIndex, ExtBuilder,
|
||||
},
|
||||
use crate::mock::{
|
||||
raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase,
|
||||
TargetIndex, UnsignedPhase, VoterIndex,
|
||||
};
|
||||
use frame_support::assert_noop;
|
||||
|
||||
@@ -1728,11 +1722,11 @@ mod feasibility_check {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Phase,
|
||||
mock::{
|
||||
ExtBuilder, MultiPhase, Runtime, roll_to, MockWeightInfo, AccountId, TargetIndex,
|
||||
Targets, multi_phase_events, System, SignedMaxSubmissions,
|
||||
multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase,
|
||||
Runtime, SignedMaxSubmissions, System, TargetIndex, Targets,
|
||||
},
|
||||
Phase,
|
||||
};
|
||||
use frame_election_provider_support::ElectionProvider;
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
@@ -2002,7 +1996,6 @@ mod tests {
|
||||
roll_to(15);
|
||||
assert_eq!(MultiPhase::current_phase(), Phase::Signed);
|
||||
|
||||
|
||||
let (solution, _) = MultiPhase::mine_solution(2).unwrap();
|
||||
// Default solution has a score of [50, 100, 5000].
|
||||
assert_eq!(solution.score, [50, 100, 5000]);
|
||||
@@ -2012,10 +2005,7 @@ mod tests {
|
||||
|
||||
<MinimumUntrustedScore<Runtime>>::put([51, 0, 0]);
|
||||
assert_noop!(
|
||||
MultiPhase::feasibility_check(
|
||||
solution,
|
||||
ElectionCompute::Signed
|
||||
),
|
||||
MultiPhase::feasibility_check(solution, ElectionCompute::Signed),
|
||||
FeasibilityError::UntrustedScoreTooLow,
|
||||
);
|
||||
})
|
||||
@@ -2039,9 +2029,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut active = 1;
|
||||
while weight_with(active)
|
||||
<= <Runtime as frame_system::Config>::BlockWeights::get().max_block
|
||||
|| active == all_voters
|
||||
while weight_with(active) <=
|
||||
<Runtime as frame_system::Config>::BlockWeights::get().max_block ||
|
||||
active == all_voters
|
||||
{
|
||||
active += 1;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,10 @@
|
||||
|
||||
use super::*;
|
||||
use crate as multi_phase;
|
||||
use multi_phase::unsigned::{IndexAssignmentOf, Voter};
|
||||
use frame_election_provider_support::{data_provider, ElectionDataProvider};
|
||||
pub use frame_support::{assert_noop, assert_ok};
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{Hooks},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_support::{parameter_types, traits::Hooks, weights::Weight};
|
||||
use multi_phase::unsigned::{IndexAssignmentOf, Voter};
|
||||
use parking_lot::RwLock;
|
||||
use sp_core::{
|
||||
offchain::{
|
||||
@@ -32,7 +29,6 @@ use sp_core::{
|
||||
},
|
||||
H256,
|
||||
};
|
||||
use frame_election_provider_support::{ElectionDataProvider, data_provider};
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing,
|
||||
CompactSolution, ElectionResult, EvaluateSupport,
|
||||
@@ -405,7 +401,7 @@ impl ElectionDataProvider<AccountId, u64> for StakingMock {
|
||||
let targets = Targets::get();
|
||||
|
||||
if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) {
|
||||
return Err("Targets too big");
|
||||
return Err("Targets too big")
|
||||
}
|
||||
|
||||
Ok((targets, 0))
|
||||
@@ -416,7 +412,7 @@ impl ElectionDataProvider<AccountId, u64> for StakingMock {
|
||||
) -> data_provider::Result<(Vec<(AccountId, VoteWeight, Vec<AccountId>)>, Weight)> {
|
||||
let voters = Voters::get();
|
||||
if maybe_max_len.map_or(false, |max_len| voters.len() > max_len) {
|
||||
return Err("Voters too big");
|
||||
return Err("Voters too big")
|
||||
}
|
||||
|
||||
Ok((voters, 0))
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
//! The signed phase implementation.
|
||||
|
||||
use crate::{
|
||||
CompactOf, Config, ElectionCompute, Pallet, RawSolution, ReadySolution, SolutionOrSnapshotSize,
|
||||
Weight, WeightInfo, QueuedSolution, SignedSubmissionsMap, SignedSubmissionIndices,
|
||||
SignedSubmissionNextIndex,
|
||||
CompactOf, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution,
|
||||
SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap,
|
||||
SolutionOrSnapshotSize, Weight, WeightInfo,
|
||||
};
|
||||
use codec::{Encode, Decode, HasCompact};
|
||||
use codec::{Decode, Encode, HasCompact};
|
||||
use frame_support::{
|
||||
storage::bounded_btree_map::BoundedBTreeMap,
|
||||
traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
|
||||
@@ -31,8 +31,8 @@ use frame_support::{
|
||||
use sp_arithmetic::traits::SaturatedConversion;
|
||||
use sp_npos_elections::{is_score_better, CompactSolution, ElectionScore};
|
||||
use sp_runtime::{
|
||||
RuntimeDebug,
|
||||
traits::{Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{
|
||||
cmp::Ordering,
|
||||
@@ -131,24 +131,30 @@ impl<T: Config> SignedSubmissions<T> {
|
||||
deletion_overlay: BTreeSet::new(),
|
||||
};
|
||||
// validate that the stored state is sane
|
||||
debug_assert!(submissions.indices.values().copied().max().map_or(
|
||||
true,
|
||||
|max_idx| submissions.next_idx > max_idx,
|
||||
));
|
||||
debug_assert!(submissions
|
||||
.indices
|
||||
.values()
|
||||
.copied()
|
||||
.max()
|
||||
.map_or(true, |max_idx| submissions.next_idx > max_idx,));
|
||||
submissions
|
||||
}
|
||||
|
||||
/// Put the signed submissions back into storage.
|
||||
pub fn put(mut self) {
|
||||
// validate that we're going to write only sane things to storage
|
||||
debug_assert!(self.insertion_overlay.keys().copied().max().map_or(
|
||||
true,
|
||||
|max_idx| self.next_idx > max_idx,
|
||||
));
|
||||
debug_assert!(self.indices.values().copied().max().map_or(
|
||||
true,
|
||||
|max_idx| self.next_idx > max_idx,
|
||||
));
|
||||
debug_assert!(self
|
||||
.insertion_overlay
|
||||
.keys()
|
||||
.copied()
|
||||
.max()
|
||||
.map_or(true, |max_idx| self.next_idx > max_idx,));
|
||||
debug_assert!(self
|
||||
.indices
|
||||
.values()
|
||||
.copied()
|
||||
.max()
|
||||
.map_or(true, |max_idx| self.next_idx > max_idx,));
|
||||
|
||||
SignedSubmissionIndices::<T>::put(self.indices);
|
||||
SignedSubmissionNextIndex::<T>::put(self.next_idx);
|
||||
@@ -203,10 +209,12 @@ impl<T: Config> SignedSubmissions<T> {
|
||||
}
|
||||
|
||||
self.insertion_overlay.remove(&remove_idx).or_else(|| {
|
||||
(!self.deletion_overlay.contains(&remove_idx)).then(|| {
|
||||
self.deletion_overlay.insert(remove_idx);
|
||||
SignedSubmissionsMap::<T>::try_get(remove_idx).ok()
|
||||
}).flatten()
|
||||
(!self.deletion_overlay.contains(&remove_idx))
|
||||
.then(|| {
|
||||
self.deletion_overlay.insert(remove_idx);
|
||||
SignedSubmissionsMap::<T>::try_get(remove_idx).ok()
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -256,10 +264,7 @@ impl<T: Config> SignedSubmissions<T> {
|
||||
///
|
||||
/// In the event that the new submission is not better than the current weakest according
|
||||
/// to `is_score_better`, we do not change anything.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
submission: SignedSubmissionOf<T>,
|
||||
) -> InsertResult<T> {
|
||||
pub fn insert(&mut self, submission: SignedSubmissionOf<T>) -> InsertResult<T> {
|
||||
// verify the expectation that we never reuse an index
|
||||
debug_assert!(!self.indices.values().any(|&idx| idx == self.next_idx));
|
||||
|
||||
@@ -271,12 +276,12 @@ impl<T: Config> SignedSubmissions<T> {
|
||||
self.indices
|
||||
.try_insert(submission.solution.score, prev_idx)
|
||||
.expect("didn't change the map size; qed");
|
||||
return InsertResult::NotInserted;
|
||||
}
|
||||
return InsertResult::NotInserted
|
||||
},
|
||||
Ok(None) => {
|
||||
// successfully inserted into the set; no need to take out weakest member
|
||||
None
|
||||
}
|
||||
},
|
||||
Err((insert_score, insert_idx)) => {
|
||||
// could not insert into the set because it is full.
|
||||
// note that we short-circuit return here in case the iteration produces `None`.
|
||||
@@ -290,11 +295,11 @@ impl<T: Config> SignedSubmissions<T> {
|
||||
|
||||
// if we haven't improved on the weakest score, don't change anything.
|
||||
if !is_score_better(insert_score, weakest_score, threshold) {
|
||||
return InsertResult::NotInserted;
|
||||
return InsertResult::NotInserted
|
||||
}
|
||||
|
||||
self.swap_out_submission(weakest_score, Some((insert_score, insert_idx)))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// we've taken out the weakest, so update the storage map and the next index
|
||||
@@ -349,17 +354,12 @@ impl<T: Config> Pallet<T> {
|
||||
let reward = T::SignedRewardBase::get();
|
||||
|
||||
while let Some(best) = all_submissions.pop_last() {
|
||||
let SignedSubmission { solution, who, deposit} = best;
|
||||
let SignedSubmission { solution, who, deposit } = best;
|
||||
let active_voters = solution.compact.voter_count() as u32;
|
||||
let feasibility_weight = {
|
||||
// defensive only: at the end of signed phase, snapshot will exits.
|
||||
let desired_targets = Self::desired_targets().unwrap_or_default();
|
||||
T::WeightInfo::feasibility_check(
|
||||
voters,
|
||||
targets,
|
||||
active_voters,
|
||||
desired_targets,
|
||||
)
|
||||
T::WeightInfo::feasibility_check(voters, targets, active_voters, desired_targets)
|
||||
};
|
||||
// the feasibility check itself has some weight
|
||||
weight = weight.saturating_add(feasibility_weight);
|
||||
@@ -375,13 +375,13 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
weight = weight
|
||||
.saturating_add(T::WeightInfo::finalize_signed_phase_accept_solution());
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
Err(_) => {
|
||||
Self::finalize_signed_phase_reject_solution(&who, deposit);
|
||||
weight = weight
|
||||
.saturating_add(T::WeightInfo::finalize_signed_phase_reject_solution());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,12 @@ impl<T: Config> Pallet<T> {
|
||||
debug_assert!(!SignedSubmissionNextIndex::<T>::exists());
|
||||
debug_assert!(SignedSubmissionsMap::<T>::iter().next().is_none());
|
||||
|
||||
log!(debug, "closed signed phase, found solution? {}, discarded {}", found_solution, discarded);
|
||||
log!(
|
||||
debug,
|
||||
"closed signed phase, found solution? {}, discarded {}",
|
||||
found_solution,
|
||||
discarded
|
||||
);
|
||||
(found_solution, weight)
|
||||
}
|
||||
|
||||
@@ -469,9 +474,12 @@ impl<T: Config> Pallet<T> {
|
||||
let feasibility_weight = Self::feasibility_weight_of(solution, size);
|
||||
|
||||
let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len);
|
||||
let weight_deposit = T::SignedDepositWeight::get().saturating_mul(feasibility_weight.saturated_into());
|
||||
let weight_deposit =
|
||||
T::SignedDepositWeight::get().saturating_mul(feasibility_weight.saturated_into());
|
||||
|
||||
T::SignedDepositBase::get().saturating_add(len_deposit).saturating_add(weight_deposit)
|
||||
T::SignedDepositBase::get()
|
||||
.saturating_add(len_deposit)
|
||||
.saturating_add(weight_deposit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,13 +487,13 @@ impl<T: Config> Pallet<T> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Phase, Error,
|
||||
mock::{
|
||||
balances, ExtBuilder, MultiPhase, Origin, raw_solution, roll_to, Runtime,
|
||||
balances, raw_solution, roll_to, ExtBuilder, MultiPhase, Origin, Runtime,
|
||||
SignedMaxSubmissions, SignedMaxWeight,
|
||||
},
|
||||
Error, Phase,
|
||||
};
|
||||
use frame_support::{dispatch::DispatchResult, assert_noop, assert_storage_noop, assert_ok};
|
||||
use frame_support::{assert_noop, assert_ok, assert_storage_noop, dispatch::DispatchResult};
|
||||
|
||||
fn submit_with_witness(
|
||||
origin: Origin,
|
||||
@@ -626,7 +634,6 @@ mod tests {
|
||||
assert_ok!(submit_with_witness(Origin::signed(99), solution));
|
||||
}
|
||||
|
||||
|
||||
// weaker.
|
||||
let solution = RawSolution { score: [4, 0, 0], ..Default::default() };
|
||||
|
||||
@@ -810,33 +817,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cannot_consume_too_much_future_weight() {
|
||||
ExtBuilder::default().signed_weight(40).mock_weight_info(true).build_and_execute(|| {
|
||||
roll_to(15);
|
||||
assert!(MultiPhase::current_phase().is_signed());
|
||||
ExtBuilder::default()
|
||||
.signed_weight(40)
|
||||
.mock_weight_info(true)
|
||||
.build_and_execute(|| {
|
||||
roll_to(15);
|
||||
assert!(MultiPhase::current_phase().is_signed());
|
||||
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 35);
|
||||
assert_eq!(solution.compact.voter_count(), 5);
|
||||
assert_eq!(<Runtime as Config>::SignedMaxWeight::get(), 40);
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 35);
|
||||
assert_eq!(solution.compact.voter_count(), 5);
|
||||
assert_eq!(<Runtime as Config>::SignedMaxWeight::get(), 40);
|
||||
|
||||
assert_ok!(submit_with_witness(Origin::signed(99), solution.clone()));
|
||||
assert_ok!(submit_with_witness(Origin::signed(99), solution.clone()));
|
||||
|
||||
<SignedMaxWeight>::set(30);
|
||||
<SignedMaxWeight>::set(30);
|
||||
|
||||
// note: resubmitting the same solution is technically okay as long as the queue has
|
||||
// space.
|
||||
assert_noop!(
|
||||
submit_with_witness(Origin::signed(99), solution),
|
||||
Error::<Runtime>::SignedTooMuchWeight,
|
||||
);
|
||||
})
|
||||
// note: resubmitting the same solution is technically okay as long as the queue has
|
||||
// space.
|
||||
assert_noop!(
|
||||
submit_with_witness(Origin::signed(99), solution),
|
||||
Error::<Runtime>::SignedTooMuchWeight,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,19 +21,18 @@ use crate::{
|
||||
helpers, Call, CompactAccuracyOf, CompactOf, Config, ElectionCompute, Error, FeasibilityError,
|
||||
Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionOrSnapshotSize, Weight, WeightInfo,
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
|
||||
use frame_system::offchain::SubmitTransaction;
|
||||
use sp_arithmetic::Perbill;
|
||||
use sp_npos_elections::{
|
||||
CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized,
|
||||
assignment_staked_to_ratio_normalized, is_score_better, seq_phragmen,
|
||||
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, is_score_better,
|
||||
seq_phragmen, CompactSolution, ElectionResult,
|
||||
};
|
||||
use sp_runtime::{
|
||||
DispatchError,
|
||||
SaturatedConversion,
|
||||
offchain::storage::{MutateStorageError, StorageValueRef},
|
||||
traits::TrailingZeroInput,
|
||||
DispatchError, SaturatedConversion,
|
||||
};
|
||||
use sp_std::{cmp::Ordering, convert::TryFrom, vec::Vec};
|
||||
|
||||
@@ -54,10 +53,8 @@ pub type Voter<T> = (
|
||||
);
|
||||
|
||||
/// The relative distribution of a voter's stake among the winning targets.
|
||||
pub type Assignment<T> = sp_npos_elections::Assignment<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
CompactAccuracyOf<T>,
|
||||
>;
|
||||
pub type Assignment<T> =
|
||||
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, CompactAccuracyOf<T>>;
|
||||
|
||||
/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular
|
||||
/// runtime `T`.
|
||||
@@ -105,7 +102,8 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
|
||||
let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
|
||||
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(MutateStorageError::ConcurrentModification(_)) => Err(MinerError::FailedToStoreSolution),
|
||||
Err(MutateStorageError::ConcurrentModification(_)) =>
|
||||
Err(MinerError::FailedToStoreSolution),
|
||||
Err(MutateStorageError::ValueFunctionFailed(_)) => {
|
||||
// this branch should be unreachable according to the definition of
|
||||
// `StorageValueRef::mutate`: that function should only ever `Err` if the closure we
|
||||
@@ -151,44 +149,45 @@ impl<T: Config> Pallet<T> {
|
||||
/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
|
||||
/// if our call's score is greater than that of the cached solution.
|
||||
pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> {
|
||||
log!(debug,"miner attempting to restore or compute an unsigned solution.");
|
||||
log!(debug, "miner attempting to restore or compute an unsigned solution.");
|
||||
|
||||
let call = restore_solution::<T>()
|
||||
.and_then(|call| {
|
||||
// ensure the cached call is still current before submitting
|
||||
if let Call::submit_unsigned(solution, _) = &call {
|
||||
// prevent errors arising from state changes in a forkful chain
|
||||
Self::basic_checks(solution, "restored")?;
|
||||
Ok(call)
|
||||
} else {
|
||||
Err(MinerError::SolutionCallInvalid)
|
||||
}
|
||||
}).or_else::<MinerError, _>(|error| {
|
||||
log!(debug, "restoring solution failed due to {:?}", error);
|
||||
match error {
|
||||
MinerError::NoStoredSolution => {
|
||||
log!(trace, "mining a new solution.");
|
||||
// if not present or cache invalidated due to feasibility, regenerate.
|
||||
// note that failing `Feasibility` can only mean that the solution was
|
||||
// computed over a snapshot that has changed due to a fork.
|
||||
let call = Self::mine_checked_call()?;
|
||||
save_solution(&call)?;
|
||||
.and_then(|call| {
|
||||
// ensure the cached call is still current before submitting
|
||||
if let Call::submit_unsigned(solution, _) = &call {
|
||||
// prevent errors arising from state changes in a forkful chain
|
||||
Self::basic_checks(solution, "restored")?;
|
||||
Ok(call)
|
||||
} else {
|
||||
Err(MinerError::SolutionCallInvalid)
|
||||
}
|
||||
MinerError::Feasibility(_) => {
|
||||
log!(trace, "wiping infeasible solution.");
|
||||
// kill the infeasible solution, hopefully in the next runs (whenever they
|
||||
// may be) we mine a new one.
|
||||
kill_ocw_solution::<T>();
|
||||
clear_offchain_repeat_frequency();
|
||||
Err(error)
|
||||
},
|
||||
_ => {
|
||||
// nothing to do. Return the error as-is.
|
||||
Err(error)
|
||||
})
|
||||
.or_else::<MinerError, _>(|error| {
|
||||
log!(debug, "restoring solution failed due to {:?}", error);
|
||||
match error {
|
||||
MinerError::NoStoredSolution => {
|
||||
log!(trace, "mining a new solution.");
|
||||
// if not present or cache invalidated due to feasibility, regenerate.
|
||||
// note that failing `Feasibility` can only mean that the solution was
|
||||
// computed over a snapshot that has changed due to a fork.
|
||||
let call = Self::mine_checked_call()?;
|
||||
save_solution(&call)?;
|
||||
Ok(call)
|
||||
},
|
||||
MinerError::Feasibility(_) => {
|
||||
log!(trace, "wiping infeasible solution.");
|
||||
// kill the infeasible solution, hopefully in the next runs (whenever they
|
||||
// may be) we mine a new one.
|
||||
kill_ocw_solution::<T>();
|
||||
clear_offchain_repeat_frequency();
|
||||
Err(error)
|
||||
},
|
||||
_ => {
|
||||
// nothing to do. Return the error as-is.
|
||||
Err(error)
|
||||
},
|
||||
}
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
Self::submit_call(call)
|
||||
}
|
||||
@@ -240,10 +239,12 @@ impl<T: Config> Pallet<T> {
|
||||
MinerError::PreDispatchChecksFailed(err)
|
||||
})?;
|
||||
|
||||
Self::feasibility_check(raw_solution.clone(), ElectionCompute::Unsigned).map_err(|err| {
|
||||
log!(debug, "feasibility check failed for {} solution: {:?}", solution_type, err);
|
||||
err
|
||||
})?;
|
||||
Self::feasibility_check(raw_solution.clone(), ElectionCompute::Unsigned).map_err(
|
||||
|err| {
|
||||
log!(debug, "feasibility check failed for {} solution: {:?}", solution_type, err);
|
||||
err
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -347,11 +348,7 @@ impl<T: Config> Pallet<T> {
|
||||
// converting to `Compact`.
|
||||
let mut index_assignments = sorted_assignments
|
||||
.into_iter()
|
||||
.map(|assignment| IndexAssignmentOf::<T>::new(
|
||||
&assignment,
|
||||
&voter_index,
|
||||
&target_index,
|
||||
))
|
||||
.map(|assignment| IndexAssignmentOf::<T>::new(&assignment, &voter_index, &target_index))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// trim assignments list for weight and length.
|
||||
@@ -390,10 +387,10 @@ impl<T: Config> Pallet<T> {
|
||||
max @ _ => {
|
||||
let seed = sp_io::offchain::random_seed();
|
||||
let random = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
|
||||
.expect("input is padded with zeroes; qed")
|
||||
% max.saturating_add(1);
|
||||
.expect("input is padded with zeroes; qed") %
|
||||
max.saturating_add(1);
|
||||
random as usize
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,18 +415,16 @@ impl<T: Config> Pallet<T> {
|
||||
max_weight: Weight,
|
||||
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
||||
) {
|
||||
let maximum_allowed_voters = Self::maximum_voter_for_weight::<T::WeightInfo>(
|
||||
desired_targets,
|
||||
size,
|
||||
max_weight,
|
||||
);
|
||||
let removing: usize = assignments.len().saturating_sub(
|
||||
maximum_allowed_voters.saturated_into(),
|
||||
);
|
||||
let maximum_allowed_voters =
|
||||
Self::maximum_voter_for_weight::<T::WeightInfo>(desired_targets, size, max_weight);
|
||||
let removing: usize =
|
||||
assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
|
||||
log!(
|
||||
debug,
|
||||
"from {} assignments, truncating to {} for weight, removing {}",
|
||||
assignments.len(), maximum_allowed_voters, removing,
|
||||
assignments.len(),
|
||||
maximum_allowed_voters,
|
||||
removing,
|
||||
);
|
||||
assignments.truncate(maximum_allowed_voters as usize);
|
||||
}
|
||||
@@ -461,7 +456,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// not much we can do if assignments are already empty.
|
||||
if high == low {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
while high - low > 1 {
|
||||
@@ -472,22 +467,21 @@ impl<T: Config> Pallet<T> {
|
||||
high = test;
|
||||
}
|
||||
}
|
||||
let maximum_allowed_voters =
|
||||
if low < assignments.len() &&
|
||||
encoded_size_of(&assignments[..low + 1])? <= max_allowed_length
|
||||
{
|
||||
low + 1
|
||||
} else {
|
||||
low
|
||||
};
|
||||
let maximum_allowed_voters = if low < assignments.len() &&
|
||||
encoded_size_of(&assignments[..low + 1])? <= max_allowed_length
|
||||
{
|
||||
low + 1
|
||||
} else {
|
||||
low
|
||||
};
|
||||
|
||||
// ensure our post-conditions are correct
|
||||
debug_assert!(
|
||||
encoded_size_of(&assignments[..maximum_allowed_voters]).unwrap() <= max_allowed_length
|
||||
);
|
||||
debug_assert!(if maximum_allowed_voters < assignments.len() {
|
||||
encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap()
|
||||
> max_allowed_length
|
||||
encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap() >
|
||||
max_allowed_length
|
||||
} else {
|
||||
true
|
||||
});
|
||||
@@ -517,7 +511,7 @@ impl<T: Config> Pallet<T> {
|
||||
max_weight: Weight,
|
||||
) -> u32 {
|
||||
if size.voters < 1 {
|
||||
return size.voters;
|
||||
return size.voters
|
||||
}
|
||||
|
||||
let max_voters = size.voters.max(1);
|
||||
@@ -536,7 +530,7 @@ impl<T: Config> Pallet<T> {
|
||||
Some(voters) if voters < max_voters => Ok(voters),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
},
|
||||
Ordering::Greater => voters.checked_sub(step).ok_or(()),
|
||||
Ordering::Equal => Ok(voters),
|
||||
}
|
||||
@@ -551,11 +545,9 @@ impl<T: Config> Pallet<T> {
|
||||
// proceed with the binary search
|
||||
Ok(next) if next != voters => {
|
||||
voters = next;
|
||||
}
|
||||
},
|
||||
// we are out of bounds, break out of the loop.
|
||||
Err(()) => {
|
||||
break;
|
||||
}
|
||||
Err(()) => break,
|
||||
// we found the right value - early exit the function.
|
||||
Ok(next) => return next,
|
||||
}
|
||||
@@ -599,17 +591,16 @@ impl<T: Config> Pallet<T> {
|
||||
|maybe_head: Result<Option<T::BlockNumber>, _>| {
|
||||
match maybe_head {
|
||||
Ok(Some(head)) if now < head => Err("fork."),
|
||||
Ok(Some(head)) if now >= head && now <= head + threshold => {
|
||||
Err("recently executed.")
|
||||
}
|
||||
Ok(Some(head)) if now >= head && now <= head + threshold =>
|
||||
Err("recently executed."),
|
||||
Ok(Some(head)) if now > head + threshold => {
|
||||
// we can run again now. Write the new head.
|
||||
Ok(now)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// value doesn't exists. Probably this node just booted up. Write, and run
|
||||
Ok(now)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -632,9 +623,7 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
|
||||
/// code, so that we do less and less storage reads here.
|
||||
pub fn unsigned_pre_dispatch_checks(
|
||||
solution: &RawSolution<CompactOf<T>>,
|
||||
) -> DispatchResult {
|
||||
pub fn unsigned_pre_dispatch_checks(solution: &RawSolution<CompactOf<T>>) -> DispatchResult {
|
||||
// ensure solution is timely. Don't panic yet. This is a cheap check.
|
||||
ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
|
||||
|
||||
@@ -643,8 +632,8 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// ensure correct number of winners.
|
||||
ensure!(
|
||||
Self::desired_targets().unwrap_or_default()
|
||||
== solution.compact.unique_targets().len() as u32,
|
||||
Self::desired_targets().unwrap_or_default() ==
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
Error::<T>::PreDispatchWrongWinnerCount,
|
||||
);
|
||||
|
||||
@@ -761,19 +750,22 @@ mod max_weight {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
mock::{
|
||||
roll_to, roll_to_with_ocw, trim_helpers, witness, BlockNumber, Call as OuterCall,
|
||||
ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, Origin, Runtime, System,
|
||||
TestCompact, TrimHelpers, UnsignedPhase,
|
||||
},
|
||||
CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource,
|
||||
TransactionValidityError,
|
||||
mock::{
|
||||
Call as OuterCall, ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, Origin, Runtime,
|
||||
TestCompact, TrimHelpers, roll_to, roll_to_with_ocw, trim_helpers, witness,
|
||||
UnsignedPhase, BlockNumber, System,
|
||||
},
|
||||
};
|
||||
use frame_benchmarking::Zero;
|
||||
use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker};
|
||||
use sp_npos_elections::IndexAssignment;
|
||||
use sp_runtime::offchain::storage_lock::{StorageLock, BlockAndTime};
|
||||
use sp_runtime::{traits::ValidateUnsigned, PerU16};
|
||||
use sp_runtime::{
|
||||
offchain::storage_lock::{BlockAndTime, StorageLock},
|
||||
traits::ValidateUnsigned,
|
||||
PerU16,
|
||||
};
|
||||
|
||||
type Assignment = crate::unsigned::Assignment<Runtime>;
|
||||
|
||||
@@ -786,8 +778,11 @@ mod tests {
|
||||
// initial
|
||||
assert_eq!(MultiPhase::current_phase(), Phase::Off);
|
||||
assert!(matches!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
||||
.unwrap_err(),
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
assert!(matches!(
|
||||
@@ -799,8 +794,11 @@ mod tests {
|
||||
roll_to(15);
|
||||
assert_eq!(MultiPhase::current_phase(), Phase::Signed);
|
||||
assert!(matches!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
||||
.unwrap_err(),
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
assert!(matches!(
|
||||
@@ -823,8 +821,11 @@ mod tests {
|
||||
<CurrentPhase<Runtime>>::put(Phase::Unsigned((false, 25)));
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
assert!(matches!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
||||
.unwrap_err(),
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap_err(),
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
||||
));
|
||||
assert!(matches!(
|
||||
@@ -895,23 +896,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn priority_is_set() {
|
||||
ExtBuilder::default().miner_tx_priority(20).desired_targets(0).build_and_execute(|| {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
ExtBuilder::default()
|
||||
.miner_tx_priority(20)
|
||||
.desired_targets(0)
|
||||
.build_and_execute(|| {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(solution.clone(), witness());
|
||||
let solution =
|
||||
RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(solution.clone(), witness());
|
||||
|
||||
assert_eq!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap()
|
||||
.priority,
|
||||
25
|
||||
);
|
||||
})
|
||||
assert_eq!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call
|
||||
)
|
||||
.unwrap()
|
||||
.priority,
|
||||
25
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -974,35 +979,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn miner_trims_weight() {
|
||||
ExtBuilder::default().miner_weight(100).mock_weight_info(true).build_and_execute(|| {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
ExtBuilder::default()
|
||||
.miner_weight(100)
|
||||
.mock_weight_info(true)
|
||||
.build_and_execute(|| {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 35);
|
||||
assert_eq!(solution.compact.voter_count(), 5);
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 35);
|
||||
assert_eq!(solution.compact.voter_count(), 5);
|
||||
|
||||
// now reduce the max weight
|
||||
<MinerMaxWeight>::set(25);
|
||||
// now reduce the max weight
|
||||
<MinerMaxWeight>::set(25);
|
||||
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 25);
|
||||
assert_eq!(solution.compact.voter_count(), 3);
|
||||
})
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 25);
|
||||
assert_eq!(solution.compact.voter_count(), 3);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1014,7 +1022,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
MultiPhase::mine_check_save_submit().unwrap_err(),
|
||||
MinerError::PreDispatchChecksFailed(DispatchError::Module{
|
||||
MinerError::PreDispatchChecksFailed(DispatchError::Module {
|
||||
index: 2,
|
||||
error: 1,
|
||||
message: Some("PreDispatchWrongWinnerCount"),
|
||||
@@ -1360,15 +1368,14 @@ mod tests {
|
||||
};
|
||||
|
||||
// Custom(7) maps to PreDispatchChecksFailed
|
||||
let pre_dispatch_check_error = TransactionValidityError::Invalid(
|
||||
InvalidTransaction::Custom(7),
|
||||
);
|
||||
let pre_dispatch_check_error =
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(7));
|
||||
assert_eq!(
|
||||
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&call,
|
||||
)
|
||||
.unwrap_err(),
|
||||
.unwrap_err(),
|
||||
pre_dispatch_check_error,
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1384,21 +1391,14 @@ mod tests {
|
||||
roll_to(25);
|
||||
|
||||
// given
|
||||
let TrimHelpers {
|
||||
mut assignments,
|
||||
encoded_size_of,
|
||||
..
|
||||
} = trim_helpers();
|
||||
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size() as u32;
|
||||
let compact_clone = compact.clone();
|
||||
|
||||
// when
|
||||
MultiPhase::trim_assignments_length(
|
||||
encoded_len,
|
||||
&mut assignments,
|
||||
encoded_size_of,
|
||||
).unwrap();
|
||||
MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of)
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
@@ -1412,11 +1412,7 @@ mod tests {
|
||||
roll_to(25);
|
||||
|
||||
// given
|
||||
let TrimHelpers {
|
||||
mut assignments,
|
||||
encoded_size_of,
|
||||
..
|
||||
} = trim_helpers();
|
||||
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size();
|
||||
let compact_clone = compact.clone();
|
||||
@@ -1426,7 +1422,8 @@ mod tests {
|
||||
encoded_len as u32 - 1,
|
||||
&mut assignments,
|
||||
encoded_size_of,
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
@@ -1441,33 +1438,26 @@ mod tests {
|
||||
roll_to(25);
|
||||
|
||||
// given
|
||||
let TrimHelpers {
|
||||
voters,
|
||||
mut assignments,
|
||||
encoded_size_of,
|
||||
voter_index,
|
||||
} = trim_helpers();
|
||||
let TrimHelpers { voters, mut assignments, encoded_size_of, voter_index } =
|
||||
trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size() as u32;
|
||||
let count = assignments.len();
|
||||
let min_stake_voter = voters.iter()
|
||||
let min_stake_voter = voters
|
||||
.iter()
|
||||
.map(|(id, weight, _)| (weight, id))
|
||||
.min()
|
||||
.and_then(|(_, id)| voter_index(id))
|
||||
.unwrap();
|
||||
|
||||
// when
|
||||
MultiPhase::trim_assignments_length(
|
||||
encoded_len - 1,
|
||||
&mut assignments,
|
||||
encoded_size_of,
|
||||
).unwrap();
|
||||
MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of)
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
|
||||
assert!(
|
||||
assignments.iter()
|
||||
.all(|IndexAssignment{ who, ..}| *who != min_stake_voter),
|
||||
assignments.iter().all(|IndexAssignment { who, .. }| *who != min_stake_voter),
|
||||
"min_stake_voter must no longer be in the set of voters",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -164,13 +164,13 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod onchain;
|
||||
use sp_std::{prelude::*, fmt::Debug};
|
||||
use frame_support::weights::Weight;
|
||||
use sp_std::{fmt::Debug, prelude::*};
|
||||
|
||||
/// Re-export some type as they are used in the interface.
|
||||
pub use sp_arithmetic::PerThing;
|
||||
pub use sp_npos_elections::{
|
||||
Assignment, ExtendedBalance, PerThing128, Supports, Support, VoteWeight
|
||||
Assignment, ExtendedBalance, PerThing128, Support, Supports, VoteWeight,
|
||||
};
|
||||
|
||||
/// Types that are used by the data provider trait.
|
||||
@@ -224,7 +224,8 @@ pub trait ElectionDataProvider<AccountId, BlockNumber> {
|
||||
_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
|
||||
_targets: Vec<AccountId>,
|
||||
_target_stake: Option<VoteWeight>,
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/// Utility function only to be used in benchmarking scenarios, to be implemented optionally,
|
||||
/// else a noop.
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
//! An implementation of [`ElectionProvider`] that does an on-chain sequential phragmen.
|
||||
|
||||
use crate::{ElectionDataProvider, ElectionProvider};
|
||||
use frame_support::{traits::Get, weights::Weight};
|
||||
use sp_npos_elections::*;
|
||||
use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*};
|
||||
use frame_support::{traits::Get, weights::Weight};
|
||||
|
||||
/// Errors of the on-chain election.
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
@@ -83,9 +83,8 @@ impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for OnChainSequen
|
||||
stake_map.insert(v.clone(), *s);
|
||||
});
|
||||
|
||||
let stake_of = |w: &T::AccountId| -> VoteWeight {
|
||||
stake_map.get(w).cloned().unwrap_or_default()
|
||||
};
|
||||
let stake_of =
|
||||
|w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() };
|
||||
|
||||
let ElectionResult { winners, assignments } =
|
||||
seq_phragmen::<_, T::Accuracy>(desired_targets as usize, targets, voters, None)
|
||||
@@ -94,16 +93,18 @@ impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for OnChainSequen
|
||||
let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?;
|
||||
let winners = to_without_backing(winners);
|
||||
|
||||
to_supports(&winners, &staked).map_err(Error::from).map(|s| (s, T::BlockWeights::get().max_block))
|
||||
to_supports(&winners, &staked)
|
||||
.map_err(Error::from)
|
||||
.map(|s| (s, T::BlockWeights::get().max_block))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::Weight;
|
||||
use sp_npos_elections::Support;
|
||||
use sp_runtime::Perbill;
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
type AccountId = u64;
|
||||
type BlockNumber = u32;
|
||||
@@ -151,20 +152,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
OnChainPhragmen::elect().unwrap().0,
|
||||
vec![
|
||||
(
|
||||
10,
|
||||
Support {
|
||||
total: 25,
|
||||
voters: vec![(1, 10), (3, 15)]
|
||||
}
|
||||
),
|
||||
(
|
||||
30,
|
||||
Support {
|
||||
total: 35,
|
||||
voters: vec![(2, 20), (3, 15)]
|
||||
}
|
||||
)
|
||||
(10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }),
|
||||
(30, Support { total: 35, voters: vec![(2, 20), (3, 15)] })
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelist};
|
||||
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize};
|
||||
use frame_system::RawOrigin;
|
||||
use frame_benchmarking::{benchmarks, account, whitelist, impl_benchmark_test_suite};
|
||||
use frame_support::{traits::OnInitialize, dispatch::DispatchResultWithPostInfo};
|
||||
|
||||
use crate::Pallet as Elections;
|
||||
|
||||
@@ -62,28 +62,34 @@ fn candidate_count<T: Config>() -> u32 {
|
||||
}
|
||||
|
||||
/// Add `c` new candidates.
|
||||
fn submit_candidates<T: Config>(c: u32, prefix: &'static str)
|
||||
-> Result<Vec<T::AccountId>, &'static str>
|
||||
{
|
||||
(0..c).map(|i| {
|
||||
let account = endowed_account::<T>(prefix, i);
|
||||
<Elections<T>>::submit_candidacy(
|
||||
RawOrigin::Signed(account.clone()).into(),
|
||||
candidate_count::<T>(),
|
||||
).map_err(|_| "failed to submit candidacy")?;
|
||||
Ok(account)
|
||||
}).collect::<Result<_, _>>()
|
||||
fn submit_candidates<T: Config>(
|
||||
c: u32,
|
||||
prefix: &'static str,
|
||||
) -> Result<Vec<T::AccountId>, &'static str> {
|
||||
(0..c)
|
||||
.map(|i| {
|
||||
let account = endowed_account::<T>(prefix, i);
|
||||
<Elections<T>>::submit_candidacy(
|
||||
RawOrigin::Signed(account.clone()).into(),
|
||||
candidate_count::<T>(),
|
||||
)
|
||||
.map_err(|_| "failed to submit candidacy")?;
|
||||
Ok(account)
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
/// Add `c` new candidates with self vote.
|
||||
fn submit_candidates_with_self_vote<T: Config>(c: u32, prefix: &'static str)
|
||||
-> Result<Vec<T::AccountId>, &'static str>
|
||||
{
|
||||
fn submit_candidates_with_self_vote<T: Config>(
|
||||
c: u32,
|
||||
prefix: &'static str,
|
||||
) -> Result<Vec<T::AccountId>, &'static str> {
|
||||
let candidates = submit_candidates::<T>(c, prefix)?;
|
||||
let stake = default_stake::<T>(BALANCE_FACTOR);
|
||||
let _ = candidates.iter().map(|c|
|
||||
submit_voter::<T>(c.clone(), vec![c.clone()], stake).map(|_| ())
|
||||
).collect::<Result<_, _>>()?;
|
||||
let _ = candidates
|
||||
.iter()
|
||||
.map(|c| submit_voter::<T>(c.clone(), vec![c.clone()], stake).map(|_| ()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
@@ -98,18 +104,16 @@ fn submit_voter<T: Config>(
|
||||
|
||||
/// create `num_voter` voters who randomly vote for at most `votes` of `all_candidates` if
|
||||
/// available.
|
||||
fn distribute_voters<T: Config>(mut all_candidates: Vec<T::AccountId>, num_voters: u32, votes: usize)
|
||||
-> Result<(), &'static str>
|
||||
{
|
||||
fn distribute_voters<T: Config>(
|
||||
mut all_candidates: Vec<T::AccountId>,
|
||||
num_voters: u32,
|
||||
votes: usize,
|
||||
) -> Result<(), &'static str> {
|
||||
let stake = default_stake::<T>(BALANCE_FACTOR);
|
||||
for i in 0..num_voters {
|
||||
// to ensure that votes are different
|
||||
all_candidates.rotate_left(1);
|
||||
let votes = all_candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.take(votes)
|
||||
.collect::<Vec<_>>();
|
||||
let votes = all_candidates.iter().cloned().take(votes).collect::<Vec<_>>();
|
||||
let voter = endowed_account::<T>("voter", i);
|
||||
submit_voter::<T>(voter, votes, stake)?;
|
||||
}
|
||||
@@ -128,13 +132,11 @@ fn fill_seats_up_to<T: Config>(m: u32) -> Result<Vec<T::AccountId>, &'static str
|
||||
m as usize,
|
||||
"wrong number of members and runners-up",
|
||||
);
|
||||
Ok(
|
||||
<Elections<T>>::members()
|
||||
.into_iter()
|
||||
.map(|m| m.who)
|
||||
.chain(<Elections<T>>::runners_up().into_iter().map(|r| r.who))
|
||||
.collect()
|
||||
)
|
||||
Ok(<Elections<T>>::members()
|
||||
.into_iter()
|
||||
.map(|m| m.who)
|
||||
.chain(<Elections<T>>::runners_up().into_iter().map(|r| r.who))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// removes all the storage items to reverse any genesis state.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,12 +17,13 @@
|
||||
|
||||
//! Migrations to version [`3.0.0`], as denoted by the changelog.
|
||||
|
||||
use codec::{Encode, Decode, FullCodec};
|
||||
use sp_std::prelude::*;
|
||||
use codec::{Decode, Encode, FullCodec};
|
||||
use frame_support::{
|
||||
RuntimeDebug, weights::Weight, Twox64Concat,
|
||||
traits::{GetPalletVersion, PalletVersion},
|
||||
weights::Weight,
|
||||
RuntimeDebug, Twox64Concat,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq)]
|
||||
struct SeatHolder<AccountId, Balance> {
|
||||
@@ -89,7 +90,7 @@ pub fn apply<T: V2ToV3>(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan
|
||||
migrate_runners_up_to_recorded_deposit::<T>(old_candidacy_bond);
|
||||
migrate_members_to_recorded_deposit::<T>(old_candidacy_bond);
|
||||
Weight::max_value()
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
log::warn!(
|
||||
target: "runtime::elections-phragmen",
|
||||
@@ -103,15 +104,9 @@ pub fn apply<T: V2ToV3>(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan
|
||||
|
||||
/// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic).
|
||||
pub fn migrate_voters_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance) {
|
||||
<Voting<T>>::translate::<(T::Balance, Vec<T::AccountId>), _>(
|
||||
|_who, (stake, votes)| {
|
||||
Some(Voter {
|
||||
votes,
|
||||
stake,
|
||||
deposit: old_deposit,
|
||||
})
|
||||
},
|
||||
);
|
||||
<Voting<T>>::translate::<(T::Balance, Vec<T::AccountId>), _>(|_who, (stake, votes)| {
|
||||
Some(Voter { votes, stake, deposit: old_deposit })
|
||||
});
|
||||
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
@@ -122,50 +117,39 @@ pub fn migrate_voters_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance) {
|
||||
|
||||
/// Migrate all candidates to recorded deposit.
|
||||
pub fn migrate_candidates_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance) {
|
||||
let _ = <Candidates<T>>::translate::<Vec<T::AccountId>, _>(
|
||||
|maybe_old_candidates| {
|
||||
maybe_old_candidates.map(|old_candidates| {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
"migrated {} candidate accounts.",
|
||||
old_candidates.len(),
|
||||
);
|
||||
old_candidates
|
||||
.into_iter()
|
||||
.map(|c| (c, old_deposit))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
},
|
||||
);
|
||||
let _ = <Candidates<T>>::translate::<Vec<T::AccountId>, _>(|maybe_old_candidates| {
|
||||
maybe_old_candidates.map(|old_candidates| {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
"migrated {} candidate accounts.",
|
||||
old_candidates.len(),
|
||||
);
|
||||
old_candidates.into_iter().map(|c| (c, old_deposit)).collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Migrate all members to recorded deposit.
|
||||
pub fn migrate_members_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance) {
|
||||
let _ = <Members<T>>::translate::<Vec<(T::AccountId, T::Balance)>, _>(
|
||||
|maybe_old_members| {
|
||||
maybe_old_members.map(|old_members| {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
"migrated {} member accounts.",
|
||||
old_members.len(),
|
||||
);
|
||||
old_members
|
||||
.into_iter()
|
||||
.map(|(who, stake)| SeatHolder {
|
||||
who,
|
||||
stake,
|
||||
deposit: old_deposit,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
},
|
||||
);
|
||||
let _ = <Members<T>>::translate::<Vec<(T::AccountId, T::Balance)>, _>(|maybe_old_members| {
|
||||
maybe_old_members.map(|old_members| {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
"migrated {} member accounts.",
|
||||
old_members.len(),
|
||||
);
|
||||
old_members
|
||||
.into_iter()
|
||||
.map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Migrate all runners-up to recorded deposit.
|
||||
pub fn migrate_runners_up_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance) {
|
||||
let _ = <RunnersUp<T>>::translate::<Vec<(T::AccountId, T::Balance)>, _>(
|
||||
|maybe_old_runners_up| {
|
||||
let _ =
|
||||
<RunnersUp<T>>::translate::<Vec<(T::AccountId, T::Balance)>, _>(|maybe_old_runners_up| {
|
||||
maybe_old_runners_up.map(|old_runners_up| {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
@@ -174,13 +158,8 @@ pub fn migrate_runners_up_to_recorded_deposit<T: V2ToV3>(old_deposit: T::Balance
|
||||
);
|
||||
old_runners_up
|
||||
.into_iter()
|
||||
.map(|(who, stake)| SeatHolder {
|
||||
who,
|
||||
stake,
|
||||
deposit: old_deposit,
|
||||
})
|
||||
.map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
//! Migrations to version [`4.0.0`], as denoted by the changelog.
|
||||
|
||||
use frame_support::{
|
||||
traits::{Get, GetPalletVersion, PalletVersion},
|
||||
weights::Weight,
|
||||
traits::{GetPalletVersion, PalletVersion, Get},
|
||||
};
|
||||
|
||||
/// The old prefix.
|
||||
@@ -32,17 +32,15 @@ pub const OLD_PREFIX: &[u8] = b"PhragmenElection";
|
||||
/// `<Runtime as frame_system::Config>::PalletInfo::name::<ElectionsPhragmenPallet>`.
|
||||
///
|
||||
/// The old storage prefix, `PhragmenElection` is hardcoded in the migration code.
|
||||
pub fn migrate<
|
||||
T: frame_system::Config,
|
||||
P: GetPalletVersion,
|
||||
N: AsRef<str>,
|
||||
>(new_pallet_name: N) -> Weight {
|
||||
pub fn migrate<T: frame_system::Config, P: GetPalletVersion, N: AsRef<str>>(
|
||||
new_pallet_name: N,
|
||||
) -> Weight {
|
||||
if new_pallet_name.as_ref().as_bytes() == OLD_PREFIX {
|
||||
log::info!(
|
||||
target: "runtime::elections-phragmen",
|
||||
"New pallet name is equal to the old prefix. No migration needs to be done.",
|
||||
);
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
let maybe_storage_version = <P as GetPalletVersion>::storage_version();
|
||||
log::info!(
|
||||
@@ -59,7 +57,7 @@ pub fn migrate<
|
||||
new_pallet_name.as_ref().as_bytes(),
|
||||
);
|
||||
<T as frame_system::Config>::BlockWeights::get().max_block
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
log::warn!(
|
||||
target: "runtime::elections-phragmen",
|
||||
@@ -103,7 +101,7 @@ pub fn pre_migration<P: GetPalletVersion, N: AsRef<str>>(new: N) {
|
||||
/// [`frame_support::traits::OnRuntimeUpgrade::post_upgrade`] for further testing.
|
||||
///
|
||||
/// Panics if anything goes wrong.
|
||||
pub fn post_migration<P : GetPalletVersion>() {
|
||||
pub fn post_migration<P: GetPalletVersion>() {
|
||||
log::info!("post-migration elections-phragmen");
|
||||
// ensure we've been updated to v4 by the automatic write of crate version -> storage version.
|
||||
assert!(<P as GetPalletVersion>::storage_version().unwrap().major == 4);
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
// --template=./.maintain/frame-weight-template.hbs
|
||||
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
|
||||
@@ -29,24 +29,26 @@
|
||||
//! whose voting is serially unsuccessful.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![recursion_limit="128"]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::{
|
||||
RuntimeDebug, print,
|
||||
traits::{Zero, One, StaticLookup, Saturating},
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
pallet_prelude::*, ensure,
|
||||
weights::{Weight, DispatchClass},
|
||||
ensure,
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
Currency, ExistenceRequirement, LockableCurrency, LockIdentifier, BalanceStatus,
|
||||
OnUnbalanced, ReservableCurrency, WithdrawReasons, ChangeMembers,
|
||||
}
|
||||
BalanceStatus, ChangeMembers, Currency, ExistenceRequirement, LockIdentifier,
|
||||
LockableCurrency, OnUnbalanced, ReservableCurrency, WithdrawReasons,
|
||||
},
|
||||
weights::{DispatchClass, Weight},
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use frame_system::pallet_prelude::*;
|
||||
pub use pallet::*;
|
||||
use sp_runtime::{
|
||||
print,
|
||||
traits::{One, Saturating, StaticLookup, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
@@ -140,9 +142,11 @@ pub const VOTER_SET_SIZE: usize = 64;
|
||||
/// NUmber of approvals grouped in one chunk.
|
||||
pub const APPROVAL_SET_SIZE: usize = 8;
|
||||
|
||||
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::NegativeImbalance;
|
||||
|
||||
/// Index used to access chunks.
|
||||
type SetIndex = u32;
|
||||
@@ -170,8 +174,7 @@ pub mod pallet {
|
||||
type PalletId: Get<LockIdentifier>;
|
||||
|
||||
/// The currency that people are electing with.
|
||||
type Currency:
|
||||
LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>
|
||||
type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>
|
||||
+ ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// Handler for the unbalanced reduction when slashing a validator.
|
||||
@@ -239,14 +242,14 @@ pub mod pallet {
|
||||
|
||||
#[pallet::extra_constants]
|
||||
impl<T: Config> Pallet<T> {
|
||||
//TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
|
||||
// TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
|
||||
/// The chunk size of the voter vector.
|
||||
#[allow(non_snake_case)]
|
||||
fn VOTER_SET_SIZE() -> u32 {
|
||||
VOTER_SET_SIZE as u32
|
||||
}
|
||||
|
||||
//TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
|
||||
// TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
|
||||
/// The chunk size of the approval vector.
|
||||
#[allow(non_snake_case)]
|
||||
fn APPROVAL_SET_SIZE() -> u32 {
|
||||
@@ -292,17 +295,12 @@ pub mod pallet {
|
||||
// bit-wise manner. In order to get a human-readable representation (`Vec<bool>`), use
|
||||
// [`all_approvals_of`]. Furthermore, each vector of scalars is chunked with the cap of
|
||||
// `APPROVAL_SET_SIZE`.
|
||||
///
|
||||
/// TWOX-NOTE: SAFE as `AccountId` is a crypto hash and `SetIndex` is not
|
||||
/// attacker-controlled.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn approvals_of)]
|
||||
pub type ApprovalsOf<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, (T::AccountId, SetIndex),
|
||||
Vec<ApprovalFlag>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub type ApprovalsOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, (T::AccountId, SetIndex), Vec<ApprovalFlag>, ValueQuery>;
|
||||
|
||||
/// The vote index and list slot that the candidate `who` was registered or `None` if they
|
||||
/// are not currently registered.
|
||||
@@ -310,26 +308,24 @@ pub mod pallet {
|
||||
/// TWOX-NOTE: SAFE as `AccountId` is a crypto hash.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn candidate_reg_info)]
|
||||
pub type RegisterInfoOf<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, (VoteIndex, u32)>;
|
||||
pub type RegisterInfoOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, (VoteIndex, u32)>;
|
||||
|
||||
/// Basic information about a voter.
|
||||
///
|
||||
/// TWOX-NOTE: SAFE as `AccountId` is a crypto hash.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn voter_info)]
|
||||
pub type VoterInfoOf<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, VoterInfo<BalanceOf<T>>>;
|
||||
pub type VoterInfoOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, VoterInfo<BalanceOf<T>>>;
|
||||
|
||||
/// The present voter list (chunked and capped at [`VOTER_SET_SIZE`]).
|
||||
///
|
||||
/// TWOX-NOTE: OKAY ― `SetIndex` is not user-controlled data.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn voters)]
|
||||
pub type Voters<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, SetIndex,
|
||||
Vec<Option<T::AccountId>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
pub type Voters<T: Config> =
|
||||
StorageMap<_, Twox64Concat, SetIndex, Vec<Option<T::AccountId>>, ValueQuery>;
|
||||
|
||||
/// the next free set to store a voter in. This will keep growing.
|
||||
#[pallet::storage]
|
||||
@@ -559,7 +555,8 @@ pub mod pallet {
|
||||
|
||||
let reporter_index = reporter_index as usize;
|
||||
let who_index = who_index as usize;
|
||||
let assumed_reporter = Self::voter_at(reporter_index).ok_or(Error::<T>::InvalidReporterIndex)?;
|
||||
let assumed_reporter =
|
||||
Self::voter_at(reporter_index).ok_or(Error::<T>::InvalidReporterIndex)?;
|
||||
let assumed_who = Self::voter_at(who_index).ok_or(Error::<T>::InvalidTargetIndex)?;
|
||||
|
||||
ensure!(assumed_reporter == reporter, Error::<T>::InvalidReporterIndex);
|
||||
@@ -567,29 +564,31 @@ pub mod pallet {
|
||||
|
||||
// will definitely kill one of reporter or who now.
|
||||
|
||||
let valid = !Self::all_approvals_of(&who).iter()
|
||||
.zip(Self::candidates().iter())
|
||||
.any(|(&appr, addr)|
|
||||
appr &&
|
||||
let valid = !Self::all_approvals_of(&who).iter().zip(Self::candidates().iter()).any(
|
||||
|(&appr, addr)| {
|
||||
appr &&
|
||||
*addr != T::AccountId::default() &&
|
||||
// defensive only: all items in candidates list are registered
|
||||
Self::candidate_reg_info(addr).map_or(false, |x| x.0 <= last_active)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Self::remove_voter(
|
||||
if valid { &who } else { &reporter },
|
||||
if valid { who_index } else { reporter_index }
|
||||
if valid { who_index } else { reporter_index },
|
||||
);
|
||||
|
||||
T::Currency::remove_lock(
|
||||
T::PalletId::get(),
|
||||
if valid { &who } else { &reporter }
|
||||
);
|
||||
T::Currency::remove_lock(T::PalletId::get(), if valid { &who } else { &reporter });
|
||||
|
||||
if valid {
|
||||
// This only fails if `reporter` doesn't exist, which it clearly must do since its
|
||||
// the origin. Still, it's no more harmful to propagate any error at this point.
|
||||
T::Currency::repatriate_reserved(&who, &reporter, T::VotingBond::get(), BalanceStatus::Free)?;
|
||||
T::Currency::repatriate_reserved(
|
||||
&who,
|
||||
&reporter,
|
||||
T::VotingBond::get(),
|
||||
BalanceStatus::Free,
|
||||
)?;
|
||||
Self::deposit_event(Event::<T>::VoterReaped(who, reporter));
|
||||
} else {
|
||||
let imbalance = T::Currency::slash_reserved(&reporter, T::VotingBond::get()).0;
|
||||
@@ -614,7 +613,10 @@ pub mod pallet {
|
||||
/// - Two fewer DB entries, one DB change.
|
||||
/// # </weight>
|
||||
#[pallet::weight(1_250_000_000)]
|
||||
pub fn retract_voter(origin: OriginFor<T>, #[pallet::compact] index: u32) -> DispatchResult {
|
||||
pub fn retract_voter(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] index: u32,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
ensure!(!Self::presentation_active(), Error::<T>::CannotRetractPresenting);
|
||||
@@ -644,7 +646,10 @@ pub mod pallet {
|
||||
/// - Three DB changes.
|
||||
/// # </weight>
|
||||
#[pallet::weight(2_500_000_000)]
|
||||
pub fn submit_candidacy(origin: OriginFor<T>, #[pallet::compact] slot: u32) -> DispatchResult {
|
||||
pub fn submit_candidacy(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] slot: u32,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
ensure!(!Self::is_a_candidate(&who), Error::<T>::DuplicatedCandidate);
|
||||
@@ -689,38 +694,31 @@ pub mod pallet {
|
||||
#[pallet::compact] index: VoteIndex,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(
|
||||
!total.is_zero(),
|
||||
Error::<T>::ZeroDeposit,
|
||||
);
|
||||
ensure!(!total.is_zero(), Error::<T>::ZeroDeposit,);
|
||||
|
||||
let candidate = T::Lookup::lookup(candidate)?;
|
||||
ensure!(index == Self::vote_index(), Error::<T>::InvalidVoteIndex);
|
||||
let (_, _, expiring) = Self::next_finalize()
|
||||
.ok_or(Error::<T>::NotPresentationPeriod)?;
|
||||
let (_, _, expiring) =
|
||||
Self::next_finalize().ok_or(Error::<T>::NotPresentationPeriod)?;
|
||||
let bad_presentation_punishment =
|
||||
T::PresentSlashPerVoter::get()
|
||||
* BalanceOf::<T>::from(Self::voter_count() as u32);
|
||||
T::PresentSlashPerVoter::get() * BalanceOf::<T>::from(Self::voter_count() as u32);
|
||||
ensure!(
|
||||
T::Currency::can_slash(&who, bad_presentation_punishment),
|
||||
Error::<T>::InsufficientPresenterFunds,
|
||||
);
|
||||
|
||||
let mut leaderboard = Self::leaderboard()
|
||||
.ok_or(Error::<T>::LeaderboardMustExist)?;
|
||||
let mut leaderboard = Self::leaderboard().ok_or(Error::<T>::LeaderboardMustExist)?;
|
||||
ensure!(total > leaderboard[0].0, Error::<T>::UnworthyCandidate);
|
||||
|
||||
if let Some(p) = Self::members().iter().position(|&(ref c, _)| c == &candidate) {
|
||||
ensure!(
|
||||
p < expiring.len(),
|
||||
Error::<T>::DuplicatedCandidate,
|
||||
);
|
||||
ensure!(p < expiring.len(), Error::<T>::DuplicatedCandidate,);
|
||||
}
|
||||
|
||||
let voters = Self::all_voters();
|
||||
let (registered_since, candidate_index): (VoteIndex, u32) =
|
||||
Self::candidate_reg_info(&candidate).ok_or(Error::<T>::InvalidCandidate)?;
|
||||
let actual_total = voters.iter()
|
||||
let actual_total = voters
|
||||
.iter()
|
||||
.filter_map(|maybe_voter| maybe_voter.as_ref())
|
||||
.filter_map(|voter| match Self::voter_info(voter) {
|
||||
Some(b) if b.last_active >= registered_since => {
|
||||
@@ -731,7 +729,9 @@ pub mod pallet {
|
||||
let weight = stake + offset + b.pot;
|
||||
if Self::approvals_of_at(voter, candidate_index as usize) {
|
||||
Some(weight)
|
||||
} else { None }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
@@ -748,7 +748,11 @@ pub mod pallet {
|
||||
// better safe than sorry.
|
||||
let imbalance = T::Currency::slash(&who, bad_presentation_punishment).0;
|
||||
T::BadPresentation::on_unbalanced(imbalance);
|
||||
Err(if dupe { Error::<T>::DuplicatedPresentation } else { Error::<T>::IncorrectTotal })?
|
||||
Err(if dupe {
|
||||
Error::<T>::DuplicatedPresentation
|
||||
} else {
|
||||
Error::<T>::IncorrectTotal
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,7 +760,10 @@ pub mod pallet {
|
||||
/// election when they expire. If more, then a new vote will be started if one is not
|
||||
/// already in progress.
|
||||
#[pallet::weight((0, DispatchClass::Operational))]
|
||||
pub fn set_desired_seats(origin: OriginFor<T>, #[pallet::compact] count: u32) -> DispatchResult {
|
||||
pub fn set_desired_seats(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] count: u32,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
DesiredSeats::<T>::put(count);
|
||||
Ok(())
|
||||
@@ -767,13 +774,14 @@ pub mod pallet {
|
||||
/// Note: A tally should happen instantly (if not already in a presentation
|
||||
/// period) to fill the seat if removal means that the desired members are not met.
|
||||
#[pallet::weight((0, DispatchClass::Operational))]
|
||||
pub fn remove_member(origin: OriginFor<T>, who: <T::Lookup as StaticLookup>::Source) -> DispatchResult {
|
||||
pub fn remove_member(
|
||||
origin: OriginFor<T>,
|
||||
who: <T::Lookup as StaticLookup>::Source,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
let new_set: Vec<(T::AccountId, T::BlockNumber)> = Self::members()
|
||||
.into_iter()
|
||||
.filter(|i| i.0 != who)
|
||||
.collect();
|
||||
let new_set: Vec<(T::AccountId, T::BlockNumber)> =
|
||||
Self::members().into_iter().filter(|i| i.0 != who).collect();
|
||||
<Members<T>>::put(&new_set);
|
||||
let new_set = new_set.into_iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
T::ChangeMembers::change_members(&[], &[who], new_set);
|
||||
@@ -821,7 +829,8 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Iff the member `who` still has a seat at blocknumber `n` returns `true`.
|
||||
pub fn will_still_be_member_at(who: &T::AccountId, n: T::BlockNumber) -> bool {
|
||||
Self::members().iter()
|
||||
Self::members()
|
||||
.iter()
|
||||
.find(|&&(ref a, _)| a == who)
|
||||
.map(|&(_, expires)| expires > n)
|
||||
.unwrap_or(false)
|
||||
@@ -859,7 +868,8 @@ impl<T: Config> Pallet<T> {
|
||||
} else {
|
||||
Some(c[c.len() - (desired_seats - coming) as usize].1)
|
||||
}
|
||||
}.map(Self::next_vote_from)
|
||||
}
|
||||
.map(Self::next_vote_from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,18 +916,12 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
ensure!(!Self::presentation_active(), Error::<T>::ApprovalPresentation);
|
||||
ensure!(index == Self::vote_index(), Error::<T>::InvalidVoteIndex);
|
||||
ensure!(
|
||||
!candidates_len.is_zero(),
|
||||
Error::<T>::ZeroCandidates,
|
||||
);
|
||||
ensure!(!candidates_len.is_zero(), Error::<T>::ZeroCandidates,);
|
||||
// Prevent a vote from voters that provide a list of votes that exceeds the candidates
|
||||
// length since otherwise an attacker may be able to submit a very long list of `votes` that
|
||||
// far exceeds the amount of candidates and waste more computation than a reasonable voting
|
||||
// bond would cover.
|
||||
ensure!(
|
||||
candidates_len >= votes.len(),
|
||||
Error::<T>::TooManyVotes,
|
||||
);
|
||||
ensure!(candidates_len >= votes.len(), Error::<T>::TooManyVotes,);
|
||||
ensure!(value >= T::MinimumVotingLock::get(), Error::<T>::InsufficientLockedValue);
|
||||
|
||||
// Amount to be locked up.
|
||||
@@ -969,19 +973,14 @@ impl<T: Config> Pallet<T> {
|
||||
NextVoterSet::<T>::put(next + 1);
|
||||
}
|
||||
<Voters<T>>::append(next, Some(who.clone()));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
T::Currency::reserve(&who, T::VotingBond::get())?;
|
||||
VoterCount::<T>::mutate(|c| *c = *c + 1);
|
||||
}
|
||||
|
||||
T::Currency::set_lock(
|
||||
T::PalletId::get(),
|
||||
&who,
|
||||
locked_balance,
|
||||
WithdrawReasons::all(),
|
||||
);
|
||||
T::Currency::set_lock(T::PalletId::get(), &who, locked_balance, WithdrawReasons::all());
|
||||
|
||||
<VoterInfoOf<T>>::insert(
|
||||
&who,
|
||||
@@ -990,7 +989,7 @@ impl<T: Config> Pallet<T> {
|
||||
last_win: index,
|
||||
stake: locked_balance,
|
||||
pot: pot_to_set,
|
||||
}
|
||||
},
|
||||
);
|
||||
Self::set_approvals_chunked(&who, votes);
|
||||
|
||||
@@ -1002,18 +1001,26 @@ impl<T: Config> Pallet<T> {
|
||||
let members = Self::members();
|
||||
let desired_seats = Self::desired_seats() as usize;
|
||||
let number = <frame_system::Pallet<T>>::block_number();
|
||||
let expiring =
|
||||
members.iter().take_while(|i| i.1 <= number).map(|i| i.0.clone()).collect::<Vec<_>>();
|
||||
let expiring = members
|
||||
.iter()
|
||||
.take_while(|i| i.1 <= number)
|
||||
.map(|i| i.0.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let retaining_seats = members.len() - expiring.len();
|
||||
if retaining_seats < desired_seats {
|
||||
let empty_seats = desired_seats - retaining_seats;
|
||||
<NextFinalize<T>>::put(
|
||||
(number + Self::presentation_duration(), empty_seats as u32, expiring)
|
||||
);
|
||||
<NextFinalize<T>>::put((
|
||||
number + Self::presentation_duration(),
|
||||
empty_seats as u32,
|
||||
expiring,
|
||||
));
|
||||
|
||||
// initialize leaderboard.
|
||||
let leaderboard_size = empty_seats + T::CarryCount::get() as usize;
|
||||
<Leaderboard<T>>::put(vec![(BalanceOf::<T>::zero(), T::AccountId::default()); leaderboard_size]);
|
||||
<Leaderboard<T>>::put(vec![
|
||||
(BalanceOf::<T>::zero(), T::AccountId::default());
|
||||
leaderboard_size
|
||||
]);
|
||||
|
||||
Self::deposit_event(Event::<T>::TallyStarted(empty_seats as u32));
|
||||
}
|
||||
@@ -1027,19 +1034,22 @@ impl<T: Config> Pallet<T> {
|
||||
let (_, coming, expiring): (T::BlockNumber, u32, Vec<T::AccountId>) =
|
||||
<NextFinalize<T>>::take()
|
||||
.ok_or("finalize can only be called after a tally is started.")?;
|
||||
let leaderboard: Vec<(BalanceOf<T>, T::AccountId)> = <Leaderboard<T>>::take()
|
||||
.unwrap_or_default();
|
||||
let leaderboard: Vec<(BalanceOf<T>, T::AccountId)> =
|
||||
<Leaderboard<T>>::take().unwrap_or_default();
|
||||
let new_expiry = <frame_system::Pallet<T>>::block_number() + Self::term_duration();
|
||||
|
||||
// return bond to winners.
|
||||
let candidacy_bond = T::CandidacyBond::get();
|
||||
let incoming: Vec<_> = leaderboard.iter()
|
||||
let incoming: Vec<_> = leaderboard
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|&&(b, _)| !b.is_zero())
|
||||
.take(coming as usize)
|
||||
.map(|(_, a)| a)
|
||||
.cloned()
|
||||
.inspect(|a| { T::Currency::unreserve(a, candidacy_bond); })
|
||||
.inspect(|a| {
|
||||
T::Currency::unreserve(a, candidacy_bond);
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Update last win index for anyone voted for any of the incomings.
|
||||
@@ -1049,14 +1059,16 @@ impl<T: Config> Pallet<T> {
|
||||
.iter()
|
||||
.filter_map(|mv| mv.as_ref())
|
||||
.filter(|v| Self::approvals_of_at(*v, index))
|
||||
.for_each(|v| <VoterInfoOf<T>>::mutate(v, |a| {
|
||||
if let Some(activity) = a { activity.last_win = Self::vote_index() + 1; }
|
||||
}));
|
||||
.for_each(|v| {
|
||||
<VoterInfoOf<T>>::mutate(v, |a| {
|
||||
if let Some(activity) = a {
|
||||
activity.last_win = Self::vote_index() + 1;
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
let members = Self::members();
|
||||
let outgoing: Vec<_> = members.iter()
|
||||
.take(expiring.len())
|
||||
.map(|a| a.0.clone()).collect();
|
||||
let outgoing: Vec<_> = members.iter().take(expiring.len()).map(|a| a.0.clone()).collect();
|
||||
|
||||
// set the new membership set.
|
||||
let mut new_set: Vec<_> = members
|
||||
@@ -1072,8 +1084,9 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// clear all except runners-up from candidate list.
|
||||
let candidates = Self::candidates();
|
||||
let mut new_candidates = vec![T::AccountId::default(); candidates.len()]; // shrink later.
|
||||
let runners_up = leaderboard.into_iter()
|
||||
let mut new_candidates = vec![T::AccountId::default(); candidates.len()]; // shrink later.
|
||||
let runners_up = leaderboard
|
||||
.into_iter()
|
||||
.rev()
|
||||
.take_while(|&(b, _)| !b.is_zero())
|
||||
.skip(coming as usize)
|
||||
@@ -1098,11 +1111,10 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
}
|
||||
// discard any superfluous slots.
|
||||
if let Some(last_index) = new_candidates
|
||||
.iter()
|
||||
.rposition(|c| *c != T::AccountId::default()) {
|
||||
new_candidates.truncate(last_index + 1);
|
||||
}
|
||||
if let Some(last_index) = new_candidates.iter().rposition(|c| *c != T::AccountId::default())
|
||||
{
|
||||
new_candidates.truncate(last_index + 1);
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::TallyFinalized(incoming, outgoing));
|
||||
|
||||
@@ -1131,7 +1143,7 @@ impl<T: Config> Pallet<T> {
|
||||
loop {
|
||||
let next_set = <Voters<T>>::get(index);
|
||||
if next_set.is_empty() {
|
||||
break;
|
||||
break
|
||||
} else {
|
||||
index += 1;
|
||||
all.extend(next_set);
|
||||
@@ -1177,9 +1189,7 @@ impl<T: Config> Pallet<T> {
|
||||
approvals_flag_vec
|
||||
.chunks(APPROVAL_SET_SIZE)
|
||||
.enumerate()
|
||||
.for_each(|(index, slice)| <ApprovalsOf<T>>::insert(
|
||||
(&who, index as SetIndex), slice)
|
||||
);
|
||||
.for_each(|(index, slice)| <ApprovalsOf<T>>::insert((&who, index as SetIndex), slice));
|
||||
}
|
||||
|
||||
/// shorthand for fetching a specific approval of a voter at a specific (global) index.
|
||||
@@ -1204,7 +1214,7 @@ impl<T: Config> Pallet<T> {
|
||||
/// Return true of the bit `n` of scalar `x` is set to `1` and false otherwise.
|
||||
fn bit_at(x: ApprovalFlag, n: usize) -> bool {
|
||||
if n < APPROVAL_FLAG_LEN {
|
||||
x & ( 1 << n ) != 0
|
||||
x & (1 << n) != 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -1215,7 +1225,7 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn bool_to_flag(x: Vec<bool>) -> Vec<ApprovalFlag> {
|
||||
let mut result: Vec<ApprovalFlag> = Vec::with_capacity(x.len() / APPROVAL_FLAG_LEN);
|
||||
if x.is_empty() {
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
result.push(0);
|
||||
let mut index = 0;
|
||||
@@ -1224,7 +1234,9 @@ impl<T: Config> Pallet<T> {
|
||||
let shl_index = counter % APPROVAL_FLAG_LEN;
|
||||
result[index] += (if x[counter] { 1 } else { 0 }) << shl_index;
|
||||
counter += 1;
|
||||
if counter > x.len() - 1 { break; }
|
||||
if counter > x.len() - 1 {
|
||||
break
|
||||
}
|
||||
if counter % APPROVAL_FLAG_LEN == 0 {
|
||||
result.push(0);
|
||||
index += 1;
|
||||
@@ -1236,15 +1248,18 @@ impl<T: Config> Pallet<T> {
|
||||
/// Convert a vec of flags (u32) to boolean.
|
||||
pub fn flag_to_bool(chunk: Vec<ApprovalFlag>) -> Vec<bool> {
|
||||
let mut result = Vec::with_capacity(chunk.len());
|
||||
if chunk.is_empty() { return vec![] }
|
||||
chunk.into_iter()
|
||||
.map(|num|
|
||||
if chunk.is_empty() {
|
||||
return vec![]
|
||||
}
|
||||
chunk
|
||||
.into_iter()
|
||||
.map(|num| {
|
||||
(0..APPROVAL_FLAG_LEN).map(|bit| Self::bit_at(num, bit)).collect::<Vec<bool>>()
|
||||
)
|
||||
})
|
||||
.for_each(|c| {
|
||||
let last_approve = match c.iter().rposition(|n| *n) {
|
||||
Some(index) => index + 1,
|
||||
None => 0
|
||||
None => 0,
|
||||
};
|
||||
result.extend(c.into_iter().take(last_approve));
|
||||
});
|
||||
@@ -1258,7 +1273,9 @@ impl<T: Config> Pallet<T> {
|
||||
let mut index = 0_u32;
|
||||
loop {
|
||||
let chunk = Self::approvals_of((who.clone(), index));
|
||||
if chunk.is_empty() { break; }
|
||||
if chunk.is_empty() {
|
||||
break
|
||||
}
|
||||
all.extend(Self::flag_to_bool(chunk));
|
||||
index += 1;
|
||||
}
|
||||
@@ -1291,7 +1308,9 @@ impl<T: Config> Pallet<T> {
|
||||
/// returned if `t` is zero.
|
||||
fn get_offset(stake: BalanceOf<T>, t: VoteIndex) -> BalanceOf<T> {
|
||||
let decay_ratio: BalanceOf<T> = T::DecayRatio::get().into();
|
||||
if t > 150 { return stake * decay_ratio }
|
||||
if t > 150 {
|
||||
return stake * decay_ratio
|
||||
}
|
||||
let mut offset = stake;
|
||||
let mut r = Zero::zero();
|
||||
let decay = decay_ratio + One::one();
|
||||
|
||||
@@ -19,16 +19,17 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as elections;
|
||||
use frame_support::{
|
||||
parameter_types, assert_ok,
|
||||
assert_ok, parameter_types,
|
||||
traits::{ChangeMembers, Currency, LockIdentifier},
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
BuildStorage, testing::Header, traits::{BlakeTwo256, IdentityLookup},
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
use crate as elections;
|
||||
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
@@ -104,7 +105,7 @@ impl ChangeMembers<u64> for TestChangeMembers {
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types!{
|
||||
parameter_types! {
|
||||
pub const ElectionPalletId: LockIdentifier = *b"py/elect";
|
||||
}
|
||||
|
||||
@@ -197,56 +198,55 @@ impl ExtBuilder {
|
||||
PRESENT_SLASH_PER_VOTER.with(|v| *v.borrow_mut() = self.bad_presentation_punishment);
|
||||
DECAY_RATIO.with(|v| *v.borrow_mut() = self.decay_ratio);
|
||||
let mut ext: sp_io::TestExternalities = GenesisConfig {
|
||||
balances: pallet_balances::GenesisConfig::<Test>{
|
||||
balances: pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(1, 10 * self.balance_factor),
|
||||
(2, 20 * self.balance_factor),
|
||||
(3, 30 * self.balance_factor),
|
||||
(4, 40 * self.balance_factor),
|
||||
(5, 50 * self.balance_factor),
|
||||
(6, 60 * self.balance_factor)
|
||||
(6, 60 * self.balance_factor),
|
||||
],
|
||||
},
|
||||
elections: elections::GenesisConfig::<Test>{
|
||||
elections: elections::GenesisConfig::<Test> {
|
||||
members: vec![],
|
||||
desired_seats: self.desired_seats,
|
||||
presentation_duration: 2,
|
||||
term_duration: 5,
|
||||
},
|
||||
}.build_storage().unwrap().into();
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap()
|
||||
.into();
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn voter_ids() -> Vec<u64> {
|
||||
Elections::all_voters().iter().map(|v| v.unwrap_or(0) ).collect::<Vec<u64>>()
|
||||
Elections::all_voters().iter().map(|v| v.unwrap_or(0)).collect::<Vec<u64>>()
|
||||
}
|
||||
|
||||
pub(crate) fn vote(i: u64, l: usize) {
|
||||
let _ = Balances::make_free_balance_be(&i, 20);
|
||||
assert_ok!(
|
||||
Elections::set_approvals(
|
||||
Origin::signed(i),
|
||||
(0..l).map(|_| true).collect::<Vec<bool>>(),
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
)
|
||||
);
|
||||
assert_ok!(Elections::set_approvals(
|
||||
Origin::signed(i),
|
||||
(0..l).map(|_| true).collect::<Vec<bool>>(),
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn vote_at(i: u64, l: usize, index: elections::VoteIndex) {
|
||||
let _ = Balances::make_free_balance_be(&i, 20);
|
||||
assert_ok!(
|
||||
Elections::set_approvals(
|
||||
Origin::signed(i),
|
||||
(0..l).map(|_| true).collect::<Vec<bool>>(),
|
||||
0,
|
||||
index,
|
||||
20,
|
||||
)
|
||||
);
|
||||
assert_ok!(Elections::set_approvals(
|
||||
Origin::signed(i),
|
||||
(0..l).map(|_| true).collect::<Vec<bool>>(),
|
||||
0,
|
||||
index,
|
||||
20,
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn create_candidate(i: u64, index: u32) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,24 +42,28 @@
|
||||
//! one unsigned transaction floating in the network.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::traits::Get;
|
||||
use frame_system::{
|
||||
self as system,
|
||||
offchain::{
|
||||
AppCrypto, CreateSignedTransaction, SendUnsignedTransaction, SendSignedTransaction,
|
||||
SignedPayload, SigningTypes, Signer, SubmitTransaction,
|
||||
}
|
||||
AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction,
|
||||
SignedPayload, Signer, SigningTypes, SubmitTransaction,
|
||||
},
|
||||
};
|
||||
use frame_support::traits::Get;
|
||||
use lite_json::json::JsonValue;
|
||||
use sp_core::crypto::KeyTypeId;
|
||||
use sp_runtime::{
|
||||
RuntimeDebug,
|
||||
offchain::{http, Duration, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}},
|
||||
offchain::{
|
||||
http,
|
||||
storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
|
||||
Duration,
|
||||
},
|
||||
traits::Zero,
|
||||
transaction_validity::{InvalidTransaction, ValidTransaction, TransactionValidity},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_std::vec::Vec;
|
||||
use lite_json::json::JsonValue;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -78,15 +82,17 @@ pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!");
|
||||
/// the types with this pallet-specific identifier.
|
||||
pub mod crypto {
|
||||
use super::KEY_TYPE;
|
||||
use sp_core::sr25519::Signature as Sr25519Signature;
|
||||
use sp_runtime::{
|
||||
app_crypto::{app_crypto, sr25519},
|
||||
traits::Verify,
|
||||
};
|
||||
use sp_core::sr25519::Signature as Sr25519Signature;
|
||||
app_crypto!(sr25519, KEY_TYPE);
|
||||
|
||||
pub struct TestAuthId;
|
||||
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature> for TestAuthId {
|
||||
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>
|
||||
for TestAuthId
|
||||
{
|
||||
type RuntimeAppPublic = Public;
|
||||
type GenericSignature = sp_core::sr25519::Signature;
|
||||
type GenericPublic = sp_core::sr25519::Public;
|
||||
@@ -97,9 +103,9 @@ pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use super::*;
|
||||
|
||||
/// This pallet's configuration trait
|
||||
#[pallet::config]
|
||||
@@ -179,8 +185,10 @@ pub mod pallet {
|
||||
let should_send = Self::choose_transaction_type(block_number);
|
||||
let res = match should_send {
|
||||
TransactionType::Signed => Self::fetch_price_and_send_signed(),
|
||||
TransactionType::UnsignedForAny => Self::fetch_price_and_send_unsigned_for_any_account(block_number),
|
||||
TransactionType::UnsignedForAll => Self::fetch_price_and_send_unsigned_for_all_accounts(block_number),
|
||||
TransactionType::UnsignedForAny =>
|
||||
Self::fetch_price_and_send_unsigned_for_any_account(block_number),
|
||||
TransactionType::UnsignedForAll =>
|
||||
Self::fetch_price_and_send_unsigned_for_all_accounts(block_number),
|
||||
TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number),
|
||||
TransactionType::None => Ok(()),
|
||||
};
|
||||
@@ -236,7 +244,7 @@ pub mod pallet {
|
||||
pub fn submit_price_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
_block_number: T::BlockNumber,
|
||||
price: u32
|
||||
price: u32,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
// This ensures that the function can only be called via unsigned transaction.
|
||||
ensure_none(origin)?;
|
||||
@@ -283,17 +291,15 @@ pub mod pallet {
|
||||
/// By default unsigned transactions are disallowed, but implementing the validator
|
||||
/// here we make sure that some particular calls (the ones produced by offchain worker)
|
||||
/// are being whitelisted and marked as valid.
|
||||
fn validate_unsigned(
|
||||
_source: TransactionSource,
|
||||
call: &Self::Call,
|
||||
) -> TransactionValidity {
|
||||
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
// Firstly let's check that we call the right function.
|
||||
if let Call::submit_price_unsigned_with_signed_payload(
|
||||
ref payload, ref signature
|
||||
) = call {
|
||||
let signature_valid = SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());
|
||||
if let Call::submit_price_unsigned_with_signed_payload(ref payload, ref signature) =
|
||||
call
|
||||
{
|
||||
let signature_valid =
|
||||
SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());
|
||||
if !signature_valid {
|
||||
return InvalidTransaction::BadProof.into();
|
||||
return InvalidTransaction::BadProof.into()
|
||||
}
|
||||
Self::validate_transaction_parameters(&payload.block_number, &payload.price)
|
||||
} else if let Call::submit_price_unsigned(block_number, new_price) = call {
|
||||
@@ -370,11 +376,10 @@ impl<T: Config> Pallet<T> {
|
||||
match last_send {
|
||||
// If we already have a value in storage and the block number is recent enough
|
||||
// we avoid sending another transaction at this time.
|
||||
Ok(Some(block)) if block_number < block + T::GracePeriod::get() => {
|
||||
Err(RECENTLY_SENT)
|
||||
},
|
||||
Ok(Some(block)) if block_number < block + T::GracePeriod::get() =>
|
||||
Err(RECENTLY_SENT),
|
||||
// In every other case we attempt to acquire the lock and send a transaction.
|
||||
_ => Ok(block_number)
|
||||
_ => Ok(block_number),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -396,10 +401,15 @@ impl<T: Config> Pallet<T> {
|
||||
// the storage entry for that. (for instance store both block number and a flag
|
||||
// indicating the type of next transaction to send).
|
||||
let transaction_type = block_number % 3u32.into();
|
||||
if transaction_type == Zero::zero() { TransactionType::Signed }
|
||||
else if transaction_type == T::BlockNumber::from(1u32) { TransactionType::UnsignedForAny }
|
||||
else if transaction_type == T::BlockNumber::from(2u32) { TransactionType::UnsignedForAll }
|
||||
else { TransactionType::Raw }
|
||||
if transaction_type == Zero::zero() {
|
||||
TransactionType::Signed
|
||||
} else if transaction_type == T::BlockNumber::from(1u32) {
|
||||
TransactionType::UnsignedForAny
|
||||
} else if transaction_type == T::BlockNumber::from(2u32) {
|
||||
TransactionType::UnsignedForAll
|
||||
} else {
|
||||
TransactionType::Raw
|
||||
}
|
||||
},
|
||||
// We are in the grace period, we should not send a transaction this time.
|
||||
Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None,
|
||||
@@ -417,7 +427,7 @@ impl<T: Config> Pallet<T> {
|
||||
let signer = Signer::<T, T::AuthorityId>::all_accounts();
|
||||
if !signer.can_sign() {
|
||||
return Err(
|
||||
"No local accounts available. Consider adding one via `author_insertKey` RPC."
|
||||
"No local accounts available. Consider adding one via `author_insertKey` RPC.",
|
||||
)?
|
||||
}
|
||||
// Make an external HTTP request to fetch the current price.
|
||||
@@ -428,14 +438,12 @@ impl<T: Config> Pallet<T> {
|
||||
// representing the call, we've just created.
|
||||
// Submit signed will return a vector of results for all accounts that were found in the
|
||||
// local keystore with expected `KEY_TYPE`.
|
||||
let results = signer.send_signed_transaction(
|
||||
|_account| {
|
||||
// Received price is wrapped into a call to `submit_price` public function of this pallet.
|
||||
// This means that the transaction, when executed, will simply call that function passing
|
||||
// `price` as an argument.
|
||||
Call::submit_price(price)
|
||||
}
|
||||
);
|
||||
let results = signer.send_signed_transaction(|_account| {
|
||||
// Received price is wrapped into a call to `submit_price` public function of this pallet.
|
||||
// This means that the transaction, when executed, will simply call that function passing
|
||||
// `price` as an argument.
|
||||
Call::submit_price(price)
|
||||
});
|
||||
|
||||
for (acc, res) in &results {
|
||||
match res {
|
||||
@@ -480,7 +488,9 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
|
||||
/// A helper function to fetch the price, sign payload and send an unsigned transaction
|
||||
fn fetch_price_and_send_unsigned_for_any_account(block_number: T::BlockNumber) -> Result<(), &'static str> {
|
||||
fn fetch_price_and_send_unsigned_for_any_account(
|
||||
block_number: T::BlockNumber,
|
||||
) -> Result<(), &'static str> {
|
||||
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
|
||||
// anyway.
|
||||
let next_unsigned_at = <NextUnsignedAt<T>>::get();
|
||||
@@ -493,23 +503,23 @@ impl<T: Config> Pallet<T> {
|
||||
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
|
||||
|
||||
// -- Sign using any account
|
||||
let (_, result) = Signer::<T, T::AuthorityId>::any_account().send_unsigned_transaction(
|
||||
|account| PricePayload {
|
||||
price,
|
||||
block_number,
|
||||
public: account.public.clone()
|
||||
},
|
||||
|payload, signature| {
|
||||
Call::submit_price_unsigned_with_signed_payload(payload, signature)
|
||||
}
|
||||
).ok_or("No local accounts accounts available.")?;
|
||||
let (_, result) = Signer::<T, T::AuthorityId>::any_account()
|
||||
.send_unsigned_transaction(
|
||||
|account| PricePayload { price, block_number, public: account.public.clone() },
|
||||
|payload, signature| {
|
||||
Call::submit_price_unsigned_with_signed_payload(payload, signature)
|
||||
},
|
||||
)
|
||||
.ok_or("No local accounts accounts available.")?;
|
||||
result.map_err(|()| "Unable to submit transaction")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A helper function to fetch the price, sign payload and send an unsigned transaction
|
||||
fn fetch_price_and_send_unsigned_for_all_accounts(block_number: T::BlockNumber) -> Result<(), &'static str> {
|
||||
fn fetch_price_and_send_unsigned_for_all_accounts(
|
||||
block_number: T::BlockNumber,
|
||||
) -> Result<(), &'static str> {
|
||||
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
|
||||
// anyway.
|
||||
let next_unsigned_at = <NextUnsignedAt<T>>::get();
|
||||
@@ -524,18 +534,14 @@ impl<T: Config> Pallet<T> {
|
||||
// -- Sign using all accounts
|
||||
let transaction_results = Signer::<T, T::AuthorityId>::all_accounts()
|
||||
.send_unsigned_transaction(
|
||||
|account| PricePayload {
|
||||
price,
|
||||
block_number,
|
||||
public: account.public.clone()
|
||||
},
|
||||
|account| PricePayload { price, block_number, public: account.public.clone() },
|
||||
|payload, signature| {
|
||||
Call::submit_price_unsigned_with_signed_payload(payload, signature)
|
||||
}
|
||||
},
|
||||
);
|
||||
for (_account_id, result) in transaction_results.into_iter() {
|
||||
if result.is_err() {
|
||||
return Err("Unable to submit transaction");
|
||||
return Err("Unable to submit transaction")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,16 +560,12 @@ impl<T: Config> Pallet<T> {
|
||||
// you can find in `sp_io`. The API is trying to be similar to `reqwest`, but
|
||||
// since we are running in a custom WASM execution environment we can't simply
|
||||
// import the library here.
|
||||
let request = http::Request::get(
|
||||
"https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD"
|
||||
);
|
||||
let request =
|
||||
http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD");
|
||||
// We set the deadline for sending of the request, note that awaiting response can
|
||||
// have a separate deadline. Next we send the request, before that it's also possible
|
||||
// to alter request headers or stream body content in case of non-GET requests.
|
||||
let pending = request
|
||||
.deadline(deadline)
|
||||
.send()
|
||||
.map_err(|_| http::Error::IoError)?;
|
||||
let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?;
|
||||
|
||||
// The request is already being processed by the host, we are free to do anything
|
||||
// else in the worker (we can send multiple concurrent requests too).
|
||||
@@ -571,12 +573,11 @@ impl<T: Config> Pallet<T> {
|
||||
// so we can block current thread and wait for it to finish.
|
||||
// Note that since the request is being driven by the host, we don't have to wait
|
||||
// for the request to have it complete, we will just not read the response.
|
||||
let response = pending.try_wait(deadline)
|
||||
.map_err(|_| http::Error::DeadlineReached)??;
|
||||
let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??;
|
||||
// Let's check the status code before we proceed to reading the response.
|
||||
if response.code != 200 {
|
||||
log::warn!("Unexpected status code: {}", response.code);
|
||||
return Err(http::Error::Unknown);
|
||||
return Err(http::Error::Unknown)
|
||||
}
|
||||
|
||||
// Next we want to fully read the response body and collect it to a vector of bytes.
|
||||
@@ -595,7 +596,7 @@ impl<T: Config> Pallet<T> {
|
||||
None => {
|
||||
log::warn!("Unable to extract price from the response: {:?}", body_str);
|
||||
Err(http::Error::Unknown)
|
||||
}
|
||||
},
|
||||
}?;
|
||||
|
||||
log::warn!("Got price: {} cents", price);
|
||||
@@ -610,8 +611,7 @@ impl<T: Config> Pallet<T> {
|
||||
let val = lite_json::parse_json(price_str);
|
||||
let price = match val.ok()? {
|
||||
JsonValue::Object(obj) => {
|
||||
let (_, v) = obj.into_iter()
|
||||
.find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
|
||||
let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
|
||||
match v {
|
||||
JsonValue::Number(number) => number,
|
||||
_ => return None,
|
||||
@@ -661,12 +661,12 @@ impl<T: Config> Pallet<T> {
|
||||
// Now let's check if the transaction has any chance to succeed.
|
||||
let next_unsigned_at = <NextUnsignedAt<T>>::get();
|
||||
if &next_unsigned_at > block_number {
|
||||
return InvalidTransaction::Stale.into();
|
||||
return InvalidTransaction::Stale.into()
|
||||
}
|
||||
// Let's make sure to reject transactions from the future.
|
||||
let current_block = <system::Pallet<T>>::block_number();
|
||||
if ¤t_block < block_number {
|
||||
return InvalidTransaction::Future.into();
|
||||
return InvalidTransaction::Future.into()
|
||||
}
|
||||
|
||||
// We prioritize transactions that are more far away from current average.
|
||||
|
||||
@@ -15,28 +15,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::*;
|
||||
use crate as example_offchain_worker;
|
||||
use std::sync::Arc;
|
||||
use crate::*;
|
||||
use codec::Decode;
|
||||
use frame_support::{assert_ok, parameter_types};
|
||||
use sp_core::{
|
||||
H256,
|
||||
offchain::{OffchainWorkerExt, TransactionPoolExt, testing},
|
||||
offchain::{testing, OffchainWorkerExt, TransactionPoolExt},
|
||||
sr25519::Signature,
|
||||
H256,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use sp_keystore::{
|
||||
{KeystoreExt, SyncCryptoStore},
|
||||
testing::KeyStore,
|
||||
};
|
||||
use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore};
|
||||
use sp_runtime::{
|
||||
RuntimeAppPublic,
|
||||
testing::{Header, TestXt},
|
||||
traits::{
|
||||
BlakeTwo256, IdentityLookup, Extrinsic as ExtrinsicT,
|
||||
IdentifyAccount, Verify,
|
||||
},
|
||||
traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify},
|
||||
RuntimeAppPublic,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
@@ -93,14 +87,16 @@ impl frame_system::offchain::SigningTypes for Test {
|
||||
type Signature = Signature;
|
||||
}
|
||||
|
||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Test where
|
||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Test
|
||||
where
|
||||
Call: From<LocalCall>,
|
||||
{
|
||||
type OverarchingCall = Call;
|
||||
type Extrinsic = Extrinsic;
|
||||
}
|
||||
|
||||
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test where
|
||||
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test
|
||||
where
|
||||
Call: From<LocalCall>,
|
||||
{
|
||||
fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
|
||||
@@ -190,7 +186,6 @@ fn knows_how_to_mock_several_http_calls() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
t.execute_with(|| {
|
||||
let price1 = Example::fetch_price().unwrap();
|
||||
let price2 = Example::fetch_price().unwrap();
|
||||
@@ -200,12 +195,12 @@ fn knows_how_to_mock_several_http_calls() {
|
||||
assert_eq!(price2, 200);
|
||||
assert_eq!(price3, 300);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_submit_signed_transaction_on_chain() {
|
||||
const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
const PHRASE: &str =
|
||||
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
|
||||
let (offchain, offchain_state) = testing::TestOffchainExt::new();
|
||||
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
|
||||
@@ -213,9 +208,9 @@ fn should_submit_signed_transaction_on_chain() {
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
crate::crypto::Public::ID,
|
||||
Some(&format!("{}/hunter1", PHRASE))
|
||||
).unwrap();
|
||||
|
||||
Some(&format!("{}/hunter1", PHRASE)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut t = sp_io::TestExternalities::default();
|
||||
t.register_extension(OffchainWorkerExt::new(offchain));
|
||||
@@ -238,7 +233,8 @@ fn should_submit_signed_transaction_on_chain() {
|
||||
|
||||
#[test]
|
||||
fn should_submit_unsigned_transaction_on_chain_for_any_account() {
|
||||
const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
const PHRASE: &str =
|
||||
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
let (offchain, offchain_state) = testing::TestOffchainExt::new();
|
||||
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
|
||||
|
||||
@@ -247,8 +243,9 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() {
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
crate::crypto::Public::ID,
|
||||
Some(&format!("{}/hunter1", PHRASE))
|
||||
).unwrap();
|
||||
Some(&format!("{}/hunter1", PHRASE)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
|
||||
.get(0)
|
||||
@@ -276,13 +273,18 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() {
|
||||
let tx = pool_state.write().transactions.pop().unwrap();
|
||||
let tx = Extrinsic::decode(&mut &*tx).unwrap();
|
||||
assert_eq!(tx.signature, None);
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload(body, signature)) = tx.call {
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload(
|
||||
body,
|
||||
signature,
|
||||
)) = tx.call
|
||||
{
|
||||
assert_eq!(body, price_payload);
|
||||
|
||||
let signature_valid = <PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber
|
||||
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
|
||||
let signature_valid =
|
||||
<PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
|
||||
|
||||
assert!(signature_valid);
|
||||
}
|
||||
@@ -291,7 +293,8 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() {
|
||||
|
||||
#[test]
|
||||
fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
|
||||
const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
const PHRASE: &str =
|
||||
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
|
||||
let (offchain, offchain_state) = testing::TestOffchainExt::new();
|
||||
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
|
||||
|
||||
@@ -300,8 +303,9 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
crate::crypto::Public::ID,
|
||||
Some(&format!("{}/hunter1", PHRASE))
|
||||
).unwrap();
|
||||
Some(&format!("{}/hunter1", PHRASE)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
|
||||
.get(0)
|
||||
@@ -329,13 +333,18 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
|
||||
let tx = pool_state.write().transactions.pop().unwrap();
|
||||
let tx = Extrinsic::decode(&mut &*tx).unwrap();
|
||||
assert_eq!(tx.signature, None);
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload(body, signature)) = tx.call {
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload(
|
||||
body,
|
||||
signature,
|
||||
)) = tx.call
|
||||
{
|
||||
assert_eq!(body, price_payload);
|
||||
|
||||
let signature_valid = <PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber
|
||||
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
|
||||
let signature_valid =
|
||||
<PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
|
||||
|
||||
assert!(signature_valid);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user