mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 22:51:13 +00:00
Generalised proxies (#6156)
* Initial work * It should work * Fix node * Fix tests * Initial test * Tests * Expunge proxy functionality from democracy and elections * Allow different proxy types * Repotted * Build * Build * Making a start on weights * Undo breaking change * Line widths. * Fix * fix tests * finish benchmarks? * Storage name! * Utility -> Proxy * proxy weight * add proxy weight * remove weights * Update transfer constraint * Again, fix constraints * Fix negation * Update frame/proxy/Cargo.toml Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Remove unneeded event. * Grumbles * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Generated
+17
@@ -3609,6 +3609,7 @@ dependencies = [
|
||||
"pallet-membership",
|
||||
"pallet-offences",
|
||||
"pallet-offences-benchmarking",
|
||||
"pallet-proxy",
|
||||
"pallet-randomness-collective-flip",
|
||||
"pallet-recovery",
|
||||
"pallet-scheduler",
|
||||
@@ -4399,6 +4400,22 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-proxy"
|
||||
version = "2.0.0-rc2"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-balances",
|
||||
"parity-scale-codec",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-randomness-collective-flip"
|
||||
version = "2.0.0-rc2"
|
||||
|
||||
@@ -86,6 +86,7 @@ members = [
|
||||
"frame/metadata",
|
||||
"frame/nicks",
|
||||
"frame/offences",
|
||||
"frame/proxy",
|
||||
"frame/randomness-collective-flip",
|
||||
"frame/recovery",
|
||||
"frame/scheduler",
|
||||
|
||||
@@ -61,6 +61,7 @@ pallet-identity = { version = "2.0.0-rc2", default-features = false, path = "../
|
||||
pallet-membership = { version = "2.0.0-rc2", default-features = false, path = "../../../frame/membership" }
|
||||
pallet-offences = { version = "2.0.0-rc2", default-features = false, path = "../../../frame/offences" }
|
||||
pallet-offences-benchmarking = { version = "2.0.0-rc2", path = "../../../frame/offences/benchmarking", default-features = false, optional = true }
|
||||
pallet-proxy = { version = "2.0.0-rc2", default-features = false, path = "../../../frame/proxy" }
|
||||
pallet-randomness-collective-flip = { version = "2.0.0-rc2", default-features = false, path = "../../../frame/randomness-collective-flip" }
|
||||
pallet-recovery = { version = "2.0.0-rc2", default-features = false, path = "../../../frame/recovery" }
|
||||
pallet-session = { version = "2.0.0-rc2", features = ["historical"], path = "../../../frame/session", default-features = false }
|
||||
@@ -111,6 +112,7 @@ std = [
|
||||
"node-primitives/std",
|
||||
"sp-offchain/std",
|
||||
"pallet-offences/std",
|
||||
"pallet-proxy/std",
|
||||
"sp-core/std",
|
||||
"pallet-randomness-collective-flip/std",
|
||||
"sp-std/std",
|
||||
@@ -149,6 +151,7 @@ runtime-benchmarks = [
|
||||
"pallet-elections-phragmen/runtime-benchmarks",
|
||||
"pallet-identity/runtime-benchmarks",
|
||||
"pallet-im-online/runtime-benchmarks",
|
||||
"pallet-proxy/runtime-benchmarks",
|
||||
"pallet-scheduler/runtime-benchmarks",
|
||||
"pallet-society/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
|
||||
@@ -24,13 +24,14 @@
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::{
|
||||
construct_runtime, parameter_types, debug,
|
||||
construct_runtime, parameter_types, debug, RuntimeDebug,
|
||||
weights::{
|
||||
Weight, IdentityFee,
|
||||
constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
|
||||
},
|
||||
traits::{Currency, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, LockIdentifier},
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_core::{
|
||||
crypto::KeyTypeId,
|
||||
u32_trait::{_1, _2, _3, _4},
|
||||
@@ -60,7 +61,6 @@ use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
use pallet_contracts_rpc_runtime_api::ContractExecResult;
|
||||
use pallet_session::{historical as pallet_session_historical};
|
||||
use sp_inherents::{InherentData, CheckInherentsResult};
|
||||
use codec::Encode;
|
||||
use static_assertions::const_assert;
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
@@ -79,6 +79,7 @@ use impls::{CurrencyToVoteHandler, Author, TargetedFeeAdjustment};
|
||||
/// Constant values used within the runtime.
|
||||
pub mod constants;
|
||||
use constants::{time::*, currency::*};
|
||||
use frame_support::traits::InstanceFilter;
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
@@ -168,11 +169,13 @@ impl frame_system::Trait for Runtime {
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * 15 * CENTS + (bytes as Balance) * 6 * CENTS }
|
||||
|
||||
parameter_types! {
|
||||
// One storage item; value is size 4+4+16+32 bytes = 56 bytes.
|
||||
pub const MultisigDepositBase: Balance = 30 * CENTS;
|
||||
// One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes.
|
||||
pub const MultisigDepositBase: Balance = deposit(1, 88);
|
||||
// Additional storage item size of 32 bytes.
|
||||
pub const MultisigDepositFactor: Balance = 5 * CENTS;
|
||||
pub const MultisigDepositFactor: Balance = deposit(0, 32);
|
||||
pub const MaxSignatories: u16 = 100;
|
||||
}
|
||||
|
||||
@@ -186,6 +189,52 @@ impl pallet_utility::Trait for Runtime {
|
||||
type IsCallable = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
// One storage item; key size 32, value size 8; .
|
||||
pub const ProxyDepositBase: Balance = deposit(1, 8);
|
||||
// Additional storage item size of 33 bytes.
|
||||
pub const ProxyDepositFactor: Balance = deposit(0, 33);
|
||||
pub const MaxProxies: u16 = 32;
|
||||
}
|
||||
|
||||
/// The type used to represent the kinds of proxying allowed.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug)]
|
||||
pub enum ProxyType {
|
||||
Any,
|
||||
NonTransfer,
|
||||
Governance,
|
||||
Staking,
|
||||
}
|
||||
impl Default for ProxyType { fn default() -> Self { Self::Any } }
|
||||
impl InstanceFilter<Call> for ProxyType {
|
||||
fn filter(&self, c: &Call) -> bool {
|
||||
match self {
|
||||
ProxyType::Any => true,
|
||||
ProxyType::NonTransfer => !matches!(c,
|
||||
Call::Balances(..) | Call::Utility(..)
|
||||
| Call::Vesting(pallet_vesting::Call::vested_transfer(..))
|
||||
| Call::Indices(pallet_indices::Call::transfer(..))
|
||||
),
|
||||
ProxyType::Governance => matches!(c,
|
||||
Call::Democracy(..) | Call::Council(..) | Call::Society(..)
|
||||
| Call::TechnicalCommittee(..) | Call::Elections(..) | Call::Treasury(..)
|
||||
),
|
||||
ProxyType::Staking => matches!(c, Call::Staking(..)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_proxy::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Call = Call;
|
||||
type Currency = Balances;
|
||||
type IsCallable = ();
|
||||
type ProxyType = ProxyType;
|
||||
type ProxyDepositBase = ProxyDepositBase;
|
||||
type ProxyDepositFactor = ProxyDepositFactor;
|
||||
type MaxProxies = MaxProxies;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * MaximumBlockWeight::get();
|
||||
}
|
||||
@@ -759,6 +808,7 @@ construct_runtime!(
|
||||
Recovery: pallet_recovery::{Module, Call, Storage, Event<T>},
|
||||
Vesting: pallet_vesting::{Module, Call, Storage, Event<T>, Config<T>},
|
||||
Scheduler: pallet_scheduler::{Module, Call, Storage, Event<T>},
|
||||
Proxy: pallet_proxy::{Module, Call, Storage, Event},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1009,6 +1059,7 @@ impl_runtime_apis! {
|
||||
add_benchmark!(params, batches, b"identity", Identity);
|
||||
add_benchmark!(params, batches, b"im-online", ImOnline);
|
||||
add_benchmark!(params, batches, b"offences", OffencesBench::<Runtime>);
|
||||
add_benchmark!(params, batches, b"proxy", Proxy);
|
||||
add_benchmark!(params, batches, b"scheduler", Scheduler);
|
||||
add_benchmark!(params, batches, b"session", SessionBench::<Runtime>);
|
||||
add_benchmark!(params, batches, b"staking", Staking);
|
||||
|
||||
@@ -57,8 +57,9 @@ fn import_single_good_block_works() {
|
||||
|
||||
match import_single_block(
|
||||
&mut substrate_test_runtime_client::new(),
|
||||
BlockOrigin::File, block,
|
||||
&mut PassThroughVerifier(true),
|
||||
BlockOrigin::File,
|
||||
block,
|
||||
&mut PassThroughVerifier(true)
|
||||
) {
|
||||
Ok(BlockImportResult::ImportedUnknown(ref num, ref aux, ref org))
|
||||
if *num == number && *aux == expected_aux && *org == Some(peer_id) => {}
|
||||
@@ -70,7 +71,8 @@ fn import_single_good_block_works() {
|
||||
fn import_single_good_known_block_is_ignored() {
|
||||
let (mut client, _hash, number, _, block) = prepare_good_block();
|
||||
match import_single_block(
|
||||
&mut client, BlockOrigin::File,
|
||||
&mut client,
|
||||
BlockOrigin::File,
|
||||
block,
|
||||
&mut PassThroughVerifier(true)
|
||||
) {
|
||||
@@ -85,8 +87,9 @@ fn import_single_good_block_without_header_fails() {
|
||||
block.header = None;
|
||||
match import_single_block(
|
||||
&mut substrate_test_runtime_client::new(),
|
||||
BlockOrigin::File, block,
|
||||
&mut PassThroughVerifier(true),
|
||||
BlockOrigin::File,
|
||||
block,
|
||||
&mut PassThroughVerifier(true)
|
||||
) {
|
||||
Err(BlockImportError::IncompleteHeader(ref org)) if *org == Some(peer_id) => {}
|
||||
_ => panic!()
|
||||
|
||||
@@ -1257,12 +1257,9 @@ where
|
||||
) {
|
||||
if amount.is_zero() || reasons.is_none() { return }
|
||||
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
|
||||
let mut locks = Self::locks(who).into_iter().filter_map(|l|
|
||||
if l.id == id {
|
||||
new_lock.take()
|
||||
} else {
|
||||
Some(l)
|
||||
}).collect::<Vec<_>>();
|
||||
let mut locks = Self::locks(who).into_iter()
|
||||
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(lock) = new_lock {
|
||||
locks.push(lock)
|
||||
}
|
||||
|
||||
@@ -97,16 +97,6 @@ fn account_vote<T: Trait>(b: BalanceOf<T>) -> AccountVote<BalanceOf<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_activate_proxy<T: Trait>(u: u32) -> Result<(T::AccountId, T::AccountId), &'static str> {
|
||||
let caller = funded_account::<T>("caller", u);
|
||||
let voter = funded_account::<T>("voter", u);
|
||||
|
||||
Democracy::<T>::open_proxy(RawOrigin::Signed(caller.clone()).into(), voter.clone())?;
|
||||
Democracy::<T>::activate_proxy(RawOrigin::Signed(voter.clone()).into(), caller.clone())?;
|
||||
|
||||
Ok((caller, voter))
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
_ { }
|
||||
|
||||
@@ -215,70 +205,6 @@ benchmarks! {
|
||||
assert_eq!(tally.nays, 1000.into(), "changed vote was not recorded");
|
||||
}
|
||||
|
||||
// Basically copy paste of `vote_new`
|
||||
proxy_vote_new {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
let (caller, voter) = open_activate_proxy::<T>(0)?;
|
||||
let account_vote = account_vote::<T>(100.into());
|
||||
|
||||
// Populate existing direct votes for the voter, they can vote on their own behalf
|
||||
for i in 0 .. r {
|
||||
let ref_idx = add_referendum::<T>(i)?;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(voter.clone()).into(), ref_idx, account_vote.clone())?;
|
||||
}
|
||||
|
||||
let referendum_index = add_referendum::<T>(r)?;
|
||||
|
||||
}: proxy_vote(RawOrigin::Signed(caller), referendum_index, account_vote)
|
||||
verify {
|
||||
let votes = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), (r + 1) as usize, "Vote was not recorded.");
|
||||
}
|
||||
|
||||
// Basically copy paste of `vote_existing`
|
||||
proxy_vote_existing {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
let (caller, voter) = open_activate_proxy::<T>(0)?;
|
||||
let account_vote = account_vote::<T>(100.into());
|
||||
|
||||
// We need to create existing direct votes
|
||||
for i in 0 ..=r {
|
||||
let ref_idx = add_referendum::<T>(i)?;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(voter.clone()).into(), ref_idx, account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), (r + 1) as usize, "Votes were not recorded.");
|
||||
|
||||
// Change vote from aye to nay
|
||||
let nay = Vote { aye: false, conviction: Conviction::Locked1x };
|
||||
let new_vote = AccountVote::Standard { vote: nay, balance: 1000.into() };
|
||||
let referendum_index = Democracy::<T>::referendum_count() - 1;
|
||||
|
||||
// This tests when a user changes a vote
|
||||
}: proxy_vote(RawOrigin::Signed(caller.clone()), referendum_index, new_vote)
|
||||
verify {
|
||||
let votes = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), (r + 1) as usize, "Vote was incorrectly added");
|
||||
let referendum_info = Democracy::<T>::referendum_info(referendum_index)
|
||||
.ok_or("referendum doesn't exist")?;
|
||||
let tally = match referendum_info {
|
||||
ReferendumInfo::Ongoing(r) => r.tally,
|
||||
_ => return Err("referendum not ongoing"),
|
||||
};
|
||||
assert_eq!(tally.nays, 1000.into(), "changed vote was not recorded");
|
||||
}
|
||||
|
||||
emergency_cancel {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
let origin = T::CancellationOrigin::successful_origin();
|
||||
@@ -505,33 +431,6 @@ benchmarks! {
|
||||
}
|
||||
}
|
||||
|
||||
activate_proxy {
|
||||
let u in 1 .. MAX_USERS;
|
||||
|
||||
let caller: T::AccountId = funded_account::<T>("caller", u);
|
||||
let proxy: T::AccountId = funded_account::<T>("proxy", u);
|
||||
Democracy::<T>::open_proxy(RawOrigin::Signed(proxy.clone()).into(), caller.clone())?;
|
||||
}: _(RawOrigin::Signed(caller.clone()), proxy.clone())
|
||||
verify {
|
||||
assert_eq!(Democracy::<T>::proxy(proxy), Some(ProxyState::Active(caller)));
|
||||
}
|
||||
|
||||
close_proxy {
|
||||
let u in 1 .. MAX_USERS;
|
||||
let (caller, _) = open_activate_proxy::<T>(u)?;
|
||||
}: _(RawOrigin::Signed(caller.clone()))
|
||||
verify {
|
||||
assert_eq!(Democracy::<T>::proxy(caller), None);
|
||||
}
|
||||
|
||||
deactivate_proxy {
|
||||
let u in 1 .. MAX_USERS;
|
||||
let (caller, voter) = open_activate_proxy::<T>(u)?;
|
||||
}: _(RawOrigin::Signed(voter.clone()), caller.clone())
|
||||
verify {
|
||||
assert_eq!(Democracy::<T>::proxy(caller), Some(ProxyState::Open(voter)));
|
||||
}
|
||||
|
||||
delegate {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
@@ -760,14 +659,6 @@ benchmarks! {
|
||||
assert_eq!(voting.locked_balance(), base_balance);
|
||||
}
|
||||
|
||||
open_proxy {
|
||||
let u in 1 .. MAX_USERS;
|
||||
|
||||
let caller: T::AccountId = funded_account::<T>("caller", u);
|
||||
let proxy: T::AccountId = funded_account::<T>("proxy", u);
|
||||
|
||||
}: _(RawOrigin::Signed(proxy), caller)
|
||||
|
||||
remove_vote {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
@@ -831,131 +722,6 @@ benchmarks! {
|
||||
assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed");
|
||||
}
|
||||
|
||||
// This is a copy of delegate benchmark, but with `open_activate_proxy`
|
||||
proxy_delegate {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
let initial_balance: BalanceOf<T> = 100.into();
|
||||
let delegated_balance: BalanceOf<T> = 1000.into();
|
||||
|
||||
let (caller, voter) = open_activate_proxy::<T>(0)?;
|
||||
|
||||
// Voter will initially delegate to `old_delegate`
|
||||
let old_delegate: T::AccountId = funded_account::<T>("old_delegate", r);
|
||||
Democracy::<T>::delegate(
|
||||
RawOrigin::Signed(voter.clone()).into(),
|
||||
old_delegate.clone(),
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
)?;
|
||||
let (target, balance) = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Delegating { target, balance, .. } => (target, balance),
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(target, old_delegate, "delegation target didn't work");
|
||||
assert_eq!(balance, delegated_balance, "delegation balance didn't work");
|
||||
// Voter will now switch to `new_delegate`
|
||||
let new_delegate: T::AccountId = funded_account::<T>("new_delegate", r);
|
||||
let account_vote = account_vote::<T>(initial_balance);
|
||||
// We need to create existing direct votes for the `new_delegate`
|
||||
for i in 0..r {
|
||||
let ref_idx = add_referendum::<T>(i)?;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::get(&new_delegate) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes were not recorded.");
|
||||
}: _(RawOrigin::Signed(caller.clone()), new_delegate.clone(), Conviction::Locked1x, delegated_balance)
|
||||
verify {
|
||||
let (target, balance) = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Delegating { target, balance, .. } => (target, balance),
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(target, new_delegate, "delegation target didn't work");
|
||||
assert_eq!(balance, delegated_balance, "delegation balance didn't work");
|
||||
let delegations = match VotingOf::<T>::get(&new_delegate) {
|
||||
Voting::Direct { delegations, .. } => delegations,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(delegations.capital, delegated_balance, "delegation was not recorded.");
|
||||
}
|
||||
|
||||
// This is a copy of undelegate benchmark, but with `open_activate_proxy`
|
||||
proxy_undelegate {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
let initial_balance: BalanceOf<T> = 100.into();
|
||||
let delegated_balance: BalanceOf<T> = 1000.into();
|
||||
|
||||
let (caller, voter) = open_activate_proxy::<T>(0)?;
|
||||
// Caller will delegate
|
||||
let the_delegate: T::AccountId = funded_account::<T>("delegate", r);
|
||||
Democracy::<T>::delegate(
|
||||
RawOrigin::Signed(voter.clone()).into(),
|
||||
the_delegate.clone(),
|
||||
Conviction::Locked1x,
|
||||
delegated_balance,
|
||||
)?;
|
||||
let (target, balance) = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Delegating { target, balance, .. } => (target, balance),
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(target, the_delegate, "delegation target didn't work");
|
||||
assert_eq!(balance, delegated_balance, "delegation balance didn't work");
|
||||
// We need to create votes direct votes for the `delegate`
|
||||
let account_vote = account_vote::<T>(initial_balance);
|
||||
for i in 0..r {
|
||||
let ref_idx = add_referendum::<T>(i)?;
|
||||
Democracy::<T>::vote(
|
||||
RawOrigin::Signed(the_delegate.clone()).into(),
|
||||
ref_idx,
|
||||
account_vote.clone()
|
||||
)?;
|
||||
}
|
||||
let votes = match VotingOf::<T>::get(&the_delegate) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes were not recorded.");
|
||||
}: _(RawOrigin::Signed(caller.clone()))
|
||||
verify {
|
||||
// Voting should now be direct
|
||||
match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { .. } => (),
|
||||
_ => return Err("undelegation failed"),
|
||||
}
|
||||
}
|
||||
|
||||
proxy_remove_vote {
|
||||
let r in 1 .. MAX_REFERENDUMS;
|
||||
|
||||
let (caller, voter) = open_activate_proxy::<T>(0)?;
|
||||
let account_vote = account_vote::<T>(100.into());
|
||||
|
||||
for i in 0 .. r {
|
||||
let ref_idx = add_referendum::<T>(i)?;
|
||||
Democracy::<T>::vote(RawOrigin::Signed(voter.clone()).into(), ref_idx, account_vote.clone())?;
|
||||
}
|
||||
|
||||
let votes = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), r as usize, "Votes not created");
|
||||
|
||||
let referendum_index = r - 1;
|
||||
|
||||
}: _(RawOrigin::Signed(caller.clone()), referendum_index)
|
||||
verify {
|
||||
let votes = match VotingOf::<T>::get(&voter) {
|
||||
Voting::Direct { votes, .. } => votes,
|
||||
_ => return Err("Votes are not direct"),
|
||||
};
|
||||
assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed");
|
||||
}
|
||||
|
||||
enact_proposal_execute {
|
||||
// Num of bytes in encoded proposal
|
||||
let b in 0 .. MAX_BYTES;
|
||||
@@ -1012,8 +778,6 @@ mod tests {
|
||||
assert_ok!(test_benchmark_second::<Test>());
|
||||
assert_ok!(test_benchmark_vote_new::<Test>());
|
||||
assert_ok!(test_benchmark_vote_existing::<Test>());
|
||||
assert_ok!(test_benchmark_proxy_vote_new::<Test>());
|
||||
assert_ok!(test_benchmark_proxy_vote_existing::<Test>());
|
||||
assert_ok!(test_benchmark_emergency_cancel::<Test>());
|
||||
assert_ok!(test_benchmark_external_propose::<Test>());
|
||||
assert_ok!(test_benchmark_external_propose_majority::<Test>());
|
||||
@@ -1025,10 +789,6 @@ mod tests {
|
||||
assert_ok!(test_benchmark_on_initialize_external::<Test>());
|
||||
assert_ok!(test_benchmark_on_initialize_public::<Test>());
|
||||
assert_ok!(test_benchmark_on_initialize_no_launch_no_maturing::<Test>());
|
||||
assert_ok!(test_benchmark_open_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_activate_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_close_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_deactivate_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_delegate::<Test>());
|
||||
assert_ok!(test_benchmark_undelegate::<Test>());
|
||||
assert_ok!(test_benchmark_clear_public_proposals::<Test>());
|
||||
@@ -1039,9 +799,6 @@ mod tests {
|
||||
assert_ok!(test_benchmark_unlock_set::<Test>());
|
||||
assert_ok!(test_benchmark_remove_vote::<Test>());
|
||||
assert_ok!(test_benchmark_remove_other_vote::<Test>());
|
||||
assert_ok!(test_benchmark_proxy_delegate::<Test>());
|
||||
assert_ok!(test_benchmark_proxy_undelegate::<Test>());
|
||||
assert_ok!(test_benchmark_proxy_remove_vote::<Test>());
|
||||
assert_ok!(test_benchmark_enact_proposal_execute::<Test>());
|
||||
assert_ok!(test_benchmark_enact_proposal_slash::<Test>());
|
||||
});
|
||||
|
||||
@@ -52,8 +52,6 @@
|
||||
//! account or an external origin) suggests that the system adopt.
|
||||
//! - **Referendum:** A proposal that is in the process of being voted on for
|
||||
//! either acceptance or rejection as a change to the system.
|
||||
//! - **Proxy:** An account that has full voting power on behalf of a separate "Stash" account
|
||||
//! that holds the funds.
|
||||
//! - **Delegation:** The act of granting your voting power to the decisions of another account for
|
||||
//! up to a certain conviction.
|
||||
//!
|
||||
@@ -93,18 +91,6 @@
|
||||
//! - `reap_vote` - Remove some account's expired votes.
|
||||
//! - `unlock` - Redetermine the account's balance lock, potentially making tokens available.
|
||||
//!
|
||||
//! Proxy administration:
|
||||
//! - `activate_proxy` - Activates a proxy that is already open to the sender.
|
||||
//! - `close_proxy` - Clears the proxy status, called by the proxy.
|
||||
//! - `deactivate_proxy` - Deactivates a proxy back to the open status, called by the stash.
|
||||
//! - `open_proxy` - Opens a proxy account on behalf of the sender.
|
||||
//!
|
||||
//! Proxy actions:
|
||||
//! - `proxy_vote` - Votes in a referendum on behalf of a stash account.
|
||||
//! - `proxy_unvote` - Cancel a previous vote, done on behalf of the voter by a proxy.
|
||||
//! - `proxy_delegate` - Delegate voting power, done on behalf of the voter by a proxy.
|
||||
//! - `proxy_undelegate` - Stop delegating voting power, done on behalf of the voter by a proxy.
|
||||
//!
|
||||
//! Preimage actions:
|
||||
//! - `note_preimage` - Registers the preimage for an upcoming proposal, requires
|
||||
//! a deposit that is returned once the proposal is enacted.
|
||||
@@ -191,7 +177,7 @@ mod types;
|
||||
pub use vote_threshold::{Approved, VoteThreshold};
|
||||
pub use vote::{Vote, AccountVote, Voting};
|
||||
pub use conviction::Conviction;
|
||||
pub use types::{ReferendumInfo, ReferendumStatus, ProxyState, Tally, UnvoteScope, Delegations};
|
||||
pub use types::{ReferendumInfo, ReferendumStatus, Tally, UnvoteScope, Delegations};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -376,14 +362,6 @@ decl_storage! {
|
||||
/// TWOX-NOTE: SAFE as `AccountId`s are crypto hashes anyway.
|
||||
pub VotingOf: map hasher(twox_64_concat) T::AccountId => Voting<BalanceOf<T>, T::AccountId, T::BlockNumber>;
|
||||
|
||||
/// Who is able to vote for whom. Value is the fund-holding account, key is the
|
||||
/// vote-transaction-sending account.
|
||||
///
|
||||
/// TWOX-NOTE: OK ― `AccountId` is a secure hash.
|
||||
// TODO: Refactor proxy into its own pallet.
|
||||
// https://github.com/paritytech/substrate/issues/5322
|
||||
pub Proxy get(fn proxy): map hasher(twox_64_concat) T::AccountId => Option<ProxyState<T::AccountId>>;
|
||||
|
||||
/// Accounts for which there are locks in action which may be removed at some point in the
|
||||
/// future. The value is the block number at which the lock expires and may be removed.
|
||||
///
|
||||
@@ -467,8 +445,6 @@ decl_error! {
|
||||
ValueLow,
|
||||
/// Proposal does not exist
|
||||
ProposalMissing,
|
||||
/// Not a proxy
|
||||
NotProxy,
|
||||
/// Unknown index
|
||||
BadIndex,
|
||||
/// Cannot cancel the same proposal twice
|
||||
@@ -485,10 +461,6 @@ decl_error! {
|
||||
NoProposal,
|
||||
/// Identity may not veto a proposal twice
|
||||
AlreadyVetoed,
|
||||
/// Already a proxy
|
||||
AlreadyProxy,
|
||||
/// Wrong proxy
|
||||
WrongProxy,
|
||||
/// Not delegated
|
||||
NotDelegated,
|
||||
/// Preimage already noted
|
||||
@@ -511,12 +483,6 @@ decl_error! {
|
||||
NotLocked,
|
||||
/// The lock on the account to be unlocked has not yet expired.
|
||||
NotExpired,
|
||||
/// A proxy-pairing was attempted to an account that was not open.
|
||||
NotOpen,
|
||||
/// A proxy-pairing was attempted to an account that was open to another account.
|
||||
WrongOpen,
|
||||
/// A proxy-de-pairing was attempted to an account that was not active.
|
||||
NotActive,
|
||||
/// The given account did not vote on the referendum.
|
||||
NotVoter,
|
||||
/// The actor has no permission to conduct the action.
|
||||
@@ -575,27 +541,6 @@ mod weight_for {
|
||||
.saturating_add(votes.saturating_mul(8_000_000))
|
||||
}
|
||||
|
||||
/// Calculate the weight for `proxy_delegate`.
|
||||
/// same as `delegate with additional:
|
||||
/// - Db reads: `Proxy`, `proxy account`
|
||||
/// - Db writes: `proxy account`
|
||||
/// - Base Weight: 68.61 + 8.039 * R µs
|
||||
pub fn proxy_delegate<T: Trait>(votes: Weight) -> Weight {
|
||||
T::DbWeight::get().reads_writes(votes.saturating_add(5), votes.saturating_add(4))
|
||||
.saturating_add(69_000_000)
|
||||
.saturating_add(votes.saturating_mul(8_000_000))
|
||||
}
|
||||
|
||||
/// Calculate the weight for `proxy_undelegate`.
|
||||
/// same as `undelegate with additional:
|
||||
/// Db reads: `Proxy`
|
||||
/// Base Weight: 39 + 7.958 * R µs
|
||||
pub fn proxy_undelegate<T: Trait>(votes: Weight) -> Weight {
|
||||
T::DbWeight::get().reads_writes(votes.saturating_add(3), votes.saturating_add(2))
|
||||
.saturating_add(40_000_000)
|
||||
.saturating_add(votes.saturating_mul(8_000_000))
|
||||
}
|
||||
|
||||
/// Calculate the weight for `note_preimage`.
|
||||
/// # <weight>
|
||||
/// - Complexity: `O(E)` with E size of `encoded_proposal` (protected by a required deposit).
|
||||
@@ -766,34 +711,6 @@ decl_module! {
|
||||
Self::try_vote(&who, ref_index, vote)
|
||||
}
|
||||
|
||||
/// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact
|
||||
/// the proposal; otherwise it is a vote to keep the status quo.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_.
|
||||
///
|
||||
/// - `ref_index`: The index of the referendum to proxy vote for.
|
||||
/// - `vote`: The vote configuration.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Complexity: `O(R)` where R is the number of referendums the proxy has voted on.
|
||||
/// weight is charged as if maximum votes.
|
||||
/// - Db reads: `ReferendumInfoOf`, `VotingOf`, `balances locks`, `Proxy`, `proxy account`
|
||||
/// - Db writes: `ReferendumInfoOf`, `VotingOf`, `balances locks`
|
||||
/// ------------
|
||||
/// - Base Weight:
|
||||
/// - Proxy Vote New: 54.35 + .344 * R µs
|
||||
/// - Proxy Vote Existing: 54.35 + .35 * R µs
|
||||
/// # </weight>
|
||||
#[weight = 55_000_000 + 350_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(5, 3)]
|
||||
fn proxy_vote(origin,
|
||||
#[compact] ref_index: ReferendumIndex,
|
||||
vote: AccountVote<BalanceOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let voter = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?;
|
||||
Self::try_vote(&voter, ref_index, vote)
|
||||
}
|
||||
|
||||
/// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same
|
||||
/// referendum.
|
||||
///
|
||||
@@ -1028,86 +945,6 @@ decl_module! {
|
||||
})
|
||||
}
|
||||
|
||||
/// Specify a proxy that is already open to us. Called by the stash.
|
||||
///
|
||||
/// NOTE: Used to be called `set_proxy`.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_.
|
||||
///
|
||||
/// - `proxy`: The account that will be activated as proxy.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Complexity: `O(1)`
|
||||
/// - Db reads: `Proxy`
|
||||
/// - Db writes: `Proxy`
|
||||
/// - Base Weight: 7.972 µs
|
||||
/// # </weight>
|
||||
#[weight = 8_000_000 + T::DbWeight::get().reads_writes(1, 1)]
|
||||
fn activate_proxy(origin, proxy: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::try_mutate(&proxy, |a| match a.take() {
|
||||
None => Err(Error::<T>::NotOpen),
|
||||
Some(ProxyState::Active(_)) => Err(Error::<T>::AlreadyProxy),
|
||||
Some(ProxyState::Open(x)) if &x == &who => {
|
||||
*a = Some(ProxyState::Active(who));
|
||||
Ok(())
|
||||
}
|
||||
Some(ProxyState::Open(_)) => Err(Error::<T>::WrongOpen),
|
||||
})?;
|
||||
}
|
||||
|
||||
/// Clear the proxy. Called by the proxy.
|
||||
///
|
||||
/// NOTE: Used to be called `resign_proxy`.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Complexity: `O(1)`
|
||||
/// - Db reads: `Proxy`, `sender account`
|
||||
/// - Db writes: `Proxy`, `sender account`
|
||||
/// - Base Weight: 15.41 µs
|
||||
/// # </weight>
|
||||
#[weight = 16_000_000 + T::DbWeight::get().reads_writes(1, 1)]
|
||||
fn close_proxy(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::mutate(&who, |a| {
|
||||
if a.is_some() {
|
||||
system::Module::<T>::dec_ref(&who);
|
||||
}
|
||||
*a = None;
|
||||
});
|
||||
}
|
||||
|
||||
/// Deactivate the proxy, but leave open to this account. Called by the stash.
|
||||
///
|
||||
/// The proxy must already be active.
|
||||
///
|
||||
/// NOTE: Used to be called `remove_proxy`.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_.
|
||||
///
|
||||
/// - `proxy`: The account that will be deactivated as proxy.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Complexity: `O(1)`
|
||||
/// - Db reads: `Proxy`
|
||||
/// - Db writes: `Proxy`
|
||||
/// - Base Weight: 8.03 µs
|
||||
/// # </weight>
|
||||
#[weight = 8_000_000 + T::DbWeight::get().reads_writes(1, 1)]
|
||||
fn deactivate_proxy(origin, proxy: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::try_mutate(&proxy, |a| match a.take() {
|
||||
None | Some(ProxyState::Open(_)) => Err(Error::<T>::NotActive),
|
||||
Some(ProxyState::Active(x)) if &x == &who => {
|
||||
*a = Some(ProxyState::Open(who));
|
||||
Ok(())
|
||||
}
|
||||
Some(ProxyState::Active(_)) => Err(Error::<T>::WrongProxy),
|
||||
})?;
|
||||
}
|
||||
|
||||
/// Delegate the voting power (with some given conviction) of the sending account.
|
||||
///
|
||||
/// The balance delegated is locked for as long as it's delegated, and thereafter for the
|
||||
@@ -1314,33 +1151,6 @@ decl_module! {
|
||||
Self::update_lock(&target);
|
||||
}
|
||||
|
||||
/// Become a proxy.
|
||||
///
|
||||
/// This must be called prior to a later `activate_proxy`.
|
||||
///
|
||||
/// Origin must be a Signed.
|
||||
///
|
||||
/// - `target`: The account whose votes will later be proxied.
|
||||
///
|
||||
/// `close_proxy` must be called before the account can be destroyed.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Complexity: O(1)
|
||||
/// - Db reads: `Proxy`, `proxy account`
|
||||
/// - Db writes: `Proxy`, `proxy account`
|
||||
/// - Base Weight: 14.86 µs
|
||||
/// # </weight>
|
||||
#[weight = 15_000_000 + T::DbWeight::get().reads_writes(2, 2)]
|
||||
fn open_proxy(origin, target: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::mutate(&who, |a| {
|
||||
if a.is_none() {
|
||||
system::Module::<T>::inc_ref(&who);
|
||||
}
|
||||
*a = Some(ProxyState::Open(target));
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove a vote for a referendum.
|
||||
///
|
||||
/// If:
|
||||
@@ -1407,94 +1217,6 @@ decl_module! {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delegate the voting power (with some given conviction) of a proxied account.
|
||||
///
|
||||
/// The balance delegated is locked for as long as it's delegated, and thereafter for the
|
||||
/// time appropriate for the conviction's lock period.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_, and the signing account must have
|
||||
/// been set as the proxy account for `target`.
|
||||
///
|
||||
/// - `target`: The account whole voting power shall be delegated and whose balance locked.
|
||||
/// This account must either:
|
||||
/// - be delegating already; or
|
||||
/// - have no voting activity (if there is, then it will need to be removed/consolidated
|
||||
/// through `reap_vote` or `unvote`).
|
||||
/// - `to`: The account whose voting the `target` account's voting power will follow.
|
||||
/// - `conviction`: The conviction that will be attached to the delegated votes. When the
|
||||
/// account is undelegated, the funds will be locked for the corresponding period.
|
||||
/// - `balance`: The amount of the account's balance to be used in delegating. This must
|
||||
/// not be more than the account's current balance.
|
||||
///
|
||||
/// Emits `Delegated`.
|
||||
///
|
||||
/// # <weight>
|
||||
/// same as `delegate with additional:
|
||||
/// - Db reads: `Proxy`, `proxy account`
|
||||
/// - Db writes: `proxy account`
|
||||
/// - Base Weight: 68.61 + 8.039 * R µs
|
||||
/// # </weight>
|
||||
#[weight = weight_for::proxy_delegate::<T>(T::MaxVotes::get().into())]
|
||||
pub fn proxy_delegate(origin,
|
||||
to: T::AccountId,
|
||||
conviction: Conviction,
|
||||
balance: BalanceOf<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?;
|
||||
let votes = Self::try_delegate(target, to, conviction, balance)?;
|
||||
|
||||
Ok(Some(weight_for::proxy_delegate::<T>(votes.into())).into())
|
||||
}
|
||||
|
||||
/// Undelegate the voting power of a proxied account.
|
||||
///
|
||||
/// Tokens may be unlocked following once an amount of time consistent with the lock period
|
||||
/// of the conviction with which the delegation was issued.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_ and the signing account must be a
|
||||
/// proxy for some other account which is currently delegating.
|
||||
///
|
||||
/// Emits `Undelegated`.
|
||||
///
|
||||
/// # <weight>
|
||||
/// same as `undelegate with additional:
|
||||
/// Db reads: `Proxy`
|
||||
/// Base Weight: 39 + 7.958 * R µs
|
||||
/// # </weight>
|
||||
#[weight = weight_for::proxy_undelegate::<T>(T::MaxVotes::get().into())]
|
||||
fn proxy_undelegate(origin) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?;
|
||||
let votes = Self::try_undelegate(target)?;
|
||||
|
||||
Ok(Some(weight_for::proxy_undelegate::<T>(votes.into())).into())
|
||||
}
|
||||
|
||||
/// Remove a proxied vote for a referendum.
|
||||
///
|
||||
/// Exactly equivalent to `remove_vote` except that it operates on the account that the
|
||||
/// sender is a proxy for.
|
||||
///
|
||||
/// The dispatch origin of this call must be _Signed_ and the signing account must be a
|
||||
/// proxy for some other account which has a registered vote for the referendum of `index`.
|
||||
///
|
||||
/// - `index`: The index of referendum of the vote to be removed.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - `O(R + log R)` where R is the number of referenda that `target` has voted on.
|
||||
/// Weight is calculated for the maximum number of vote.
|
||||
/// - Db reads: `ReferendumInfoOf`, `VotingOf`, `Proxy`
|
||||
/// - Db writes: `ReferendumInfoOf`, `VotingOf`
|
||||
/// - Base Weight: 26.35 + .36 * R µs
|
||||
/// # </weight>
|
||||
#[weight = 26_000_000 + 360_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(2, 3)]
|
||||
fn proxy_remove_vote(origin, index: ReferendumIndex) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?;
|
||||
Self::try_remove_vote(&target, index, UnvoteScope::Any)
|
||||
}
|
||||
|
||||
/// Enact a proposal from a referendum. For now we just make the weight be the maximum.
|
||||
#[weight = T::MaximumBlockWeight::get()]
|
||||
fn enact_proposal(origin, proposal_hash: T::Hash, index: ReferendumIndex) -> DispatchResult {
|
||||
@@ -1538,16 +1260,6 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
// Exposed mutables.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn force_proxy(stash: T::AccountId, proxy: T::AccountId) {
|
||||
Proxy::<T>::mutate(&proxy, |o| {
|
||||
if o.is_none() {
|
||||
system::Module::<T>::inc_ref(&proxy);
|
||||
}
|
||||
*o = Some(ProxyState::Active(stash))
|
||||
})
|
||||
}
|
||||
|
||||
/// Start a referendum.
|
||||
pub fn internal_start_referendum(
|
||||
proposal_hash: T::Hash,
|
||||
|
||||
@@ -38,7 +38,6 @@ mod external_proposing;
|
||||
mod fast_tracking;
|
||||
mod lock_voting;
|
||||
mod preimage;
|
||||
mod proxying;
|
||||
mod public_proposals;
|
||||
mod scheduling;
|
||||
mod voting;
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The tests for functionality concerning proxying.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn proxy_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Democracy::proxy(10), None);
|
||||
assert!(System::allow_death(&10));
|
||||
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::<Test>::NotOpen);
|
||||
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(10), 1));
|
||||
assert!(!System::allow_death(&10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1)));
|
||||
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::<Test>::WrongOpen);
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1)));
|
||||
|
||||
// Can't set when already set.
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::<Test>::AlreadyProxy);
|
||||
|
||||
// But this works because 11 isn't proxying.
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(11), 2));
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1)));
|
||||
assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2)));
|
||||
|
||||
// 2 cannot fire 1's proxy:
|
||||
assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::<Test>::WrongProxy);
|
||||
|
||||
// 1 deactivates their proxy:
|
||||
assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1)));
|
||||
// but the proxy account cannot be killed until the proxy is closed.
|
||||
assert!(!System::allow_death(&10));
|
||||
|
||||
// and then 10 closes it completely:
|
||||
assert_ok!(Democracy::close_proxy(Origin::signed(10)));
|
||||
assert_eq!(Democracy::proxy(10), None);
|
||||
assert!(System::allow_death(&10));
|
||||
|
||||
// 11 just closes without 2's "permission".
|
||||
assert_ok!(Democracy::close_proxy(Origin::signed(11)));
|
||||
assert_eq!(Democracy::proxy(11), None);
|
||||
assert!(System::allow_death(&11));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voting_and_removing_votes_should_work_with_proxy() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_ok!(propose_set_balance_and_note(1, 2, 1));
|
||||
|
||||
fast_forward_to(2);
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(10), 1));
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10));
|
||||
|
||||
assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, aye(1)));
|
||||
assert_eq!(tally(r), Tally { ayes: 1, nays: 0, turnout: 10 });
|
||||
|
||||
assert_ok!(Democracy::proxy_remove_vote(Origin::signed(10), r));
|
||||
assert_eq!(tally(r), Tally { ayes: 0, nays: 0, turnout: 0 });
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegation_and_undelegation_should_work_with_proxy() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(0);
|
||||
assert_ok!(propose_set_balance_and_note(1, 2, 1));
|
||||
fast_forward_to(2);
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(10), 1));
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10));
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, aye(2)));
|
||||
|
||||
assert_ok!(Democracy::proxy_delegate(Origin::signed(10), 2, Conviction::None, 10));
|
||||
assert_eq!(tally(r), Tally { ayes: 3, nays: 0, turnout: 30 });
|
||||
|
||||
assert_ok!(Democracy::proxy_undelegate(Origin::signed(10)));
|
||||
assert_eq!(tally(r), Tally { ayes: 2, nays: 0, turnout: 20 });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,24 +197,6 @@ impl<BlockNumber, Hash, Balance: Default> ReferendumInfo<BlockNumber, Hash, Bala
|
||||
}
|
||||
}
|
||||
|
||||
/// State of a proxy voting account.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
|
||||
pub enum ProxyState<AccountId> {
|
||||
/// Account is open to becoming a proxy but is not yet assigned.
|
||||
Open(AccountId),
|
||||
/// Account is actively being a proxy.
|
||||
Active(AccountId),
|
||||
}
|
||||
|
||||
impl<AccountId> ProxyState<AccountId> {
|
||||
pub (crate) fn as_active(self) -> Option<AccountId> {
|
||||
match self {
|
||||
ProxyState::Active(a) => Some(a),
|
||||
ProxyState::Open(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an `unvote` operation is able to make actions that are not strictly always in the
|
||||
/// interest of an account.
|
||||
pub enum UnvoteScope {
|
||||
|
||||
@@ -274,10 +274,6 @@ decl_storage! {
|
||||
/// of each entry; It may be the direct summed approval stakes, or a weighted version of it.
|
||||
/// Sorted from low to high.
|
||||
pub Leaderboard get(fn leaderboard): Option<Vec<(BalanceOf<T>, T::AccountId)> >;
|
||||
|
||||
/// Who is able to vote for whom. Value is the fund-holding account, key is the
|
||||
/// vote-transaction-sending account.
|
||||
pub Proxy get(fn proxy): map hasher(blake2_128_concat) T::AccountId => Option<T::AccountId>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,8 +288,6 @@ decl_error! {
|
||||
CannotReapPresenting,
|
||||
/// Cannot reap during grace period.
|
||||
ReapGrace,
|
||||
/// Not a proxy.
|
||||
NotProxy,
|
||||
/// Invalid reporter index.
|
||||
InvalidReporterIndex,
|
||||
/// Invalid target index.
|
||||
@@ -430,23 +424,6 @@ decl_module! {
|
||||
Self::do_set_approvals(who, votes, index, hint, value)
|
||||
}
|
||||
|
||||
/// Set candidate approvals from a proxy. Approval slots stay valid as long as candidates in
|
||||
/// those slots are registered.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - Same as `set_approvals` with one additional storage read.
|
||||
/// # </weight>
|
||||
#[weight = 2_500_000_000]
|
||||
fn proxy_set_approvals(origin,
|
||||
votes: Vec<bool>,
|
||||
#[compact] index: VoteIndex,
|
||||
hint: SetIndex,
|
||||
#[compact] value: BalanceOf<T>,
|
||||
) -> DispatchResult {
|
||||
let who = Self::proxy(ensure_signed(origin)?).ok_or(Error::<T>::NotProxy)?;
|
||||
Self::do_set_approvals(who, votes, index, hint, value)
|
||||
}
|
||||
|
||||
/// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices
|
||||
/// must now be either unregistered or registered to a candidate that registered the slot
|
||||
/// after the voter gave their last approval set.
|
||||
|
||||
@@ -868,45 +868,6 @@ fn election_voting_should_work() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn election_proxy_voting_should_work() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(5), 0));
|
||||
|
||||
<Proxy<Test>>::insert(11, 1);
|
||||
<Proxy<Test>>::insert(12, 2);
|
||||
<Proxy<Test>>::insert(13, 3);
|
||||
<Proxy<Test>>::insert(14, 4);
|
||||
assert_ok!(
|
||||
Elections::proxy_set_approvals(Origin::signed(11), vec![true], 0, 0, 10)
|
||||
);
|
||||
assert_ok!(
|
||||
Elections::proxy_set_approvals(Origin::signed(14), vec![true], 0, 1, 40)
|
||||
);
|
||||
|
||||
assert_eq!(Elections::all_approvals_of(&1), vec![true]);
|
||||
assert_eq!(Elections::all_approvals_of(&4), vec![true]);
|
||||
assert_eq!(voter_ids(), vec![1, 4]);
|
||||
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(2), 1));
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(3), 2));
|
||||
|
||||
assert_ok!(
|
||||
Elections::proxy_set_approvals(Origin::signed(12), vec![false, true], 0, 2, 20)
|
||||
);
|
||||
assert_ok!(
|
||||
Elections::proxy_set_approvals(Origin::signed(13), vec![false, true], 0, 3, 30)
|
||||
);
|
||||
|
||||
assert_eq!(Elections::all_approvals_of(&1), vec![true]);
|
||||
assert_eq!(Elections::all_approvals_of(&4), vec![true]);
|
||||
assert_eq!(Elections::all_approvals_of(&2), vec![false, true]);
|
||||
assert_eq!(Elections::all_approvals_of(&3), vec![false, true]);
|
||||
|
||||
assert_eq!(voter_ids(), vec![1, 4, 2, 3]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn election_simple_tally_should_work() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "pallet-proxy"
|
||||
version = "2.0.0-rc2"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.dev"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME proxying pallet"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false }
|
||||
frame-support = { version = "2.0.0-rc2", default-features = false, path = "../support" }
|
||||
frame-system = { version = "2.0.0-rc2", default-features = false, path = "../system" }
|
||||
sp-core = { version = "2.0.0-rc2", default-features = false, path = "../../primitives/core" }
|
||||
sp-runtime = { version = "2.0.0-rc2", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { version = "2.0.0-rc2", default-features = false, path = "../../primitives/std" }
|
||||
|
||||
frame-benchmarking = { version = "2.0.0-rc2", default-features = false, path = "../benchmarking", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "2.0.0-rc2", path = "../../primitives/core" }
|
||||
pallet-balances = { version = "2.0.0-rc2", path = "../balances" }
|
||||
sp-io = { version = "2.0.0-rc2", path = "../../primitives/io" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sp-runtime/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"sp-std/std"
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"frame-support/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,88 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Benchmarks for Proxy Pallet
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_system::RawOrigin;
|
||||
use frame_benchmarking::{benchmarks, account};
|
||||
use sp_runtime::traits::Bounded;
|
||||
use crate::Module as Proxy;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn add_proxies<T: Trait>(n: u32) -> Result<(), &'static str> {
|
||||
let caller: T::AccountId = account("caller", 0, SEED);
|
||||
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
|
||||
for i in 0..n {
|
||||
Proxy::<T>::add_proxy(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
account("target", i, SEED),
|
||||
T::ProxyType::default()
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
_ {
|
||||
let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::<T>(p)?;
|
||||
}
|
||||
|
||||
proxy {
|
||||
let p in ...;
|
||||
// In this case the caller is the "target" proxy
|
||||
let caller: T::AccountId = account("target", p - 1, SEED);
|
||||
// ... and "real" is the traditional caller. This is not a typo.
|
||||
let real: T::AccountId = account("caller", 0, SEED);
|
||||
let call: <T as Trait>::Call = frame_system::Call::<T>::remark(vec![]).into();
|
||||
}: _(RawOrigin::Signed(caller), real, Some(T::ProxyType::default()), Box::new(call))
|
||||
|
||||
add_proxy {
|
||||
let p in ...;
|
||||
let caller: T::AccountId = account("caller", 0, SEED);
|
||||
}: _(RawOrigin::Signed(caller), account("target", T::MaxProxies::get().into(), SEED), T::ProxyType::default())
|
||||
|
||||
remove_proxy {
|
||||
let p in ...;
|
||||
let caller: T::AccountId = account("caller", 0, SEED);
|
||||
}: _(RawOrigin::Signed(caller), account("target", 0, SEED), T::ProxyType::default())
|
||||
|
||||
remove_proxies {
|
||||
let p in ...;
|
||||
let caller: T::AccountId = account("caller", 0, SEED);
|
||||
}: _(RawOrigin::Signed(caller))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{new_test_ext, Test};
|
||||
use frame_support::assert_ok;
|
||||
|
||||
#[test]
|
||||
fn test_benchmarks() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(test_benchmark_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_add_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_remove_proxy::<Test>());
|
||||
assert_ok!(test_benchmark_remove_proxies::<Test>());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Proxy Module
|
||||
//! A module allowing accounts to give permission to other accounts to dispatch types of calls from
|
||||
//! their signed origin.
|
||||
//!
|
||||
//! - [`proxy::Trait`](./trait.Trait.html)
|
||||
//! - [`Call`](./enum.Call.html)
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! [`Call`]: ./enum.Call.html
|
||||
//! [`Trait`]: ./trait.Trait.html
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::{decl_module, decl_event, decl_error, decl_storage, Parameter, ensure};
|
||||
use frame_support::{
|
||||
traits::{Get, ReservableCurrency, Currency, Filter, InstanceFilter},
|
||||
weights::{GetDispatchInfo, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}},
|
||||
dispatch::{PostDispatchInfo, IsSubType},
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed};
|
||||
use sp_runtime::{DispatchResult, traits::{Dispatchable, Zero}};
|
||||
use sp_runtime::traits::Member;
|
||||
|
||||
mod tests;
|
||||
mod benchmarking;
|
||||
|
||||
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
|
||||
|
||||
/// Configuration trait.
|
||||
pub trait Trait: frame_system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event> + Into<<Self as frame_system::Trait>::Event>;
|
||||
|
||||
/// The overarching call type.
|
||||
type Call: Parameter + Dispatchable<Origin=Self::Origin, PostInfo=PostDispatchInfo>
|
||||
+ GetDispatchInfo + From<frame_system::Call<Self>> + IsSubType<Module<Self>, Self>;
|
||||
|
||||
/// The currency mechanism.
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// Is a given call compatible with the proxying subsystem?
|
||||
type IsCallable: Filter<<Self as Trait>::Call>;
|
||||
|
||||
/// A kind of proxy; specified with the proxy and passed in to the `IsProxyable` fitler.
|
||||
/// The instance filter determines whether a given call may be proxied under this type.
|
||||
type ProxyType: Parameter + Member + Ord + PartialOrd + InstanceFilter<<Self as Trait>::Call>
|
||||
+ Default;
|
||||
|
||||
/// The base amount of currency needed to reserve for creating a proxy.
|
||||
///
|
||||
/// This is held for an additional storage item whose value size is
|
||||
/// `sizeof(Balance)` bytes and whose key size is `sizeof(AccountId)` bytes.
|
||||
type ProxyDepositBase: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The amount of currency needed per proxy added.
|
||||
///
|
||||
/// This is held for adding 32 bytes plus an instance of `ProxyType` more into a pre-existing
|
||||
/// storage value.
|
||||
type ProxyDepositFactor: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The maximum amount of proxies allowed for a single account.
|
||||
type MaxProxies: Get<u16>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Proxy {
|
||||
/// The set of account proxies. Maps the account which has delegated to the accounts
|
||||
/// which are being delegated to, together with the amount held on deposit.
|
||||
pub Proxies: map hasher(twox_64_concat) T::AccountId => (Vec<(T::AccountId, T::ProxyType)>, BalanceOf<T>);
|
||||
}
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
pub enum Error for Module<T: Trait> {
|
||||
/// There are too many proxies registered.
|
||||
TooMany,
|
||||
/// Proxy registration not found.
|
||||
NotFound,
|
||||
/// Sender is not a proxy of the account to be proxied.
|
||||
NotProxy,
|
||||
/// A call with a `false` `IsCallable` filter was attempted.
|
||||
Uncallable,
|
||||
/// A call which is incompatible with the proxy type's filter was attempted.
|
||||
Unproxyable,
|
||||
/// Account is already a proxy.
|
||||
Duplicate,
|
||||
/// Call may not be made by proxy because it may escalate its privileges.
|
||||
NoPermission,
|
||||
}
|
||||
}
|
||||
|
||||
decl_event! {
|
||||
/// Events type.
|
||||
pub enum Event {
|
||||
/// A proxy was executed correctly, with the given result.
|
||||
ProxyExecuted(DispatchResult),
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
type Error = Error<T>;
|
||||
|
||||
/// Deposit one of this module's events by using the default implementation.
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Dispatch the given `call` from an account that the sender is authorised for through
|
||||
/// `add_proxy`.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `real`: The account that the proxy will make a call on behalf of.
|
||||
/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
|
||||
/// - `call`: The call to be made by the `real` account.
|
||||
///
|
||||
/// # <weight>
|
||||
/// P is the number of proxies the user has
|
||||
/// - Base weight: 19.87 + .141 * P µs
|
||||
/// - DB weight: 1 storage read.
|
||||
/// - Plus the weight of the `call`
|
||||
/// # </weight>
|
||||
#[weight = {
|
||||
let di = call.get_dispatch_info();
|
||||
(T::DbWeight::get().reads(1)
|
||||
.saturating_add(di.weight)
|
||||
.saturating_add(20 * WEIGHT_PER_MICROS)
|
||||
.saturating_add((140 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into())),
|
||||
di.class)
|
||||
}]
|
||||
fn proxy(origin,
|
||||
real: T::AccountId,
|
||||
force_proxy_type: Option<T::ProxyType>,
|
||||
call: Box<<T as Trait>::Call>
|
||||
) {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(T::IsCallable::filter(&call), Error::<T>::Uncallable);
|
||||
let (_, proxy_type) = Proxies::<T>::get(&real).0.into_iter()
|
||||
.find(|x| &x.0 == &who && force_proxy_type.as_ref().map_or(true, |y| &x.1 == y))
|
||||
.ok_or(Error::<T>::NotProxy)?;
|
||||
match call.is_sub_type() {
|
||||
Some(Call::add_proxy(_, ref pt)) | Some(Call::remove_proxy(_, ref pt)) =>
|
||||
ensure!(&proxy_type == pt, Error::<T>::NoPermission),
|
||||
_ => (),
|
||||
}
|
||||
ensure!(proxy_type.filter(&call), Error::<T>::Unproxyable);
|
||||
let e = call.dispatch(frame_system::RawOrigin::Signed(real).into());
|
||||
Self::deposit_event(Event::ProxyExecuted(e.map(|_| ()).map_err(|e| e.error)));
|
||||
}
|
||||
|
||||
/// Register a proxy account for the sender that is able to make calls on its behalf.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `proxy`: The account that the `caller` would like to make a proxy.
|
||||
/// - `proxy_type`: The permissions allowed for this proxy account.
|
||||
///
|
||||
/// # <weight>
|
||||
/// P is the number of proxies the user has
|
||||
/// - Base weight: 17.48 + .176 * P µs
|
||||
/// - DB weight: 1 storage read and write.
|
||||
/// # </weight>
|
||||
#[weight = T::DbWeight::get().reads_writes(1, 1)
|
||||
.saturating_add(18 * WEIGHT_PER_MICROS)
|
||||
.saturating_add((200 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into()))
|
||||
]
|
||||
fn add_proxy(origin, proxy: T::AccountId, proxy_type: T::ProxyType) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxies::<T>::try_mutate(&who, |(ref mut proxies, ref mut deposit)| {
|
||||
ensure!(proxies.len() < T::MaxProxies::get() as usize, Error::<T>::TooMany);
|
||||
let typed_proxy = (proxy, proxy_type);
|
||||
let i = proxies.binary_search(&typed_proxy).err().ok_or(Error::<T>::Duplicate)?;
|
||||
proxies.insert(i, typed_proxy);
|
||||
let new_deposit = T::ProxyDepositBase::get()
|
||||
+ T::ProxyDepositFactor::get() * (proxies.len() as u32).into();
|
||||
if new_deposit > *deposit {
|
||||
T::Currency::reserve(&who, new_deposit - *deposit)?;
|
||||
} else if new_deposit < *deposit {
|
||||
T::Currency::unreserve(&who, *deposit - new_deposit);
|
||||
}
|
||||
*deposit = new_deposit;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Unregister a proxy account for the sender.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `proxy`: The account that the `caller` would like to remove as a proxy.
|
||||
/// - `proxy_type`: The permissions currently enabled for the removed proxy account.
|
||||
///
|
||||
/// # <weight>
|
||||
/// P is the number of proxies the user has
|
||||
/// - Base weight: 14.37 + .164 * P µs
|
||||
/// - DB weight: 1 storage read and write.
|
||||
/// # </weight>
|
||||
#[weight = T::DbWeight::get().reads_writes(1, 1)
|
||||
.saturating_add(14 * WEIGHT_PER_MICROS)
|
||||
.saturating_add((160 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into()))
|
||||
]
|
||||
fn remove_proxy(origin, proxy: T::AccountId, proxy_type: T::ProxyType) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxies::<T>::try_mutate_exists(&who, |x| {
|
||||
let (mut proxies, old_deposit) = x.take().ok_or(Error::<T>::NotFound)?;
|
||||
let typed_proxy = (proxy, proxy_type);
|
||||
let i = proxies.binary_search(&typed_proxy).ok().ok_or(Error::<T>::NotFound)?;
|
||||
proxies.remove(i);
|
||||
let new_deposit = if proxies.is_empty() {
|
||||
BalanceOf::<T>::zero()
|
||||
} else {
|
||||
T::ProxyDepositBase::get() + T::ProxyDepositFactor::get() * (proxies.len() as u32).into()
|
||||
};
|
||||
if new_deposit > old_deposit {
|
||||
T::Currency::reserve(&who, new_deposit - old_deposit)?;
|
||||
} else if new_deposit < old_deposit {
|
||||
T::Currency::unreserve(&who, old_deposit - new_deposit);
|
||||
}
|
||||
if !proxies.is_empty() {
|
||||
*x = Some((proxies, new_deposit))
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Unregister all proxy accounts for the sender.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// # <weight>
|
||||
/// P is the number of proxies the user has
|
||||
/// - Base weight: 13.73 + .129 * P µs
|
||||
/// - DB weight: 1 storage read and write.
|
||||
/// # </weight>
|
||||
#[weight = T::DbWeight::get().reads_writes(1, 1)
|
||||
.saturating_add(14 * WEIGHT_PER_MICROS)
|
||||
.saturating_add((130 * WEIGHT_PER_NANOS).saturating_mul(T::MaxProxies::get().into()))
|
||||
]
|
||||
fn remove_proxies(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
let (_, old_deposit) = Proxies::<T>::take(&who);
|
||||
T::Currency::unreserve(&who, old_deposit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for Proxy Pallet
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
assert_ok, assert_noop, impl_outer_origin, parameter_types, impl_outer_dispatch,
|
||||
weights::Weight, impl_outer_event, RuntimeDebug
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header};
|
||||
use crate as proxy;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
impl_outer_event! {
|
||||
pub enum TestEvent for Test {
|
||||
system<T>,
|
||||
pallet_balances<T>,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
impl_outer_dispatch! {
|
||||
pub enum Call for Test where origin: Origin {
|
||||
frame_system::System,
|
||||
pallet_balances::Balances,
|
||||
proxy::Proxy,
|
||||
}
|
||||
}
|
||||
|
||||
// For testing the pallet, we construct most of a mock runtime. This means
|
||||
// first constructing a configuration type (`Test`) which `impl`s each of the
|
||||
// configuration traits of pallets we want to use.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = Call;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type DbWeight = ();
|
||||
type BlockExecutionWeight = ();
|
||||
type ExtrinsicBaseWeight = ();
|
||||
type MaximumExtrinsicWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
type Event = TestEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ProxyDepositBase: u64 = 1;
|
||||
pub const ProxyDepositFactor: u64 = 1;
|
||||
pub const MaxProxies: u16 = 3;
|
||||
}
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug)]
|
||||
pub enum ProxyType {
|
||||
Any,
|
||||
JustTransfer,
|
||||
}
|
||||
impl Default for ProxyType { fn default() -> Self { Self::Any } }
|
||||
impl InstanceFilter<Call> for ProxyType {
|
||||
fn filter(&self, c: &Call) -> bool {
|
||||
match self {
|
||||
ProxyType::Any => true,
|
||||
ProxyType::JustTransfer => match c {
|
||||
Call::Balances(pallet_balances::Call::transfer(..)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct TestIsCallable;
|
||||
impl Filter<Call> for TestIsCallable {
|
||||
fn filter(c: &Call) -> bool {
|
||||
match *c {
|
||||
Call::Balances(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = TestEvent;
|
||||
type Call = Call;
|
||||
type Currency = Balances;
|
||||
type IsCallable = TestIsCallable;
|
||||
type ProxyType = ProxyType;
|
||||
type ProxyDepositBase = ProxyDepositBase;
|
||||
type ProxyDepositFactor = ProxyDepositFactor;
|
||||
type MaxProxies = MaxProxies;
|
||||
}
|
||||
|
||||
type System = frame_system::Module<Test>;
|
||||
type Balances = pallet_balances::Module<Test>;
|
||||
type Proxy = Module<Test>;
|
||||
|
||||
use frame_system::Call as SystemCall;
|
||||
use pallet_balances::Call as BalancesCall;
|
||||
use pallet_balances::Error as BalancesError;
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
fn last_event() -> TestEvent {
|
||||
system::Module::<Test>::events().pop().map(|e| e.event).expect("Event expected")
|
||||
}
|
||||
|
||||
fn expect_event<E: Into<TestEvent>>(e: E) {
|
||||
assert_eq!(last_event(), e.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_remove_proxies_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any));
|
||||
assert_noop!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any), Error::<Test>::Duplicate);
|
||||
assert_eq!(Balances::reserved_balance(1), 2);
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer));
|
||||
assert_eq!(Balances::reserved_balance(1), 3);
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any));
|
||||
assert_eq!(Balances::reserved_balance(1), 4);
|
||||
assert_noop!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::Any), Error::<Test>::TooMany);
|
||||
assert_noop!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::JustTransfer), Error::<Test>::NotFound);
|
||||
assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any));
|
||||
assert_eq!(Balances::reserved_balance(1), 3);
|
||||
assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any));
|
||||
assert_eq!(Balances::reserved_balance(1), 2);
|
||||
assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer));
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_add_proxy_without_balance() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(5), 3, ProxyType::Any));
|
||||
assert_eq!(Balances::reserved_balance(5), 2);
|
||||
assert_noop!(
|
||||
Proxy::add_proxy(Origin::signed(5), 4, ProxyType::Any),
|
||||
BalancesError::<Test, _>::InsufficientBalance
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxying_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer));
|
||||
assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any));
|
||||
|
||||
let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1)));
|
||||
assert_noop!(Proxy::proxy(Origin::signed(4), 1, None, call.clone()), Error::<Test>::NotProxy);
|
||||
assert_noop!(Proxy::proxy(Origin::signed(2), 1, Some(ProxyType::Any), call.clone()), Error::<Test>::NotProxy);
|
||||
assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone()));
|
||||
expect_event(Event::ProxyExecuted(Ok(())));
|
||||
assert_eq!(Balances::free_balance(6), 1);
|
||||
|
||||
let call = Box::new(Call::System(SystemCall::remark(vec![])));
|
||||
assert_noop!(Proxy::proxy(Origin::signed(3), 1, None, call.clone()), Error::<Test>::Uncallable);
|
||||
|
||||
let call = Box::new(Call::Balances(BalancesCall::transfer_keep_alive(6, 1)));
|
||||
assert_noop!(Proxy::proxy(Origin::signed(2), 1, None, call.clone()), Error::<Test>::Unproxyable);
|
||||
assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone()));
|
||||
expect_event(Event::ProxyExecuted(Ok(())));
|
||||
assert_eq!(Balances::free_balance(6), 2);
|
||||
});
|
||||
}
|
||||
@@ -43,6 +43,16 @@ impl<T> Filter<T> for () {
|
||||
fn filter(_: &T) -> bool { true }
|
||||
}
|
||||
|
||||
/// Simple trait for providing a filter over a reference to some type, given an instance of itself.
|
||||
pub trait InstanceFilter<T> {
|
||||
/// Determine if a given value should be allowed through the filter (returns `true`) or not.
|
||||
fn filter(&self, _: &T) -> bool;
|
||||
}
|
||||
|
||||
impl<T> InstanceFilter<T> for () {
|
||||
fn filter(&self, _: &T) -> bool { true }
|
||||
}
|
||||
|
||||
/// An abstraction of a value stored within storage, but possibly as part of a larger composite
|
||||
/// item.
|
||||
pub trait StoredMap<K, T> {
|
||||
|
||||
@@ -168,7 +168,7 @@ decl_error! {
|
||||
WrongTimepoint,
|
||||
/// A timepoint was given, yet no multisig operation is underway.
|
||||
UnexpectedTimepoint,
|
||||
/// A call with a `false` IsCallable filter was attempted.
|
||||
/// A call with a `false` `IsCallable` filter was attempted.
|
||||
Uncallable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +99,11 @@ parameter_types! {
|
||||
pub const MultisigDepositFactor: u64 = 1;
|
||||
pub const MaxSignatories: u16 = 3;
|
||||
}
|
||||
|
||||
pub struct TestIsCallable;
|
||||
impl Filter<Call> for TestIsCallable {
|
||||
fn filter(c: &Call) -> bool {
|
||||
match *c {
|
||||
Call::Balances(pallet_balances::Call::transfer(..)) => true,
|
||||
Call::Balances(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -128,7 +127,7 @@ use pallet_balances::Error as BalancesError;
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 10)],
|
||||
balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
|
||||
Reference in New Issue
Block a user