diff --git a/integration-tests/bifrost-kusama/src/vtoken_voting.rs b/integration-tests/bifrost-kusama/src/vtoken_voting.rs index f9f38865b..28e64c639 100644 --- a/integration-tests/bifrost-kusama/src/vtoken_voting.rs +++ b/integration-tests/bifrost-kusama/src/vtoken_voting.rs @@ -59,15 +59,28 @@ fn vote_works() { }); Bifrost::execute_with(|| { - use bifrost_kusama_runtime::{RuntimeEvent, RuntimeOrigin, System, VtokenVoting}; + use bifrost_kusama_runtime::{ + RuntimeEvent, RuntimeOrigin, System, VtokenMinting, VtokenVoting, + }; - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - MultiAddress::Id(ALICE.into()), - VKSM, - u64::MAX.into(), - Zero::zero(), + assert_ok!(VtokenMinting::mint( + RuntimeOrigin::signed(ALICE.into()), + KSM, + 1_000_000_000_000, + Default::default() )); + assert_eq!( + ::VTokenSupplyProvider::get_token_supply( + KSM + ), + Some(1_000_000_000_000) + ); + assert_eq!( + ::VTokenSupplyProvider::get_vtoken_supply( + VKSM + ), + Some(1_000_000_000_000) + ); let token = CurrencyId::to_token(&vtoken).unwrap(); assert_ok!(XcmInterface::set_xcm_dest_weight_and_fee( token, @@ -137,7 +150,8 @@ fn vote_works() { who: _, vtoken: VKSM, poll_index: 0, - vote: _, + new_vote: _, + delegator_vote: _, }) ))); System::reset_events(); diff --git a/node/primitives/src/traits.rs b/node/primitives/src/traits.rs index 08d5b04c0..a900a866c 100644 --- a/node/primitives/src/traits.rs +++ b/node/primitives/src/traits.rs @@ -486,3 +486,9 @@ pub trait DerivativeAccountHandler { #[cfg(feature = "runtime-benchmarks")] fn add_delegator(token: CurrencyId, index: DerivativeIndex, who: xcm::v3::MultiLocation); } + +pub trait VTokenSupplyProvider { + fn get_vtoken_supply(vtoken: CurrencyId) -> Option; + + fn get_token_supply(token: CurrencyId) -> Option; +} diff --git a/pallets/flexible-fee/src/mock.rs b/pallets/flexible-fee/src/mock.rs index 3dec5be1e..2c42a6fc0 100644 --- a/pallets/flexible-fee/src/mock.rs +++ b/pallets/flexible-fee/src/mock.rs @@ -36,7 +36,7 @@ use frame_system as system; use frame_system::{EnsureRoot, EnsureSignedBy}; use node_primitives::{ Balance, CurrencyId, DerivativeAccountHandler, DerivativeIndex, ExtraFeeInfo, MessageId, - ParaId, TokenSymbol, VKSM, + ParaId, TokenSymbol, VTokenSupplyProvider, VKSM, }; use orml_traits::MultiCurrency; use pallet_xcm::EnsureResponse; @@ -501,6 +501,18 @@ impl pallet_xcm::Config for Test { //************** Salp mock end ***************** // ************** VtokenVoting mock start ***************** +pub struct SimpleVTokenSupplyProvider; + +impl VTokenSupplyProvider for SimpleVTokenSupplyProvider { + fn get_vtoken_supply(_: CurrencyId) -> Option { + Some(u64::MAX.into()) + } + + fn get_token_supply(_: CurrencyId) -> Option { + Some(u64::MAX.into()) + } +} + impl bifrost_vtoken_voting::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; @@ -511,6 +523,7 @@ impl bifrost_vtoken_voting::Config for Test { type XcmDestWeightAndFee = XcmDestWeightAndFee; type DerivativeAccount = DerivativeAccount; type RelaychainBlockNumberProvider = RelaychainDataProvider; + type VTokenSupplyProvider = SimpleVTokenSupplyProvider; type MaxVotes = ConstU32<256>; type ParachainId = ParaInfo; type QueryTimeout = QueryTimeout; diff --git a/pallets/vtoken-minting/src/lib.rs b/pallets/vtoken-minting/src/lib.rs index 7b8d8fa06..4de8e9b44 100644 --- a/pallets/vtoken-minting/src/lib.rs +++ b/pallets/vtoken-minting/src/lib.rs @@ -43,8 +43,8 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use node_primitives::{ - CurrencyId, CurrencyIdConversion, CurrencyIdRegister, RedeemType, SlpOperator, SlpxOperator, - TimeUnit, VtokenMintingInterface, VtokenMintingOperator, + CurrencyId, CurrencyIdConversion, CurrencyIdExt, CurrencyIdRegister, RedeemType, SlpOperator, + SlpxOperator, TimeUnit, VTokenSupplyProvider, VtokenMintingInterface, VtokenMintingOperator, }; use orml_traits::MultiCurrency; pub use pallet::*; @@ -1648,3 +1648,21 @@ impl VtokenMintingInterface, CurrencyIdOf, BalanceO T::HydradxParachainId::get() } } + +impl VTokenSupplyProvider, BalanceOf> for Pallet { + fn get_vtoken_supply(vtoken: CurrencyIdOf) -> Option> { + if CurrencyId::is_vtoken(&vtoken) { + Some(T::MultiCurrency::total_issuance(vtoken)) + } else { + None + } + } + + fn get_token_supply(token: CurrencyIdOf) -> Option> { + if CurrencyId::is_token(&token) { + Some(Self::token_pool(token)) + } else { + None + } + } +} diff --git a/pallets/vtoken-voting/src/lib.rs b/pallets/vtoken-voting/src/lib.rs index aaae91831..ce045008d 100644 --- a/pallets/vtoken-voting/src/lib.rs +++ b/pallets/vtoken-voting/src/lib.rs @@ -45,7 +45,7 @@ use frame_support::{ use frame_system::pallet_prelude::{BlockNumberFor, *}; use node_primitives::{ currency::{VDOT, VKSM}, - traits::{DerivativeAccountHandler, XcmDestWeightAndFeeHandler}, + traits::{DerivativeAccountHandler, VTokenSupplyProvider, XcmDestWeightAndFeeHandler}, CurrencyId, DerivativeIndex, XcmOperationType, }; use orml_traits::{MultiCurrency, MultiLockableCurrency}; @@ -110,6 +110,8 @@ pub mod pallet { type RelaychainBlockNumberProvider: BlockNumberProvider>; + type VTokenSupplyProvider: VTokenSupplyProvider, BalanceOf>; + #[pallet::constant] type ParachainId: Get; @@ -131,7 +133,8 @@ pub mod pallet { who: AccountIdOf, vtoken: CurrencyIdOf, poll_index: PollIndex, - vote: AccountVote>, + new_vote: AccountVote>, + delegator_vote: AccountVote>, }, Unlocked { who: AccountIdOf, @@ -270,7 +273,7 @@ pub mod pallet { PollIndex, DerivativeIndex, AccountIdOf, - Option>>, + Option<(AccountVote>, BalanceOf)>, ), >; @@ -396,8 +399,10 @@ pub mod pallet { let who = ensure_signed(origin)?; Self::ensure_vtoken(&vtoken)?; ensure!(UndecidingTimeout::::contains_key(vtoken), Error::::NoData); - let derivative_index = Self::try_select_derivative_index(vtoken, vote)?; 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, new_vote)?; if let Some(d) = VoteDelegatorFor::::get((&who, vtoken, poll_index)) { ensure!(d == derivative_index, Error::::ChangeDelegator) } @@ -419,7 +424,14 @@ pub mod pallet { } // record vote info - let maybe_old_vote = Self::try_vote(&who, vtoken, poll_index, derivative_index, vote)?; + let maybe_old_vote = Self::try_vote( + &who, + vtoken, + poll_index, + derivative_index, + new_vote, + vote.balance(), + )?; // send XCM message let delegator_vote = @@ -449,7 +461,13 @@ pub mod pallet { }, )?; - Self::deposit_event(Event::::Voted { who, vtoken, poll_index, vote }); + Self::deposit_event(Event::::Voted { + who, + vtoken, + poll_index, + new_vote, + delegator_vote, + }); Ok(()) } @@ -644,8 +662,15 @@ pub mod pallet { // rollback vote Self::try_remove_vote(&who, vtoken, poll_index, UnvoteScope::Any)?; Self::update_lock(&who, vtoken, &poll_index)?; - if let Some(old_vote) = maybe_old_vote { - Self::try_vote(&who, vtoken, poll_index, derivative_index, old_vote)?; + if let Some((old_vote, vtoken_balance)) = maybe_old_vote { + Self::try_vote( + &who, + vtoken, + poll_index, + derivative_index, + old_vote, + vtoken_balance, + )?; } } else { if !VoteDelegatorFor::::contains_key((&who, vtoken, poll_index)) { @@ -747,7 +772,8 @@ pub mod pallet { poll_index: PollIndex, derivative_index: DerivativeIndex, vote: AccountVote>, - ) -> Result>>, DispatchError> { + vtoken_balance: BalanceOf, + ) -> Result>, BalanceOf)>, DispatchError> { ensure!( vote.balance() <= T::MultiCurrency::total_balance(vtoken, who), Error::::InsufficientFunds @@ -762,16 +788,20 @@ pub mod pallet { // 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, votes[i].2, votes[i].1)?; - old_vote = Some(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].3 = vtoken_balance; }, Err(i) => { votes - .try_insert(i, (poll_index, vote, derivative_index)) + .try_insert( + i, + (poll_index, vote, derivative_index, vtoken_balance), + ) .map_err(|_| Error::::MaxVotesReached)?; }, } @@ -786,7 +816,7 @@ pub mod pallet { } // 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, vote.balance())?; + Self::extend_lock(&who, vtoken, &poll_index, vtoken_balance)?; Ok(old_vote) }) }) @@ -1128,5 +1158,22 @@ pub mod pallet { } }) } + + fn compute_new_vote( + vtoken: CurrencyIdOf, + 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))?; + + Ok(new_vote) + } } } diff --git a/pallets/vtoken-voting/src/mock.rs b/pallets/vtoken-voting/src/mock.rs index a1ea540fa..cf527979e 100644 --- a/pallets/vtoken-voting/src/mock.rs +++ b/pallets/vtoken-voting/src/mock.rs @@ -31,7 +31,7 @@ use frame_system::EnsureRoot; use node_primitives::{ currency::{KSM, VBNC, VKSM}, traits::XcmDestWeightAndFeeHandler, - CurrencyId, DoNothingRouter, TokenSymbol, XcmOperationType, + CurrencyId, DoNothingRouter, TokenSymbol, VTokenSupplyProvider, XcmOperationType, }; use pallet_xcm::EnsureResponse; use sp_core::H256; @@ -308,6 +308,18 @@ impl BlockNumberProvider for RelaychainDataProvider { } } +pub struct SimpleVTokenSupplyProvider; + +impl VTokenSupplyProvider for SimpleVTokenSupplyProvider { + fn get_vtoken_supply(_: CurrencyId) -> Option { + Some(u64::MAX.into()) + } + + fn get_token_supply(_: CurrencyId) -> Option { + Some(u64::MAX.into()) + } +} + impl vtoken_voting::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; @@ -318,6 +330,7 @@ impl vtoken_voting::Config for Runtime { type XcmDestWeightAndFee = XcmDestWeightAndFee; type DerivativeAccount = DerivativeAccount; type RelaychainBlockNumberProvider = RelaychainDataProvider; + type VTokenSupplyProvider = SimpleVTokenSupplyProvider; type MaxVotes = ConstU32<256>; type ParachainId = ParachainId; type QueryTimeout = QueryTimeout; diff --git a/pallets/vtoken-voting/src/tests.rs b/pallets/vtoken-voting/src/tests.rs index 47116a9d9..e3690e48a 100644 --- a/pallets/vtoken-voting/src/tests.rs +++ b/pallets/vtoken-voting/src/tests.rs @@ -83,7 +83,8 @@ fn basic_voting_works() { who: ALICE, vtoken, poll_index, - vote: aye(2, 5), + new_vote: aye(2, 5), + delegator_vote: aye(2, 5), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); @@ -112,7 +113,8 @@ fn split_voting_works() { who: ALICE, vtoken, poll_index, - vote: split(10, 0), + new_vote: split(10, 0), + delegator_vote: split(10, 0), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); assert_ok!(VtokenVoting::vote( @@ -149,7 +151,8 @@ fn abstain_voting_works() { who: ALICE, vtoken, poll_index, - vote: split_abstain(0, 0, 10), + 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())); @@ -163,7 +166,8 @@ fn abstain_voting_works() { who: BOB, vtoken, poll_index, - vote: split_abstain(0, 0, 20), + 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())); @@ -596,7 +600,8 @@ fn notify_vote_success_works() { who: ALICE, vtoken, poll_index, - vote: aye(2, 5), + new_vote: aye(2, 5), + delegator_vote: aye(2, 5), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); @@ -707,7 +712,8 @@ fn notify_vote_fail_works() { who: ALICE, vtoken, poll_index, - vote: aye(2, 5), + new_vote: aye(2, 5), + delegator_vote: aye(2, 5), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); @@ -757,7 +763,8 @@ fn notify_remove_delegator_vote_success_works() { who: ALICE, vtoken, poll_index, - vote: aye(2, 5), + new_vote: aye(2, 5), + delegator_vote: aye(2, 5), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); @@ -813,7 +820,8 @@ fn notify_remove_delegator_vote_fail_works() { who: ALICE, vtoken, poll_index, - vote: aye(2, 5), + new_vote: aye(2, 5), + delegator_vote: aye(2, 5), })); assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response_success())); diff --git a/pallets/vtoken-voting/src/vote.rs b/pallets/vtoken-voting/src/vote.rs index 0d1854c93..07ef6f504 100644 --- a/pallets/vtoken-voting/src/vote.rs +++ b/pallets/vtoken-voting/src/vote.rs @@ -24,8 +24,8 @@ use node_primitives::DerivativeIndex; use pallet_conviction_voting::{Conviction, Delegations, Vote}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, One, Zero}, - Saturating, + traits::{AtLeast32BitUnsigned, EnsureDivAssign, EnsureMulAssign, One, Zero}, + ArithmeticError, Saturating, }; use sp_std::{fmt::Debug, prelude::*}; @@ -233,6 +233,44 @@ impl AccountVote { } Ok(()) } + + pub fn checked_mul(&mut self, balance: Balance) -> Result<(), ArithmeticError> + where + Balance: Copy + EnsureMulAssign, + { + match self { + AccountVote::Standard { vote: _, balance: b1 } => b1.ensure_mul_assign(balance)?, + AccountVote::Split { aye: a1, nay: n1 } => { + a1.ensure_mul_assign(balance)?; + n1.ensure_mul_assign(balance)?; + }, + AccountVote::SplitAbstain { aye: a1, nay: n1, abstain: ab1 } => { + a1.ensure_mul_assign(balance)?; + n1.ensure_mul_assign(balance)?; + ab1.ensure_mul_assign(balance)?; + }, + } + Ok(()) + } + + pub fn checked_div(&mut self, balance: Balance) -> Result<(), ArithmeticError> + where + Balance: Copy + EnsureDivAssign, + { + match self { + AccountVote::Standard { vote: _, balance: b1 } => b1.ensure_div_assign(balance)?, + AccountVote::Split { aye: a1, nay: n1 } => { + a1.ensure_div_assign(balance)?; + n1.ensure_div_assign(balance)?; + }, + AccountVote::SplitAbstain { aye: a1, nay: n1, abstain: ab1 } => { + a1.ensure_div_assign(balance)?; + n1.ensure_div_assign(balance)?; + ab1.ensure_div_assign(balance)?; + }, + } + Ok(()) + } } /// A "prior" lock, i.e. a lock for some now-forgotten reason. @@ -296,7 +334,7 @@ where MaxVotes: Get, { /// The current votes of the account. - pub votes: BoundedVec<(PollIndex, AccountVote, DerivativeIndex), MaxVotes>, + pub votes: BoundedVec<(PollIndex, AccountVote, DerivativeIndex, Balance), MaxVotes>, /// The total amount of delegations that this account has received, post-conviction-weighting. pub delegations: Delegations, /// Any pre-existing locks from past voting/delegating activity. diff --git a/runtime/bifrost-kusama/src/lib.rs b/runtime/bifrost-kusama/src/lib.rs index 01b341f47..fc781312e 100644 --- a/runtime/bifrost-kusama/src/lib.rs +++ b/runtime/bifrost-kusama/src/lib.rs @@ -1498,6 +1498,7 @@ impl bifrost_vtoken_voting::Config for Runtime { type XcmDestWeightAndFee = XcmInterface; type DerivativeAccount = DerivativeAccountProvider; type RelaychainBlockNumberProvider = RelaychainDataProvider; + type VTokenSupplyProvider = VtokenMinting; type ParachainId = SelfParaChainId; type MaxVotes = ConstU32<256>; type QueryTimeout = QueryTimeout; diff --git a/runtime/bifrost-polkadot/src/lib.rs b/runtime/bifrost-polkadot/src/lib.rs index 29c922a41..4a0e39371 100644 --- a/runtime/bifrost-polkadot/src/lib.rs +++ b/runtime/bifrost-polkadot/src/lib.rs @@ -1383,6 +1383,7 @@ impl bifrost_vtoken_voting::Config for Runtime { type XcmDestWeightAndFee = XcmInterface; type DerivativeAccount = DerivativeAccountProvider; type RelaychainBlockNumberProvider = RelaychainDataProvider; + type VTokenSupplyProvider = VtokenMinting; type ParachainId = SelfParaChainId; type MaxVotes = ConstU32<256>; type QueryTimeout = QueryTimeout;