From 9cd2ca64e97bda3102d3891706c21561131bb464 Mon Sep 17 00:00:00 2001 From: Edwin Date: Wed, 13 Dec 2023 06:48:03 -0600 Subject: [PATCH] Improve vtoken-voting (#1101) * Fix tests compile errors * Add method Utility::BatchAll * Add function set_vote_cap_ratio * Add function vote_cap * Add function vote_to_capital * Add function compute_delegator_total_vote * Fix import * Add function allocate_delegator_votes * Fix function ensure_referendum_expired * Fix test `notify_remove_delegator_vote_fail_works` * Remove tests `split_voting_works` and `abstain_voting_works` * Remove delegator_vote_roles in GenesisConfig * Remove `set_delegator_role` and add `add_delegator` * Fix `ensure_referendum_expired` * Fix integration-tests vtoken_voting::vote_works * Fix compiles * Add function account_vote * Optimize compute_delegator_total_vote * Optimize allocate_delegator_votes * Remove try_select_derivative_index, try_add_delegator_vote, try_sub_delegator_vote * Minor fix * Allow vote by any conviction * Test `add_delegator_works` * Fix test `successful_but_zero_conviction_vote_balance_can_be_unlocked` * Fix integration-tests/bifrost-kusama/src/vtoken_voting.rs * Allow vote by any conviction * Fix * Fix tests * Fix copyright * Fix benchmarking, clippy and copyright * Fix --- Dockerfile | 2 +- integration-tests/bifrost-kusama/Cargo.toml | 2 +- .../bifrost-kusama/src/vtoken_voting.rs | 13 +- pallets/flexible-fee/src/mock.rs | 2 +- pallets/leverage-staking/src/weights.rs | 2 +- pallets/vtoken-voting/Cargo.toml | 1 + pallets/vtoken-voting/src/benchmarking.rs | 38 +- pallets/vtoken-voting/src/call.rs | 10 +- pallets/vtoken-voting/src/lib.rs | 430 ++++++++++-------- pallets/vtoken-voting/src/mock.rs | 17 +- pallets/vtoken-voting/src/tests.rs | 370 ++++++++------- pallets/vtoken-voting/src/vote.rs | 25 + pallets/vtoken-voting/src/weights.rs | 14 +- .../src/governance/fellowship.rs | 14 +- .../src/weights/bifrost_vtoken_voting.rs | 6 +- .../src/governance/fellowship.rs | 14 +- .../src/weights/bifrost_vtoken_voting.rs | 6 +- 17 files changed, 559 insertions(+), 407 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1651db50b..22adac1db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2022 Liebi Technologies (UK) Ltd. +# Copyright (C) Liebi Technologies PTE. LTD. # This file is part of Bifrost. # Bifrost is free software: you can redistribute it and/or modify diff --git a/integration-tests/bifrost-kusama/Cargo.toml b/integration-tests/bifrost-kusama/Cargo.toml index e9475a860..2da8fc0df 100644 --- a/integration-tests/bifrost-kusama/Cargo.toml +++ b/integration-tests/bifrost-kusama/Cargo.toml @@ -33,7 +33,7 @@ parachains-common = { workspace = true } xcm-emulator = { workspace = true } # Local -integration-tests-common = { path = "../common", default-features = false} +integration-tests-common = { path = "../common", default-features = false } bifrost-slp = { workspace = true } bifrost-vtoken-voting = { workspace = true } bifrost-primitives = { workspace = true } diff --git a/integration-tests/bifrost-kusama/src/vtoken_voting.rs b/integration-tests/bifrost-kusama/src/vtoken_voting.rs index 83677554e..0ff148604 100644 --- a/integration-tests/bifrost-kusama/src/vtoken_voting.rs +++ b/integration-tests/bifrost-kusama/src/vtoken_voting.rs @@ -24,7 +24,7 @@ use bifrost_primitives::{ KSM, }; use bifrost_slp::{Ledger, MinimumsMaximums, SubstrateLedger}; -use bifrost_vtoken_voting::{AccountVote, TallyOf, VoteRole}; +use bifrost_vtoken_voting::{AccountVote, TallyOf}; use frame_support::{ assert_ok, dispatch::RawOrigin, @@ -32,7 +32,8 @@ use frame_support::{ weights::Weight, }; use integration_tests_common::{BifrostKusama, BifrostKusamaAlice, Kusama, KusamaAlice}; -use pallet_conviction_voting::{Conviction, Vote}; +use pallet_conviction_voting::Vote; +use sp_runtime::Perbill; use xcm::v3::Parent; use xcm_emulator::{Parachain, RelayChain, TestExt}; @@ -138,12 +139,12 @@ fn vote_works() { }))) )); - assert_ok!(VtokenVoting::set_delegator_role( + assert_ok!(VtokenVoting::set_vote_cap_ratio( RuntimeOrigin::root(), vtoken, - 5, - VoteRole::Standard { aye: true, conviction: Conviction::Locked5x }, + Perbill::from_percent(90) )); + assert_ok!(VtokenVoting::add_delegator(RuntimeOrigin::root(), vtoken, 5)); assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 0)); assert_ok!(VtokenVoting::set_undeciding_timeout(RuntimeOrigin::root(), vtoken, 100)); @@ -165,7 +166,7 @@ fn vote_works() { who: _, vtoken: VKSM, poll_index: 0, - new_vote: _, + token_vote: _, delegator_vote: _, }) ))); diff --git a/pallets/flexible-fee/src/mock.rs b/pallets/flexible-fee/src/mock.rs index acde1a864..32bdce637 100644 --- a/pallets/flexible-fee/src/mock.rs +++ b/pallets/flexible-fee/src/mock.rs @@ -94,7 +94,7 @@ pub(crate) const VTOKENVOTING_VOTE_CALL: ::Runtime RuntimeCall::VtokenVoting(bifrost_vtoken_voting::Call::vote { vtoken: VKSM, poll_index: 1u32, - vote: AccountVote::Split { aye: 1, nay: 1 }, + vtoken_vote: AccountVote::Split { aye: 1, nay: 1 }, }); impl bifrost_asset_registry::Config for Test { diff --git a/pallets/leverage-staking/src/weights.rs b/pallets/leverage-staking/src/weights.rs index b37293727..61ce0902f 100644 --- a/pallets/leverage-staking/src/weights.rs +++ b/pallets/leverage-staking/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Bifrost. -// Copyright (C) 2019-2022 Liebi Technologies (UK) Ltd. +// Copyright (C) Liebi Technologies PTE. LTD. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/pallets/vtoken-voting/Cargo.toml b/pallets/vtoken-voting/Cargo.toml index a739b9f65..cc7e02b77 100644 --- a/pallets/vtoken-voting/Cargo.toml +++ b/pallets/vtoken-voting/Cargo.toml @@ -49,6 +49,7 @@ std = [ "sp-io/std", "sp-runtime/std", "xcm/std", + "xcm-builder/std", ] kusama = [] polkadot = [] diff --git a/pallets/vtoken-voting/src/benchmarking.rs b/pallets/vtoken-voting/src/benchmarking.rs index 11da157bf..54e0392b8 100644 --- a/pallets/vtoken-voting/src/benchmarking.rs +++ b/pallets/vtoken-voting/src/benchmarking.rs @@ -55,12 +55,8 @@ fn init_vote(vtoken: CurrencyIdOf) -> Result<(), BenchmarkError> { T::DerivativeAccount::add_delegator(token, derivative_index, Parent.into()); T::DerivativeAccount::new_delegator_ledger(token, Parent.into()); Pallet::::set_undeciding_timeout(RawOrigin::Root.into(), vtoken, Zero::zero())?; - Pallet::::set_delegator_role( - RawOrigin::Root.into(), - vtoken, - derivative_index, - VoteRole::Standard { aye: true, conviction: Conviction::Locked1x }, - )?; + Pallet::::add_delegator(RawOrigin::Root.into(), vtoken, derivative_index)?; + Pallet::::set_vote_cap_ratio(RawOrigin::Root.into(), vtoken, Perbill::from_percent(10))?; Ok(()) } @@ -190,6 +186,13 @@ mod benchmarks { init_vote::(vtoken)?; Pallet::::vote(origin.clone().into(), vtoken, poll_index, vote)?; + + let notify_origin = + T::ResponseOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let query_id = 0u64; + let response = Response::DispatchResult(MaybeErrorCode::Success); + Pallet::::notify_vote(notify_origin, query_id, response)?; + Pallet::::set_referendum_status( RawOrigin::Root.into(), vtoken, @@ -237,12 +240,11 @@ mod benchmarks { } #[benchmark] - pub fn set_delegator_role() -> Result<(), BenchmarkError> { + pub fn add_delegator() -> Result<(), BenchmarkError> { let origin = T::ControlOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let vtoken = VKSM; let derivative_index = 10; - let vote_role = VoteRole::SplitAbstain; init_vote::(vtoken)?; T::DerivativeAccount::add_delegator( @@ -252,12 +254,7 @@ mod benchmarks { ); #[extrinsic_call] - _( - origin as ::RuntimeOrigin, - vtoken, - derivative_index, - vote_role, - ); + _(origin as ::RuntimeOrigin, vtoken, derivative_index); Ok(()) } @@ -353,6 +350,19 @@ mod benchmarks { Ok(()) } + #[benchmark] + pub fn set_vote_cap_ratio() -> Result<(), BenchmarkError> { + let origin = + T::ControlOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let vtoken = VKSM; + let vote_cap_ratio = Perbill::from_percent(10); + + #[extrinsic_call] + _(origin as ::RuntimeOrigin, vtoken, vote_cap_ratio); + + Ok(()) + } + // This line generates test cases for benchmarking, and could be run by: // `cargo test -p pallet-example-basic --all-features`, you will see one line per case: // `test benchmarking::bench_sort_vector ... ok` diff --git a/pallets/vtoken-voting/src/call.rs b/pallets/vtoken-voting/src/call.rs index 33e65d37f..eaada92fd 100644 --- a/pallets/vtoken-voting/src/call.rs +++ b/pallets/vtoken-voting/src/call.rs @@ -19,7 +19,7 @@ use crate::{AccountVote, BalanceOf, Config, DerivativeIndex, PollIndex}; use parity_scale_codec::{Decode, Encode}; use sp_runtime::{traits::StaticLookup, RuntimeDebug}; -use sp_std::boxed::Box; +use sp_std::prelude::*; #[cfg(feature = "kusama")] pub use kusama::*; @@ -83,14 +83,22 @@ impl ConvictionVotingCall for RelayCall { pub enum Utility { #[codec(index = 1)] AsDerivative(DerivativeIndex, Box), + #[codec(index = 2)] + BatchAll(Vec), } pub trait UtilityCall { fn as_derivative(derivative_index: DerivativeIndex, call: Call) -> Call; + + fn batch_all(calls: Vec) -> Call; } impl UtilityCall> for RelayCall { fn as_derivative(derivative_index: DerivativeIndex, call: Self) -> Self { Self::Utility(Utility::AsDerivative(derivative_index, Box::new(call))) } + + fn batch_all(calls: Vec) -> Self { + Self::Utility(Utility::BatchAll(calls)) + } } diff --git a/pallets/vtoken-voting/src/lib.rs b/pallets/vtoken-voting/src/lib.rs index eafdda21b..3060fbfe0 100644 --- a/pallets/vtoken-voting/src/lib.rs +++ b/pallets/vtoken-voting/src/lib.rs @@ -30,7 +30,6 @@ mod tests; mod call; mod vote; -pub mod migration; pub mod weights; use crate::vote::{Casting, Tally, Voting}; @@ -52,10 +51,12 @@ use frame_support::{ use frame_system::pallet_prelude::{BlockNumberFor, *}; use orml_traits::{MultiCurrency, MultiLockableCurrency}; pub use pallet::*; -use pallet_conviction_voting::UnvoteScope; +use pallet_conviction_voting::{Conviction, UnvoteScope, Vote}; use sp_runtime::{ - traits::{BlockNumberProvider, CheckedSub, Saturating, UniqueSaturatedInto, Zero}, - ArithmeticError, + traits::{ + BlockNumberProvider, Bounded, CheckedDiv, CheckedMul, Saturating, UniqueSaturatedInto, Zero, + }, + ArithmeticError, Perbill, }; use sp_std::prelude::*; pub use weights::WeightInfo; @@ -142,7 +143,7 @@ pub mod pallet { who: AccountIdOf, vtoken: CurrencyIdOf, poll_index: PollIndex, - new_vote: AccountVote>, + token_vote: AccountVote>, delegator_vote: AccountVote>, }, Unlocked { @@ -155,9 +156,8 @@ pub mod pallet { vtoken: CurrencyIdOf, derivative_index: DerivativeIndex, }, - DelegatorRoleSet { + DelegatorAdded { vtoken: CurrencyIdOf, - role: VoteRole, derivative_index: DerivativeIndex, }, ReferendumInfoCreated { @@ -197,6 +197,10 @@ pub mod pallet { query_id: QueryId, response: Response, }, + VoteCapRatioSet { + vtoken: CurrencyIdOf, + vote_cap_ratio: Perbill, + }, } #[pallet::error] @@ -235,10 +239,12 @@ pub mod pallet { MaxVotesReached, /// Maximum number of items reached. TooMany, - /// Change delegator is not allowed. - ChangeDelegator, - /// DelegatorVoteRole mismatch. - DelegatorVoteRoleMismatch, + /// The given vote is not Standard vote. + NotStandardVote, + /// The given conviction is not valid. + InvalidConviction, + /// The given value is out of range. + OutOfRange, } /// Information concerning any given referendum. @@ -305,14 +311,38 @@ pub mod pallet { StorageDoubleMap<_, Twox64Concat, CurrencyIdOf, Twox64Concat, DerivativeIndex, VoteRole>; #[pallet::storage] - pub type DelegatorVote = StorageNMap< + pub type Delegators = StorageMap< _, - ( - NMapKey>, - NMapKey, - NMapKey, - ), - AccountVote>, + Twox64Concat, + CurrencyIdOf, + BoundedVec>, + ValueQuery, + >; + + #[pallet::storage] + pub type VoteCapRatio = + StorageMap<_, Twox64Concat, CurrencyIdOf, Perbill, ValueQuery>; + + #[pallet::storage] + pub type DelegatorVotes = StorageDoubleMap< + _, + Twox64Concat, + CurrencyIdOf, + Twox64Concat, + PollIndex, + BoundedVec<(DerivativeIndex, AccountVote>), ConstU32<100>>, + ValueQuery, + >; + + #[pallet::storage] + pub type PendingDelegatorVotes = StorageDoubleMap< + _, + Twox64Concat, + CurrencyIdOf, + Twox64Concat, + PollIndex, + BoundedVec<(DerivativeIndex, AccountVote>), ConstU32<100>>, + ValueQuery, >; #[pallet::storage] @@ -338,20 +368,20 @@ pub mod pallet { #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { - pub delegator_vote_roles: Vec<(CurrencyIdOf, u8, DerivativeIndex)>, + pub delegators: (CurrencyIdOf, Vec), pub undeciding_timeouts: Vec<(CurrencyIdOf, BlockNumberFor)>, + pub vote_cap_ratio: (CurrencyIdOf, Perbill), } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - self.delegator_vote_roles.iter().for_each(|(vtoken, role, derivative_index)| { - let vote_role = VoteRole::try_from(*role).unwrap(); - DelegatorVoteRole::::insert(vtoken, derivative_index, vote_role); - }); + let (vtoken, delegators) = &self.delegators; + Delegators::::insert(vtoken, BoundedVec::truncate_from(delegators.clone())); self.undeciding_timeouts.iter().for_each(|(vtoken, undeciding_timeout)| { UndecidingTimeout::::insert(vtoken, undeciding_timeout); }); + VoteCapRatio::::insert(self.vote_cap_ratio.0, self.vote_cap_ratio.1); } } @@ -409,18 +439,14 @@ pub mod pallet { origin: OriginFor, vtoken: CurrencyIdOf, #[pallet::compact] poll_index: PollIndex, - vote: AccountVote>, + vtoken_vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::ensure_vtoken(&vtoken)?; ensure!(UndecidingTimeout::::contains_key(vtoken), Error::::NoData); Self::ensure_no_pending_vote(vtoken, poll_index)?; - let new_vote = Self::compute_new_vote(vtoken, vote)?; - let derivative_index = Self::try_select_derivative_index(vtoken, poll_index, new_vote)?; - if let Some(d) = VoteDelegatorFor::::get((&who, vtoken, poll_index)) { - ensure!(d == derivative_index, Error::::ChangeDelegator) - } + let token_vote = Self::compute_token_vote(vtoken, vtoken_vote)?; // create referendum if not exist let mut submitted = false; @@ -438,36 +464,45 @@ pub mod pallet { submitted = true; } - if !DelegatorVote::::contains_key((vtoken, poll_index, derivative_index)) { - let role = DelegatorVoteRole::::get(vtoken, derivative_index) - .ok_or(Error::::NoData)?; - let target_role = VoteRole::from(vote); - ensure!(role == target_role, Error::::DelegatorVoteRoleMismatch); - let default_vote: AccountVote> = target_role.into(); - DelegatorVote::::insert((vtoken, poll_index, derivative_index), default_vote); - } - // record vote info - let maybe_old_vote = Self::try_vote( - &who, + let (maybe_old_vote, maybe_total_vote) = + Self::try_vote(&who, vtoken, poll_index, token_vote, vtoken_vote.balance())?; + + let delegator_total_vote = Self::compute_delegator_total_vote( vtoken, - poll_index, - derivative_index, - new_vote, - vote.balance(), + maybe_total_vote.ok_or(Error::::NoData)?, )?; + let new_delegator_votes = + Self::allocate_delegator_votes(vtoken, poll_index, delegator_total_vote)?; + + PendingDelegatorVotes::::try_mutate(vtoken, poll_index, |item| -> DispatchResult { + for (derivative_index, vote) in new_delegator_votes.iter() { + item.try_push((*derivative_index, *vote)).map_err(|_| Error::::TooMany)?; + } + Ok(()) + })?; // send XCM message - let delegator_vote = DelegatorVote::::get((vtoken, poll_index, derivative_index)) - .ok_or(Error::::NoData)?; - let vote_call = - as ConvictionVotingCall>::vote(poll_index, delegator_vote); + let vote_calls = new_delegator_votes + .iter() + .map(|(_derivative_index, vote)| { + as ConvictionVotingCall>::vote(poll_index, *vote) + }) + .collect::>(); + let vote_call = if vote_calls.len() == 1 { + vote_calls.into_iter().nth(0).ok_or(Error::::NoData)? + } else { + ensure!(false, Error::::NoPermissionYet); + as UtilityCall>>::batch_all(vote_calls) + }; let notify_call = Call::::notify_vote { query_id: 0, response: Default::default() }; let (weight, extra_fee) = T::XcmDestWeightAndFee::get_operation_weight_and_fee( CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?, XcmOperationType::Vote, ) .ok_or(Error::::NoData)?; + + let derivative_index = new_delegator_votes[0].0; Self::send_xcm_with_notify( derivative_index, vote_call, @@ -489,8 +524,8 @@ pub mod pallet { who, vtoken, poll_index, - new_vote, - delegator_vote, + token_vote, + delegator_vote: new_delegator_votes[0].1, }); Ok(()) @@ -531,11 +566,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; Self::ensure_vtoken(&vtoken)?; + ensure!(DelegatorVotes::::get(vtoken, poll_index).len() > 0, Error::::NoData); Self::ensure_referendum_expired(vtoken, poll_index)?; - ensure!( - DelegatorVote::::contains_key((vtoken, poll_index, derivative_index)), - Error::::NoData - ); let notify_call = Call::::notify_remove_delegator_vote { query_id: 0, @@ -590,12 +622,11 @@ pub mod pallet { } #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::set_delegator_role())] - pub fn set_delegator_role( + #[pallet::weight(::WeightInfo::add_delegator())] + pub fn add_delegator( origin: OriginFor, vtoken: CurrencyIdOf, #[pallet::compact] derivative_index: DerivativeIndex, - vote_role: VoteRole, ) -> DispatchResult { T::ControlOrigin::ensure_origin(origin)?; Self::ensure_vtoken(&vtoken)?; @@ -605,17 +636,16 @@ pub mod pallet { Error::::NoData ); ensure!( - !DelegatorVoteRole::::contains_key(vtoken, derivative_index), + !Delegators::::get(vtoken).contains(&derivative_index), Error::::DerivativeIndexOccupied ); - DelegatorVoteRole::::insert(vtoken, derivative_index, vote_role); + Delegators::::try_mutate(vtoken, |vec| -> DispatchResult { + vec.try_push(derivative_index).map_err(|_| Error::::TooMany)?; + Ok(()) + })?; - Self::deposit_event(Event::::DelegatorRoleSet { - vtoken, - role: vote_role, - derivative_index, - }); + Self::deposit_event(Event::::DelegatorAdded { vtoken, derivative_index }); Ok(()) } @@ -684,22 +714,30 @@ pub mod pallet { { if !success { // rollback vote + let _ = PendingDelegatorVotes::::clear(u32::MAX, None); Self::try_remove_vote(&who, vtoken, poll_index, UnvoteScope::Any)?; Self::update_lock(&who, vtoken, &poll_index)?; if let Some((old_vote, vtoken_balance)) = maybe_old_vote { - Self::try_vote( - &who, - vtoken, - poll_index, - derivative_index, - old_vote, - vtoken_balance, - )?; + Self::try_vote(&who, vtoken, poll_index, old_vote, vtoken_balance)?; } } else { if !VoteDelegatorFor::::contains_key((&who, vtoken, poll_index)) { VoteDelegatorFor::::insert((&who, vtoken, poll_index), derivative_index); } + let _ = DelegatorVotes::::clear(u32::MAX, None); + DelegatorVotes::::try_mutate( + vtoken, + poll_index, + |item| -> DispatchResult { + for (derivative_index, vote) in + PendingDelegatorVotes::::take(vtoken, poll_index).iter() + { + item.try_push((*derivative_index, *vote)) + .map_err(|_| Error::::TooMany)?; + } + Ok(()) + }, + )?; } PendingVotingInfo::::remove(query_id); Self::deposit_event(Event::::VoteNotified { vtoken, poll_index, success }); @@ -756,24 +794,12 @@ pub mod pallet { response: Response, ) -> DispatchResult { let responder = Self::ensure_xcm_response_or_governance(origin)?; - if let Some((vtoken, poll_index, derivative_index)) = + if let Some((vtoken, poll_index, _derivative_index)) = PendingRemoveDelegatorVote::::get(query_id) { let success = Response::DispatchResult(MaybeErrorCode::Success) == response; if success { - DelegatorVote::::try_mutate_exists( - (vtoken, poll_index, derivative_index), - |maybe_vote| { - if let Some(inner_vote) = maybe_vote { - inner_vote - .checked_sub(*inner_vote) - .map_err(|_| Error::::NoData)?; - Ok(()) - } else { - Err(Error::::NoData) - } - }, - )?; + let _ = DelegatorVotes::::clear(u32::MAX, None); } PendingRemoveDelegatorVote::::remove(query_id); Self::deposit_event(Event::::DelegatorVoteRemovedNotified { @@ -786,6 +812,21 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::set_vote_cap_ratio())] + pub fn set_vote_cap_ratio( + origin: OriginFor, + vtoken: CurrencyIdOf, + vote_cap_ratio: Perbill, + ) -> DispatchResult { + T::ControlOrigin::ensure_origin(origin)?; + Self::ensure_vtoken(&vtoken)?; + VoteCapRatio::::insert(vtoken, vote_cap_ratio); + Self::deposit_event(Event::::VoteCapRatioSet { vtoken, vote_cap_ratio }); + + Ok(()) + } } impl Pallet { @@ -793,15 +834,18 @@ pub mod pallet { who: &AccountIdOf, vtoken: CurrencyIdOf, poll_index: PollIndex, - derivative_index: DerivativeIndex, vote: AccountVote>, vtoken_balance: BalanceOf, - ) -> Result>, BalanceOf)>, DispatchError> { + ) -> Result< + (Option<(AccountVote>, BalanceOf)>, Option>>), + DispatchError, + > { ensure!( vtoken_balance <= T::MultiCurrency::total_balance(vtoken, who), Error::::InsufficientFunds ); let mut old_vote = None; + let mut total_vote = None; Self::try_access_poll(vtoken, poll_index, |poll_status| { let tally = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; VotingFor::::try_mutate(who, |voting| { @@ -810,39 +854,37 @@ pub mod pallet { Ok(i) => { // Shouldn't be possible to fail, but we handle it gracefully. tally.remove(votes[i].1).ok_or(ArithmeticError::Underflow)?; - Self::try_sub_delegator_vote( - vtoken, poll_index, votes[i].2, votes[i].1, - )?; old_vote = Some((votes[i].1, votes[i].3)); if let Some(approve) = votes[i].1.as_standard() { tally.reduce(approve, *delegations); } votes[i].1 = vote; - votes[i].2 = derivative_index; + votes[i].2 = 0; // Deprecated: derivative_index votes[i].3 = vtoken_balance; }, Err(i) => { votes .try_insert( i, - (poll_index, vote, derivative_index, vtoken_balance), + // Deprecated: derivative_index + (poll_index, vote, 0, vtoken_balance), ) .map_err(|_| Error::::MaxVotesReached)?; }, } // Shouldn't be possible to fail, but we handle it gracefully. tally.add(vote).ok_or(ArithmeticError::Overflow)?; - Self::try_add_delegator_vote(vtoken, poll_index, derivative_index, vote)?; if let Some(approve) = vote.as_standard() { tally.increase(approve, *delegations); } + total_vote = Some(tally.account_vote(Conviction::Locked1x)); } else { return Err(Error::::AlreadyDelegating.into()); } // Extend the lock to `balance` (rather than setting it) since we don't know // what other votes are in place. Self::extend_lock(&who, vtoken, &poll_index, vtoken_balance)?; - Ok(old_vote) + Ok((old_vote, total_vote)) }) }) } @@ -873,7 +915,6 @@ pub mod pallet { ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; - Self::try_sub_delegator_vote(vtoken, poll_index, v.2, v.1)?; if let Some(approve) = v.1.as_standard() { tally.reduce(approve, *delegations); } @@ -1052,9 +1093,9 @@ pub mod pallet { fn ensure_referendum_completed( vtoken: CurrencyIdOf, poll_index: PollIndex, - ) -> Result, DispatchError> { + ) -> DispatchResult { match ReferendumInfoFor::::get(vtoken, poll_index) { - Some(ReferendumInfo::Completed(moment)) => Ok(moment), + Some(ReferendumInfo::Completed(_)) => Ok(()), _ => Err(Error::::NotCompleted.into()), } } @@ -1062,17 +1103,30 @@ pub mod pallet { fn ensure_referendum_expired( vtoken: CurrencyIdOf, poll_index: PollIndex, - ) -> Result, DispatchError> { - match ReferendumInfoFor::::get(vtoken, poll_index) { - Some(ReferendumInfo::Completed(moment)) => { + ) -> DispatchResult { + let delegator_votes = DelegatorVotes::::get(vtoken, poll_index).into_inner(); + let (_derivative_index, delegator_vote) = + delegator_votes.first().ok_or(Error::::NoData)?; + match (ReferendumInfoFor::::get(vtoken, poll_index), delegator_vote.locked_if(true)) + { + (Some(ReferendumInfo::Completed(moment)), Some((lock_periods, _balance))) => { let locking_period = VoteLockingPeriod::::get(vtoken).ok_or(Error::::NoData)?; ensure!( T::RelaychainBlockNumberProvider::current_block_number() >= - moment.saturating_add(locking_period), + moment.saturating_add( + locking_period.saturating_mul(lock_periods.into()) + ), + Error::::NotExpired + ); + Ok(()) + }, + (Some(ReferendumInfo::Completed(moment)), None) => { + ensure!( + T::RelaychainBlockNumberProvider::current_block_number() >= moment, Error::::NotExpired ); - Ok(moment.saturating_add(locking_period)) + Ok(()) }, _ => Err(Error::::NotExpired.into()), } @@ -1081,9 +1135,9 @@ pub mod pallet { fn ensure_referendum_killed( vtoken: CurrencyIdOf, poll_index: PollIndex, - ) -> Result, DispatchError> { + ) -> DispatchResult { match ReferendumInfoFor::::get(vtoken, poll_index) { - Some(ReferendumInfo::Killed(moment)) => Ok(moment), + Some(ReferendumInfo::Killed(_)) => Ok(()), _ => Err(Error::::NotKilled.into()), } } @@ -1117,98 +1171,114 @@ pub mod pallet { } } - fn try_select_derivative_index( + fn compute_token_vote( vtoken: CurrencyIdOf, - poll_index: PollIndex, - new_vote: AccountVote>, - ) -> Result { + vote: AccountVote>, + ) -> Result>, DispatchError> { let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; + let vtoken_supply = + T::VTokenSupplyProvider::get_vtoken_supply(vtoken).ok_or(Error::::NoData)?; + let token_supply = + T::VTokenSupplyProvider::get_token_supply(token).ok_or(Error::::NoData)?; + let mut new_vote = vote; + new_vote + .checked_mul(token_supply) + .and_then(|_| new_vote.checked_div(vtoken_supply))?; - let vote_roles = DelegatorVoteRole::::iter_prefix(vtoken).collect::>(); - let mut delegator_votes = - DelegatorVote::::iter_prefix((vtoken, poll_index)).collect::>(); - let delegator_vote_keys = - delegator_votes.iter().map(|(index, _)| *index).collect::>(); - for vote_role in vote_roles { - if !delegator_vote_keys.contains(&vote_role.0) { - delegator_votes - .push((vote_role.0, AccountVote::>::from(vote_role.1))); - } - } - let mut data = delegator_votes - .into_iter() - .map(|(index, voted)| { - let (_, available_vote) = T::DerivativeAccount::get_stake_info(token, index) - .unwrap_or(Default::default()); - (available_vote, voted, index) - }) - .filter(|(_, voted, _)| VoteRole::from(*voted) == VoteRole::from(new_vote)) - .collect::>(); - data.sort_by(|a, b| { - (b.0.saturating_sub(b.1.balance())).cmp(&(a.0.saturating_sub(a.1.balance()))) - }); + Ok(new_vote) + } - let (available_vote, voted, index) = data.first().ok_or(Error::::NoData)?; - available_vote - .checked_sub(&voted.balance()) - .ok_or(ArithmeticError::Underflow)? - .checked_sub(&new_vote.balance()) - .ok_or(ArithmeticError::Underflow)?; + pub(crate) fn vote_cap(vtoken: CurrencyIdOf) -> Result, DispatchError> { + let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; + let token_supply = + T::VTokenSupplyProvider::get_token_supply(token).ok_or(Error::::NoData)?; + let vote_cap_ratio = VoteCapRatio::::get(vtoken); - Ok(*index) + Ok(vote_cap_ratio * token_supply) } - pub(crate) fn try_add_delegator_vote( - vtoken: CurrencyIdOf, - poll_index: PollIndex, - derivative_index: DerivativeIndex, - vote: AccountVote>, - ) -> Result>, DispatchError> { - DelegatorVote::::try_mutate_exists( - (vtoken, poll_index, derivative_index), - |maybe_vote| match maybe_vote { - Some(inner_vote) => { - inner_vote.checked_add(vote).map_err(|_| ArithmeticError::Overflow)?; - Ok(*inner_vote) - }, - None => Err(Error::::NoData.into()), - }, - ) + pub(crate) fn vote_to_capital(conviction: Conviction, vote: BalanceOf) -> BalanceOf { + let capital = match conviction { + Conviction::None => + vote.checked_mul(&10u8.into()).unwrap_or_else(BalanceOf::::max_value), + x => vote.checked_div(&u8::from(x).into()).unwrap_or_else(Zero::zero), + }; + capital } - fn try_sub_delegator_vote( + pub(crate) fn compute_delegator_total_vote( vtoken: CurrencyIdOf, - poll_index: PollIndex, - derivative_index: DerivativeIndex, vote: AccountVote>, ) -> Result>, DispatchError> { - DelegatorVote::::try_mutate_exists( - (vtoken, poll_index, derivative_index), - |maybe_vote| match maybe_vote { - Some(inner_vote) => { - inner_vote.checked_sub(vote).map_err(|_| ArithmeticError::Underflow)?; - Ok(*inner_vote) - }, - None => Err(Error::::NoData.into()), - }, - ) + let aye = vote.as_standard().ok_or(Error::::NotStandardVote)?; + let conviction_votes = vote + .as_standard_vote() + .ok_or(Error::::NotStandardVote)? + .conviction + .votes(vote.balance()) + .votes; + let vote_cap = Self::vote_cap(vtoken)?; + for i in 0..=6 { + let conviction = + Conviction::try_from(i).map_err(|_| Error::::InvalidConviction)?; + let capital = Self::vote_to_capital(conviction, conviction_votes); + if capital <= vote_cap { + return Ok(AccountVote::new_standard(Vote { aye, conviction }, capital)); + } + } + + Err(Error::::InsufficientFunds.into()) } - fn compute_new_vote( + pub(crate) fn allocate_delegator_votes( vtoken: CurrencyIdOf, - vote: AccountVote>, - ) -> Result>, DispatchError> { + poll_index: PollIndex, + delegator_total_vote: AccountVote>, + ) -> Result>)>, DispatchError> { + let vote_role: VoteRole = delegator_total_vote.into(); + let mut delegator_total_vote = delegator_total_vote; + let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; - let vtoken_supply = - T::VTokenSupplyProvider::get_vtoken_supply(vtoken).ok_or(Error::::NoData)?; - let token_supply = - T::VTokenSupplyProvider::get_token_supply(token).ok_or(Error::::NoData)?; - let mut new_vote = vote; - new_vote - .checked_mul(token_supply) - .and_then(|_| new_vote.checked_div(vtoken_supply))?; + let mut delegator_votes = DelegatorVotes::::get(vtoken, poll_index).into_inner(); + let delegator_vote_keys = + delegator_votes.iter().map(|(index, _)| *index).collect::>(); + for derivative_index in Delegators::::get(vtoken) { + if !delegator_vote_keys.contains(&derivative_index) { + delegator_votes + .push((derivative_index, AccountVote::>::from(vote_role))); + } + } + let data = delegator_votes + .into_iter() + .map(|(derivative_index, _)| { + let (_, available_vote) = + T::DerivativeAccount::get_stake_info(token, derivative_index) + .unwrap_or_default(); + (derivative_index, available_vote) + }) + .collect::>(); - Ok(new_vote) + let mut delegator_votes = Vec::new(); + for (derivative_index, available_vote) in data { + if available_vote >= delegator_total_vote.balance() { + delegator_votes.push((derivative_index, delegator_total_vote)); + return Ok(delegator_votes); + } else { + let account_vote = AccountVote::new_standard( + delegator_total_vote.as_standard_vote().ok_or(Error::::NoData)?, + available_vote, + ); + delegator_votes.push((derivative_index, account_vote)); + delegator_total_vote + .checked_sub(account_vote) + .map_err(|_| ArithmeticError::Underflow)? + } + } + if delegator_total_vote.balance() != Zero::zero() { + return Err(Error::::OutOfRange.into()); + } + + Ok(delegator_votes) } } } diff --git a/pallets/vtoken-voting/src/mock.rs b/pallets/vtoken-voting/src/mock.rs index f8d350147..d924363f3 100644 --- a/pallets/vtoken-voting/src/mock.rs +++ b/pallets/vtoken-voting/src/mock.rs @@ -38,7 +38,7 @@ use pallet_xcm::EnsureResponse; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, BlockNumberProvider, ConstU32, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use xcm::prelude::*; use xcm_builder::FixedWeightBounds; @@ -353,20 +353,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); vtoken_voting::GenesisConfig:: { - delegator_vote_roles: vec![ - (VKSM, 0, 0), - (VKSM, 1, 1), - (VKSM, 2, 2), - (VKSM, 3, 3), - (VKSM, 4, 4), - (VKSM, 5, 5), - (VKSM, 10, 10), - (VKSM, 11, 11), - (VKSM, 15, 15), - (VKSM, 20, 20), - (VKSM, 21, 21), - ], + delegators: (VKSM, vec![0, 1, 2, 3, 4, 5, 10, 11, 15, 20, 21]), undeciding_timeouts: vec![(VKSM, 100)], + vote_cap_ratio: (VKSM, Perbill::from_percent(10)), } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/vtoken-voting/src/tests.rs b/pallets/vtoken-voting/src/tests.rs index ce131fe17..1d70ea26d 100644 --- a/pallets/vtoken-voting/src/tests.rs +++ b/pallets/vtoken-voting/src/tests.rs @@ -42,14 +42,6 @@ fn nay(amount: Balance, conviction: u8) -> AccountVote { AccountVote::Standard { vote, balance: amount } } -fn split(aye: Balance, nay: Balance) -> AccountVote { - AccountVote::Split { aye, nay } -} - -fn split_abstain(aye: Balance, nay: Balance, abstain: Balance) -> AccountVote { - AccountVote::SplitAbstain { aye, nay, abstain } -} - fn tally(vtoken: CurrencyId, poll_index: u32) -> TallyOf { VtokenVoting::ensure_referendum_ongoing(vtoken, poll_index) .expect("No poll") @@ -84,49 +76,10 @@ fn basic_voting_works() { who: ALICE, vtoken, poll_index, - new_vote: aye(2, 5), - delegator_vote: aye(2, 5), - })); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - - assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); - - assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken, &poll_index)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} - -#[test] -fn split_voting_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - split(10, 0) - )); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(1, 0, 10)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - new_vote: split(10, 0), - delegator_vote: split(10, 0), + token_vote: aye(2, 5), + delegator_vote: aye(100, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - split(5, 5) - )); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 5)); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); @@ -136,67 +89,6 @@ fn split_voting_works() { }); } -#[test] -fn abstain_voting_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - split_abstain(0, 0, 10) - )); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - new_vote: split_abstain(0, 0, 10), - delegator_vote: split_abstain(0, 0, 10), - })); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 10)); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(BOB), - vtoken, - poll_index, - split_abstain(0, 0, 20) - )); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: BOB, - vtoken, - poll_index, - new_vote: split_abstain(0, 0, 20), - delegator_vote: split_abstain(0, 0, 30), - })); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 30)); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(BOB), - vtoken, - poll_index, - split_abstain(10, 0, 10) - )); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(1, 0, 30)); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!(usable_balance(vtoken, &BOB), 0); - - assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(1, 0, 20)); - - assert_ok!(VtokenVoting::try_remove_vote(&BOB, vtoken, poll_index, UnvoteScope::Any)); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); - - assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken, &poll_index)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - - assert_ok!(VtokenVoting::update_lock(&BOB, vtoken, &poll_index)); - assert_eq!(usable_balance(vtoken, &BOB), 20); - }); -} - #[test] fn voting_balance_gets_locked() { new_test_ext().execute_with(|| { @@ -228,19 +120,30 @@ fn successful_but_zero_conviction_vote_balance_can_be_unlocked() { let vtoken = VKSM; assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(1, 1))); + assert_eq!(usable_balance(vtoken, &ALICE), 9); assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(BOB), vtoken, poll_index, nay(20, 0))); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(3, 1))); + assert_eq!(usable_balance(vtoken, &ALICE), 7); assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(BOB), vtoken, poll_index, nay(20, 0))); + assert_eq!(usable_balance(vtoken, &BOB), 0); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); + + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); assert_ok!(VtokenVoting::set_referendum_status( RuntimeOrigin::root(), vtoken, poll_index, ReferendumInfoOf::::Completed(3), )); - assert_ok!(VtokenVoting::try_remove_vote(&BOB, vtoken, poll_index, UnvoteScope::Any)); - assert_ok!(VtokenVoting::update_lock(&BOB, vtoken, &poll_index)); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(BOB), vtoken, poll_index)); assert_eq!(usable_balance(vtoken, &BOB), 20); + + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); }); } @@ -528,22 +431,15 @@ fn kill_referendum_with_origin_signed_fails() { } #[test] -fn set_delegator_role_works() { +fn add_delegator_works() { new_test_ext().execute_with(|| { let vtoken = VKSM; let derivative_index: DerivativeIndex = 100; - let role = aye(10, 3).into(); - assert_ok!(VtokenVoting::set_delegator_role( - RuntimeOrigin::root(), - vtoken, - derivative_index, - role, - )); + assert_ok!(VtokenVoting::add_delegator(RuntimeOrigin::root(), vtoken, derivative_index,)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::DelegatorRoleSet { + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::DelegatorAdded { vtoken, - role, derivative_index, })); }); @@ -689,7 +585,7 @@ fn notify_vote_success_works() { let vtoken = VKSM; let query_id = 0; let response = response_success(); - let derivative_index = 5; + let derivative_index = 0; assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); assert_eq!( @@ -700,16 +596,21 @@ fn notify_vote_success_works() { })) ); assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_eq!(tally(vtoken, poll_index), Tally::from_parts(10, 0, 2)); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { who: ALICE, vtoken, poll_index, - new_vote: aye(2, 5), - delegator_vote: aye(2, 5), + token_vote: aye(2, 5), + delegator_vote: aye(100, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); @@ -720,9 +621,14 @@ fn notify_vote_success_works() { tally: TallyOf::::from_parts(10, 0, 2), })) ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() ); System::assert_has_event(RuntimeEvent::VtokenVoting(Event::VoteNotified { vtoken, @@ -807,7 +713,7 @@ fn notify_vote_fail_works() { let vtoken = VKSM; let query_id = 0; let response = response_fail(); - let derivative_index = 5; + let derivative_index = 0; assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); assert_eq!( @@ -817,30 +723,28 @@ fn notify_vote_fail_works() { tally: TallyOf::::from_parts(10, 0, 2), })) ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() ); assert_eq!(tally(vtoken, poll_index), Tally::from_parts(10, 0, 2)); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { who: ALICE, vtoken, poll_index, - new_vote: aye(2, 5), - delegator_vote: aye(2, 5), + token_vote: aye(2, 5), + delegator_vote: aye(100, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); assert_eq!(ReferendumInfoFor::::get(vtoken, poll_index), None); - assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(0, 5)) - ); - System::assert_has_event(RuntimeEvent::VtokenVoting(Event::VoteNotified { - vtoken, - poll_index, - success: false, - })); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { responder: Parent.into(), query_id, @@ -870,23 +774,37 @@ fn notify_remove_delegator_vote_success_works() { let poll_index = 3; let vtoken = VKSM; let mut query_id = 0; - let derivative_index = 5; + let derivative_index = 0; let response = response_success(); assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() ); assert_eq!(tally(vtoken, poll_index), Tally::from_parts(10, 0, 2)); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { who: ALICE, vtoken, poll_index, - new_vote: aye(2, 5), - delegator_vote: aye(2, 5), + token_vote: aye(2, 5), + delegator_vote: aye(100, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); + assert_eq!( + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() + ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_ok!(VtokenVoting::set_referendum_status( RuntimeOrigin::root(), @@ -894,19 +812,16 @@ fn notify_remove_delegator_vote_success_works() { poll_index, ReferendumInfoOf::::Completed(3), )); - assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10,)); + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); - RelaychainDataProvider::set_block_number(15); + RelaychainDataProvider::set_block_number(3); assert_ok!(VtokenVoting::remove_delegator_vote( RuntimeOrigin::signed(ALICE), vtoken, poll_index, derivative_index, )); - assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) - ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); query_id = 1; assert_ok!(VtokenVoting::notify_remove_delegator_vote( @@ -914,10 +829,7 @@ fn notify_remove_delegator_vote_success_works() { query_id, response.clone() )); - assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(0, 5)) - ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); System::assert_has_event(RuntimeEvent::VtokenVoting(Event::DelegatorVoteRemovedNotified { vtoken, poll_index, @@ -937,19 +849,37 @@ fn notify_remove_delegator_vote_fail_works() { let poll_index = 3; let vtoken = VKSM; let mut query_id = 0; - let derivative_index = 5; + let derivative_index = 0; let response = response_fail(); assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!( + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() + ); assert_eq!(tally(vtoken, poll_index), Tally::from_parts(10, 0, 2)); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { who: ALICE, vtoken, poll_index, - new_vote: aye(2, 5), - delegator_vote: aye(2, 5), + token_vote: aye(2, 5), + delegator_vote: aye(100, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response_success())); + assert_eq!( + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( + derivative_index, + aye(100, 0) + )]) + .unwrap() + ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); assert_ok!(VtokenVoting::set_referendum_status( RuntimeOrigin::root(), @@ -957,19 +887,16 @@ fn notify_remove_delegator_vote_fail_works() { poll_index, ReferendumInfoOf::::Completed(3), )); - assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10,)); + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); - RelaychainDataProvider::set_block_number(15); + RelaychainDataProvider::set_block_number(3); assert_ok!(VtokenVoting::remove_delegator_vote( RuntimeOrigin::signed(ALICE), vtoken, poll_index, derivative_index, )); - assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) - ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); query_id = 1; assert_ok!(VtokenVoting::notify_remove_delegator_vote( @@ -977,10 +904,7 @@ fn notify_remove_delegator_vote_fail_works() { query_id, response.clone() )); - assert_eq!( - DelegatorVote::::get((vtoken, poll_index, derivative_index)), - Some(aye(2, 5)) - ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { responder: Parent.into(), query_id, @@ -1053,3 +977,105 @@ fn on_idle_works() { assert_eq!(actual_count, count); }); } + +#[test] +fn set_vote_cap_ratio_works() { + new_test_ext().execute_with(|| { + let vtoken = VKSM; + + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(0) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(0)); + + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(10) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(10)); + + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(100) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(100)); + }); +} + +#[test] +fn vote_cap_works() { + new_test_ext().execute_with(|| { + let vtoken = VKSM; + assert_eq!(VtokenVoting::vote_cap(vtoken), Ok((u64::MAX / 10) as Balance)); + }); +} + +#[test] +fn vote_to_capital_works() { + new_test_ext().execute_with(|| { + assert_eq!(VtokenVoting::vote_to_capital(Conviction::None, 300), 3000); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked1x, 300), 300); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked2x, 300), 150); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked3x, 300), 100); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked4x, 300), 75); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked5x, 300), 60); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked6x, 300), 50); + }); +} + +#[test] +fn compute_delegator_total_vote_works() { + new_test_ext().execute_with(|| { + let vtoken = VKSM; + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(10, 0)), Ok(aye(10, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 1)), Ok(aye(20, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 2)), Ok(aye(40, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 3)), Ok(aye(60, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 4)), Ok(aye(80, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 5)), Ok(aye(100, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 6)), Ok(aye(120, 0))); + + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(10, 0)), Ok(nay(10, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 1)), Ok(nay(20, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 2)), Ok(nay(40, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 3)), Ok(nay(60, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 4)), Ok(nay(80, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 5)), Ok(nay(100, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 6)), Ok(nay(120, 0))); + }); +} + +#[test] +fn compute_delegator_total_vote_with_low_value_will_loss() { + new_test_ext().execute_with(|| { + let vtoken = VKSM; + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(9, 0)), Ok(aye(0, 0))); + assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(9, 0)), Ok(nay(0, 0))); + }); +} + +#[test] +fn allocate_delegator_votes_works() { + new_test_ext().execute_with(|| { + let vtoken = VKSM; + let poll_index = 3; + let vote = aye(5e9 as Balance, 2); + + let delegator_votes = VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); + assert_eq!(delegator_votes, Ok(vec![(0, aye(4294967295, 2)), (1, aye(705032705, 2))])); + assert_eq!( + delegator_votes.unwrap().into_iter().map(|(_derivative_index, vote)| vote).fold( + aye(0, 2), + |mut acc, vote| { + let _ = acc.checked_add(vote); + acc + }, + ), + vote + ); + }); +} diff --git a/pallets/vtoken-voting/src/vote.rs b/pallets/vtoken-voting/src/vote.rs index 516ec94fc..6699f2eda 100644 --- a/pallets/vtoken-voting/src/vote.rs +++ b/pallets/vtoken-voting/src/vote.rs @@ -152,6 +152,17 @@ pub enum AccountVote { } impl AccountVote { + pub fn new_standard(vote: Vote, balance: Balance) -> Self { + AccountVote::Standard { vote, balance } + } + + pub fn as_standard_vote(&self) -> Option { + match self { + AccountVote::Standard { vote, .. } => Some(*vote), + _ => None, + } + } + /// Returns `Some` of the lock periods that the account is locked for, assuming that the /// referendum passed iff `approved` is `true`. pub fn locked_if(self, _approved: bool) -> Option<(u32, Balance)> { @@ -478,6 +489,20 @@ impl< } } + pub fn account_vote(&self, conviction: Conviction) -> AccountVote { + if self.ayes >= self.nays { + AccountVote::Standard { + vote: Vote { aye: true, conviction }, + balance: self.ayes - self.nays, + } + } else { + AccountVote::Standard { + vote: Vote { aye: false, conviction }, + balance: self.nays - self.ayes, + } + } + } + /// Add an account's vote into the tally. pub fn add(&mut self, vote: AccountVote) -> Option<()> { match vote { diff --git a/pallets/vtoken-voting/src/weights.rs b/pallets/vtoken-voting/src/weights.rs index 4dd34b6e6..8005536f1 100644 --- a/pallets/vtoken-voting/src/weights.rs +++ b/pallets/vtoken-voting/src/weights.rs @@ -58,12 +58,13 @@ pub trait WeightInfo { fn unlock() -> Weight; fn remove_delegator_vote() -> Weight; fn kill_referendum() -> Weight; - fn set_delegator_role() -> Weight; + fn add_delegator() -> Weight; fn set_referendum_status() -> Weight; fn set_undeciding_timeout() -> Weight; fn set_vote_locking_period() -> Weight; fn notify_vote() -> Weight; fn notify_remove_delegator_vote() -> Weight; + fn set_vote_cap_ratio() -> Weight; } // For backwards compatibility and tests @@ -217,7 +218,7 @@ impl WeightInfo for () { /// Proof Skipped: Slp DelegatorsIndex2Multilocation (max_values: None, max_size: None, mode: Measured) /// Storage: VtokenVoting DelegatorVote (r:1 w:1) /// Proof: VtokenVoting DelegatorVote (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) - fn set_delegator_role() -> Weight { + fn add_delegator() -> Weight { // Proof Size summary in bytes: // Measured: `372` // Estimated: `3837` @@ -279,4 +280,13 @@ impl WeightInfo for () { Weight::from_parts(39_364_000, 3501) .saturating_add(RocksDbWeight::get().reads(1_u64)) } + + fn set_vote_cap_ratio() -> Weight { + // Proof Size summary in bytes: + // Measured: `329` + // Estimated: `3501` + // Minimum execution time: 38_747_000 picoseconds. + Weight::from_parts(39_364_000, 3501) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } } diff --git a/runtime/bifrost-kusama/src/governance/fellowship.rs b/runtime/bifrost-kusama/src/governance/fellowship.rs index deafe647e..b88fda75e 100644 --- a/runtime/bifrost-kusama/src/governance/fellowship.rs +++ b/runtime/bifrost-kusama/src/governance/fellowship.rs @@ -1,18 +1,20 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Bifrost. -// Polkadot is free software: you can redistribute it and/or modify +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with this program. If not, see . //! Elements of governance concerning the Polkadot Fellowship. This is only a temporary arrangement //! since the Polkadot Fellowship belongs under the Polkadot Relay. However, that is not yet in diff --git a/runtime/bifrost-kusama/src/weights/bifrost_vtoken_voting.rs b/runtime/bifrost-kusama/src/weights/bifrost_vtoken_voting.rs index bb84d9973..361ddb923 100644 --- a/runtime/bifrost-kusama/src/weights/bifrost_vtoken_voting.rs +++ b/runtime/bifrost-kusama/src/weights/bifrost_vtoken_voting.rs @@ -203,7 +203,7 @@ impl bifrost_vtoken_voting::WeightInfo for BifrostWeigh // Proof Skipped: Slp DelegatorsIndex2Multilocation (max_values: None, max_size: None, mode: Measured) // Storage: VtokenVoting DelegatorVote (r:1 w:1) // Proof: VtokenVoting DelegatorVote (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) - fn set_delegator_role() -> Weight { + fn add_delegator() -> Weight { // Proof Size summary in bytes: // Measured: `372` // Estimated: `3837` @@ -265,4 +265,8 @@ impl bifrost_vtoken_voting::WeightInfo for BifrostWeigh Weight::from_parts(38_955_000, 3501) .saturating_add(T::DbWeight::get().reads(1)) } + fn set_vote_cap_ratio() -> Weight { + Weight::from_parts(38_955_000, 3501) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/runtime/bifrost-polkadot/src/governance/fellowship.rs b/runtime/bifrost-polkadot/src/governance/fellowship.rs index deafe647e..b88fda75e 100644 --- a/runtime/bifrost-polkadot/src/governance/fellowship.rs +++ b/runtime/bifrost-polkadot/src/governance/fellowship.rs @@ -1,18 +1,20 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Bifrost. -// Polkadot is free software: you can redistribute it and/or modify +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with this program. If not, see . //! Elements of governance concerning the Polkadot Fellowship. This is only a temporary arrangement //! since the Polkadot Fellowship belongs under the Polkadot Relay. However, that is not yet in diff --git a/runtime/bifrost-polkadot/src/weights/bifrost_vtoken_voting.rs b/runtime/bifrost-polkadot/src/weights/bifrost_vtoken_voting.rs index bb84d9973..361ddb923 100644 --- a/runtime/bifrost-polkadot/src/weights/bifrost_vtoken_voting.rs +++ b/runtime/bifrost-polkadot/src/weights/bifrost_vtoken_voting.rs @@ -203,7 +203,7 @@ impl bifrost_vtoken_voting::WeightInfo for BifrostWeigh // Proof Skipped: Slp DelegatorsIndex2Multilocation (max_values: None, max_size: None, mode: Measured) // Storage: VtokenVoting DelegatorVote (r:1 w:1) // Proof: VtokenVoting DelegatorVote (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) - fn set_delegator_role() -> Weight { + fn add_delegator() -> Weight { // Proof Size summary in bytes: // Measured: `372` // Estimated: `3837` @@ -265,4 +265,8 @@ impl bifrost_vtoken_voting::WeightInfo for BifrostWeigh Weight::from_parts(38_955_000, 3501) .saturating_add(T::DbWeight::get().reads(1)) } + fn set_vote_cap_ratio() -> Weight { + Weight::from_parts(38_955_000, 3501) + .saturating_add(T::DbWeight::get().reads(1)) + } }