diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d6e6a6..729244a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ +# v0.9.30-mainnet.161 + +- migrate trading rewards to marketing rewards +- distribute transaction fees to broker +- change the caller of `register_broker` to broker beneficiary + # v0.9.30-mainnet.159 - enable polygon chain bridge -- fix node rpc compile error +- switch the compiler toolchain to stable # v0.9.30-node.158 @@ -9,7 +15,7 @@ # v0.9.30-mainnet.158 -- fix bug about issue token +- fix bug of issuing token # v0.9.30-node.157 diff --git a/pallets/chainbridge-handler/src/mock.rs b/pallets/chainbridge-handler/src/mock.rs index b92c268..c20e50b 100644 --- a/pallets/chainbridge-handler/src/mock.rs +++ b/pallets/chainbridge-handler/src/mock.rs @@ -159,7 +159,7 @@ parameter_types! { pub struct PhantomData; -impl fuso_support::traits::Rewarding for PhantomData { +impl fuso_support::traits::Rewarding for PhantomData { type Balance = Balance; fn era_duration() -> BlockNumber { @@ -174,13 +174,30 @@ impl fuso_support::traits::Rewarding for Phanto 0 } - fn save_trading( - _trader: &AccountId, - _amount: Balance, - _at: BlockNumber, + fn put_liquidity(_maker: &AccountId, _symbol: (u32, u32), _vol: Balance, _at: BlockNumber) {} + + /// when liquidity is took out, the liquidity provider will get the reward. + /// the rewards are calculated in the formula below: + /// contribution ƒi = vol * min(current - from, era_duration) / 720 + /// rewards of contribution ∂ = ƒi / ∑ƒi * era_rewards + /// NOTE: `vol` should be volume rather than amount + fn consume_liquidity( + _maker: &AccountId, + _symbol: (u32, u32), + _vol: Balance, + _current: BlockNumber, ) -> frame_support::pallet_prelude::DispatchResult { Ok(()) } + + /// remove liquidity + fn remove_liquidity( + _maker: &AccountId, + _symbol: (u32, u32), + _vol: Balance, + ) -> Result { + Ok(1) + } } parameter_types! { @@ -194,6 +211,7 @@ parameter_types! { impl pallet_fuso_verifier::Config for Test { type Asset = Assets; + type BrokerBeneficiary = (); type Callback = RuntimeCall; type DominatorCheckGracePeriod = DominatorCheckGracePeriod; type DominatorOnlineThreshold = DominatorOnlineThreshold; diff --git a/pallets/fuso-support/src/traits.rs b/pallets/fuso-support/src/traits.rs index 49e86c4..1a1d8ad 100644 --- a/pallets/fuso-support/src/traits.rs +++ b/pallets/fuso-support/src/traits.rs @@ -184,7 +184,7 @@ pub trait NamedReservableToken: Token { ) -> sp_std::result::Result; } -pub trait Rewarding { +pub trait Rewarding { /// $TAO type Balance: Member + Parameter @@ -200,7 +200,28 @@ pub trait Rewarding { fn acked_reward(who: &AccountId) -> Self::Balance; - fn save_trading(trader: &AccountId, amount: Volume, at: BlockNumber) -> DispatchResult; + /// put liquidity `vol` into `symbol`(override the previous value) `at` block number. + /// NOTE: if the `maker` has already added liquidity at the same `symbol`, then the block number will be updated to `at`. + fn put_liquidity(maker: &AccountId, symbol: Symbol, vol: Volume, at: BlockNumber); + + /// when liquidity is took out, the liquidity provider will get the reward. + /// the rewards are calculated in the formula below: + /// contribution ƒi = vol * min(current - from, era_duration) / 720 + /// rewards of contribution ∂ = ƒi / ∑ƒi * era_rewards + /// NOTE: `vol` should be volume rather than amount + fn consume_liquidity( + maker: &AccountId, + symbol: Symbol, + vol: Volume, + current: BlockNumber, + ) -> DispatchResult; + + /// remove liquidity + fn remove_liquidity( + maker: &AccountId, + symbol: Symbol, + vol: Volume, + ) -> Result; } pub trait Agent { diff --git a/pallets/indicator/src/lib.rs b/pallets/indicator/src/lib.rs index c0b7f56..863e3d8 100644 --- a/pallets/indicator/src/lib.rs +++ b/pallets/indicator/src/lib.rs @@ -39,7 +39,6 @@ pub mod pallet { } #[pallet::event] - #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { PriceUpdated(TokenId, Balance), } @@ -103,7 +102,6 @@ pub mod pallet { + (Perquintill::from_rational(volume % amount, amount).deconstruct() as u128) .into(); - Self::deposit_event(Event::PriceUpdated(token_id, p.price)); } }); } diff --git a/pallets/market/src/lib.rs b/pallets/market/src/lib.rs index 38d7053..7d2efa9 100644 --- a/pallets/market/src/lib.rs +++ b/pallets/market/src/lib.rs @@ -134,15 +134,15 @@ pub mod pallet { #[pallet::weight(8_790_000_000)] pub fn register_broker( origin: OriginFor, - beneficiary: T::AccountId, + broker: T::AccountId, rpc_endpoint: Vec, name: Vec, ) -> DispatchResultWithPostInfo { - let broker = ensure_signed(origin)?; + let beneficiary = ensure_signed(origin)?; ensure!(name.len() <= 20, Error::::BrokerNameTooLong); let requires = T::BrokerStakingThreshold::get(); T::Assets::transfer_token( - &broker, + &beneficiary, T::Assets::native_token_id(), requires, &Self::system_account(), @@ -163,18 +163,45 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(8_790_000_000)] + pub fn deregister_broker( + origin: OriginFor, + broker: T::AccountId, + ) -> DispatchResultWithPostInfo { + let beneficiary = ensure_signed(origin)?; + Brokers::::try_mutate_exists(&broker, |b| -> DispatchResult { + ensure!(b.is_some(), Error::::BrokerNotFound); + let broker = b.take().unwrap(); + ensure!( + broker.beneficiary == beneficiary, + Error::::BrokerNotFound + ); + T::Assets::transfer_token( + &Self::system_account(), + T::Assets::native_token_id(), + broker.staked, + &broker.beneficiary, + )?; + Ok(()) + })?; + Self::deposit_event(Event::BrokerDeregistered(broker)); + Ok(().into()) + } + #[pallet::weight(8_790_000_000)] pub fn broker_set_rpc_endpoint( origin: OriginFor, + broker: T::AccountId, rpc_endpoint: Vec, ) -> DispatchResultWithPostInfo { - let broker = ensure_signed(origin)?; + let admin = ensure_signed(origin)?; Brokers::::try_mutate_exists(&broker, |b| -> DispatchResult { - if let Some(broker) = b { - broker.rpc_endpoint = rpc_endpoint; - Ok(()) - } else { - Err(Error::::BrokerNotFound.into()) + match b { + Some(broker) if broker.beneficiary == admin => { + broker.rpc_endpoint = rpc_endpoint; + Ok(()) + } + _ => Err(Error::::BrokerNotFound.into()), } })?; Ok(().into()) diff --git a/pallets/market/src/tests.rs b/pallets/market/src/tests.rs index 5f39a92..a2d179f 100644 --- a/pallets/market/src/tests.rs +++ b/pallets/market/src/tests.rs @@ -25,13 +25,14 @@ pub fn register_broker_should_work() { b"test-broker".to_vec(), )); assert_eq!(Balances::free_balance(&ferdie), 90000 * DOLLARS); - assert_eq!(Market::beneficiary(ferdie.clone()), charlie.clone().into()); + assert_eq!(Market::beneficiary(charlie.clone()), ferdie.clone().into()); assert_ok!(Market::broker_set_rpc_endpoint( RuntimeOrigin::signed(ferdie.clone()), + charlie.clone().into(), b"192.168.1.1".to_vec(), )); assert_eq!( - crate::Brokers::::get(ferdie).unwrap().rpc_endpoint, + crate::Brokers::::get(charlie).unwrap().rpc_endpoint, b"192.168.1.1" ); }); diff --git a/pallets/reward/src/lib.rs b/pallets/reward/src/lib.rs index 1bfd34c..ff5358c 100644 --- a/pallets/reward/src/lib.rs +++ b/pallets/reward/src/lib.rs @@ -29,7 +29,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use fuso_support::traits::{Rewarding, Token}; use sp_runtime::{ - traits::{CheckedAdd, Zero}, + traits::{CheckedAdd, CheckedSub, Zero}, DispatchError, DispatchResult, Perquintill, }; use sp_std::result::Result; @@ -40,6 +40,11 @@ pub mod pallet { pub type Balance = <::Asset as Token<::AccountId>>::Balance; + pub type TokenId = + <::Asset as Token<::AccountId>>::TokenId; + + pub type Symbol = (TokenId, TokenId); + pub type Era = ::BlockNumber; #[pallet::config] @@ -51,6 +56,9 @@ pub mod pallet { #[pallet::constant] type EraDuration: Get; + #[pallet::constant] + type TimeCoefficientZoom: Get; + // DEPRECATED #[pallet::constant] type RewardsPerEra: Get>; @@ -71,6 +79,8 @@ pub mod pallet { Overflow, DivideByZero, RewardNotFound, + InsufficientLiquidity, + LiquidityNotFound, } #[pallet::hooks] @@ -83,24 +93,65 @@ pub mod pallet { pub last_modify: Era, } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, Default)] + pub struct LiquidityProvider { + pub volume: Volume, + pub start_from: BlockNumber, + } + #[pallet::storage] - #[pallet::getter(fn rewards)] - pub type Rewards = StorageMap< + #[pallet::getter(fn liquidity_pool)] + pub type LiquidityPool = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, - Reward, Volume, Era>, - ValueQuery, + Blake2_128Concat, + Symbol, + LiquidityProvider, T::BlockNumber>, + OptionQuery, >; + /// constant #[pallet::storage] #[pallet::getter(fn era_rewards)] pub type EraRewards = StorageValue<_, Balance, ValueQuery, T::RewardsPerEra>; + /// deprecated, use `MarketingRewards` after v160 + #[pallet::storage] + #[pallet::getter(fn rewards)] + pub type Rewards = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + Reward, Volume, Era>, + ValueQuery, + >; + + /// deprecated, use `TradingVolumes` after v160 #[pallet::storage] #[pallet::getter(fn volumes)] pub type Volumes = StorageMap<_, Blake2_128Concat, Era, Volume, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn taken_liquidity)] + pub type TakenLiquidity = + StorageMap<_, Blake2_128Concat, Era, Volume, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn marketing_rewards)] + pub type MarketingRewards = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + Reward, Volume, Era>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn trading_volumes)] + pub type TradingVolumes = + StorageMap<_, Blake2_128Concat, Era, Volume, ValueQuery>; + #[pallet::pallet] #[pallet::without_storage_info] #[pallet::generate_store(pub (super) trait Store)] @@ -111,12 +162,17 @@ pub mod pallet { where Volume: Into, Balance: From, + T::BlockNumber: Into, { - #[pallet::weight(10000000)] + #[pallet::weight(100_000_000_000)] pub fn take_reward(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let at = frame_system::Pallet::::block_number(); + let legacy_reward = Self::claim_legacy_reward(&who, at)?; let reward = Self::claim_reward(&who, at)?; + let reward = reward + .checked_add(&legacy_reward) + .ok_or(Error::::Overflow)?; Self::deposit_event(Event::RewardClaimed(who, reward)); Ok(().into()) } @@ -137,14 +193,15 @@ pub mod pallet { where Volume: Into, Balance: From, + T::BlockNumber: Into, { #[transactional] - fn claim_reward( + fn claim_legacy_reward( who: &T::AccountId, at: T::BlockNumber, ) -> Result, DispatchError> { let at = at - at % Self::era_duration(); - let confirmed = Self::rotate_reward(at, Zero::zero(), &who)?; + let confirmed = Self::rotate_legacy_reward(at, Zero::zero(), &who)?; if confirmed == Zero::zero() { return Ok(Zero::zero()); } @@ -169,8 +226,27 @@ pub mod pallet { }) } + /// deprecated #[transactional] - fn rotate_reward( + pub(crate) fn save_trading( + trader: &T::AccountId, + vol: Volume, + at: T::BlockNumber, + ) -> DispatchResult { + if vol == Zero::zero() { + return Ok(()); + } + let at = at - at % Self::era_duration(); + Volumes::::try_mutate(&at, |v| -> DispatchResult { + Ok(*v = v.checked_add(&vol).ok_or(Error::::Overflow)?) + })?; + Self::rotate_legacy_reward(at, vol, trader)?; + Ok(()) + } + + /// legacy rewards rotation + #[transactional] + fn rotate_legacy_reward( at: T::BlockNumber, vol: Volume, account: &T::AccountId, @@ -208,12 +284,87 @@ pub mod pallet { } }) } + + #[transactional] + fn claim_reward( + who: &T::AccountId, + at: T::BlockNumber, + ) -> Result, DispatchError> { + let at = at - at % Self::era_duration(); + let confirmed = Self::rotate_reward(at, Zero::zero(), &who)?; + if confirmed == Zero::zero() { + return Ok(Zero::zero()); + } + MarketingRewards::::try_mutate_exists( + who, + |r| -> Result, DispatchError> { + ensure!(r.is_some(), Error::::RewardNotFound); + let mut reward: Reward, Volume, Era> = r.take().unwrap(); + let confirmed = reward.confirmed; + reward.confirmed = Zero::zero(); + if reward.pending_vol > Zero::zero() { + r.replace(reward); + } + if confirmed > Zero::zero() { + T::Asset::try_mutate_account(&T::Asset::native_token_id(), &who, |b| { + T::Asset::try_mutate_issuance(&T::Asset::native_token_id(), |v| { + *v = v.checked_add(&confirmed).ok_or(Error::::Overflow)?; + Ok(()) + })?; + Ok(b.0 += confirmed) + })?; + } + Ok(confirmed) + }, + ) + } + + #[transactional] + fn rotate_reward( + begin_of_era: T::BlockNumber, + contribution: Volume, + account: &T::AccountId, + ) -> Result, DispatchError> { + MarketingRewards::::try_mutate(account, |r| -> Result, DispatchError> { + if begin_of_era == r.last_modify { + r.pending_vol = r + .pending_vol + .checked_add(&contribution) + .ok_or(Error::::Overflow)?; + Ok(r.confirmed) + } else { + if r.pending_vol == Zero::zero() { + r.pending_vol = contribution; + r.last_modify = begin_of_era; + } else { + let pending_vol: u128 = r.pending_vol.into(); + let total_vol: u128 = TakenLiquidity::::get(r.last_modify).into(); + ensure!(total_vol > 0, Error::::DivideByZero); + let p: Perquintill = Perquintill::from_rational(pending_vol, total_vol); + let mut era_reward: u128 = EraRewards::::get().into(); + let now = frame_system::Pallet::::block_number(); + if now > T::RewardTerminateAt::get() { + era_reward = 0u128; + } + let a = p * era_reward; + r.confirmed = r + .confirmed + .checked_add(&a.into()) + .ok_or(Error::::Overflow)?; + r.pending_vol = contribution; + r.last_modify = begin_of_era; + } + Ok(r.confirmed) + } + }) + } } - impl Rewarding, T::BlockNumber> for Pallet + impl Rewarding, Symbol, T::BlockNumber> for Pallet where Volume: Into, Balance: From, + T::BlockNumber: Into, { type Balance = Balance; @@ -222,28 +373,94 @@ pub mod pallet { } fn total_volume(at: T::BlockNumber) -> Volume { - Self::volumes(at - at % Self::era_duration()) + Self::trading_volumes(at - at % Self::era_duration()) } fn acked_reward(who: &T::AccountId) -> Self::Balance { - Self::rewards(who).confirmed + Self::marketing_rewards(who).confirmed } - #[transactional] - fn save_trading( - trader: &T::AccountId, + /// put liquidity `vol` into `symbol`(override the previous value) `at` block number. + /// NOTE: if the `maker` has already added liquidity at the same `symbol`, then the block number will be updated to `at`. + fn put_liquidity( + maker: &T::AccountId, + symbol: Symbol, vol: Volume, at: T::BlockNumber, + ) { + if vol.is_zero() { + return; + } + LiquidityPool::::insert( + maker, + &symbol, + LiquidityProvider { + volume: vol, + start_from: at, + }, + ); + } + + /// when liquidity is took out, the liquidity provider will get the reward. + /// the rewards are calculated in the formula below: + /// contribution ƒi = vol * min(current - from, era_duration) / 720 + /// rewards of contribution ∂ = ƒi / ∑ƒi * era_rewards + /// NOTE: `vol` should be volume rather than amount + fn consume_liquidity( + maker: &T::AccountId, + symbol: Symbol, + vol: Volume, + current: T::BlockNumber, ) -> DispatchResult { - if vol == Zero::zero() { - return Ok(()); + // exclude v1 orders + if let Ok(start_from) = Self::remove_liquidity(maker, symbol, vol) { + let begin_of_era = current - current % Self::era_duration(); + let duration: u32 = (current - start_from).into(); + let era: u32 = Self::era_duration().into(); + let zoom = T::TimeCoefficientZoom::get(); + let coefficient = u128::min(duration.into(), era.into()) / zoom as u128; + let coefficient = u128::max(coefficient, 1); + let contribution = coefficient * vol.into(); + TakenLiquidity::::try_mutate(&begin_of_era, |v| -> DispatchResult { + Ok(*v = v + .checked_add(&contribution.into()) + .ok_or(Error::::Overflow)?) + })?; + TradingVolumes::::try_mutate(&begin_of_era, |v| -> DispatchResult { + Ok(*v = v.checked_add(&vol).ok_or(Error::::Overflow)?) + })?; + Self::rotate_reward(begin_of_era, contribution.into(), maker)?; } - let at = at - at % Self::era_duration(); - Volumes::::try_mutate(&at, |v| -> DispatchResult { - Ok(*v = v.checked_add(&vol).ok_or(Error::::Overflow)?) - })?; - Self::rotate_reward(at, vol, trader)?; Ok(()) } + + /// remove liquidity + fn remove_liquidity( + maker: &T::AccountId, + symbol: Symbol, + vol: Volume, + ) -> Result { + if vol.is_zero() { + return Ok(Zero::zero()); + } + LiquidityPool::::try_mutate_exists( + maker, + &symbol, + |l| -> Result { + let liquidity = l.take(); + ensure!(liquidity.is_some(), Error::::LiquidityNotFound); + let mut liquidity = liquidity.unwrap(); + liquidity.volume = liquidity + .volume + .checked_sub(&vol) + .ok_or(Error::::InsufficientLiquidity)?; + let start_from = liquidity.start_from; + if liquidity.volume > Zero::zero() { + l.replace(liquidity); + } + Ok(start_from) + }, + ) + } } } diff --git a/pallets/reward/src/mock.rs b/pallets/reward/src/mock.rs index 00bc74d..124b362 100644 --- a/pallets/reward/src/mock.rs +++ b/pallets/reward/src/mock.rs @@ -114,6 +114,7 @@ impl pallet_fuso_token::Config for Test { parameter_types! { pub const EraDuration: BlockNumber = 100; + pub const TimeCoefficientZoom: u32 = 5; pub const RewardsPerEra: Balance = 1000000000000000000000000; pub const RewardTerminateAt: BlockNumber = 500000000; } @@ -124,6 +125,7 @@ impl pallet_fuso_reward::Config for Test { type RewardTerminateAt = RewardTerminateAt; type RewardsPerEra = RewardsPerEra; type RuntimeEvent = RuntimeEvent; + type TimeCoefficientZoom = TimeCoefficientZoom; } // Configure a mock runtime to test the pallet. diff --git a/pallets/reward/src/tests.rs b/pallets/reward/src/tests.rs index 588665d..e702e19 100644 --- a/pallets/reward/src/tests.rs +++ b/pallets/reward/src/tests.rs @@ -1,4 +1,4 @@ -use frame_support::assert_ok; +use frame_support::{assert_noop, assert_ok}; use fuso_support::traits::Rewarding; use sp_keyring::AccountKeyring; @@ -6,9 +6,10 @@ use crate::mock::*; use crate::Rewards; use crate::*; type RewardModule = Pallet; +type Balances = pallet_balances::Pallet; #[test] -fn test_reward_should_work() { +fn test_legacy_reward_should_work() { new_test_ext().execute_with(|| { frame_system::Pallet::::set_block_number(100); let alice: AccountId = AccountKeyring::Alice.into(); @@ -60,3 +61,78 @@ fn test_reward_should_work() { assert_eq!(vol, 20000); }); } + +#[test] +fn test_marketing_reward_should_work() { + new_test_ext().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + let alice: AccountId = AccountKeyring::Alice.into(); + let ferdie: AccountId = AccountKeyring::Ferdie.into(); + // get 5... + assert_ok!(RewardModule::save_trading(&alice, 5000, 1)); + assert_ok!(RewardModule::save_trading(&ferdie, 5000, 1)); + + assert_eq!(0, Balances::free_balance(&alice)); + // upgrade happens here + frame_system::Pallet::::set_block_number(101); + // get 10... + assert_ok!(RewardModule::save_trading(&alice, 5000, 101)); + let alice_reward = RewardModule::rewards(&alice); + assert_eq!( + alice_reward, + Reward { + confirmed: 500000000000000000000000, + pending_vol: 5000, + last_modify: 100 + } + ); + // alice didn't trade after upgrade + // get 0 + RewardModule::consume_liquidity(&alice, (0, 1), 10, 101).unwrap(); + assert_eq!( + RewardModule::marketing_rewards(&alice), + Reward { + confirmed: 0, + pending_vol: 0, + last_modify: 0, + } + ); + // alice placed a new order + RewardModule::put_liquidity(&alice, (0, 1), 1000, 101); + // get 10... + RewardModule::consume_liquidity(&alice, (0, 1), 10, 101).unwrap(); + assert_eq!( + RewardModule::marketing_rewards(&alice), + Reward { + confirmed: 0, + pending_vol: 10, + last_modify: 100, + } + ); + frame_system::Pallet::::set_block_number(301); + RewardModule::consume_liquidity(&alice, (0, 1), 10, 301).unwrap(); + assert_eq!( + RewardModule::marketing_rewards(&alice), + Reward { + confirmed: 1000000000000000000000000, + pending_vol: 200, + last_modify: 300, + } + ); + assert_noop!( + RewardModule::remove_liquidity(&alice, (0, 1), 1000), + Error::::InsufficientLiquidity + ); + assert_ok!(RewardModule::remove_liquidity(&alice, (0, 1), 980)); + RewardModule::take_reward(frame_system::RawOrigin::Signed(alice.clone()).into()).unwrap(); + assert_eq!(2500000000000000000000000, Balances::free_balance(&alice)); + frame_system::Pallet::::set_block_number(501); + RewardModule::take_reward(frame_system::RawOrigin::Signed(alice.clone()).into()).unwrap(); + assert_eq!(3500000000000000000000000, Balances::free_balance(&alice)); + RewardModule::put_liquidity(&alice, (0, 1), 1000, 301); + assert_noop!( + RewardModule::remove_liquidity(&alice, (0, 1), 1100), + Error::::InsufficientLiquidity + ); + }); +} diff --git a/pallets/verifier/src/lib.rs b/pallets/verifier/src/lib.rs index 8abae58..35e57e3 100644 --- a/pallets/verifier/src/lib.rs +++ b/pallets/verifier/src/lib.rs @@ -15,15 +15,13 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] pub use pallet::*; -pub mod weights; - #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; - #[cfg(test)] pub mod mock; #[cfg(test)] pub mod tests; +pub mod weights; #[frame_support::pallet] pub mod pallet { @@ -38,7 +36,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use fuso_support::{ constants::*, - traits::{MarketManager, PriceOracle, ReservableToken, Rewarding, Token}, + traits::{FeeBeneficiary, MarketManager, PriceOracle, ReservableToken, Rewarding, Token}, }; use scale_info::TypeInfo; use sp_core::sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}; @@ -61,6 +59,7 @@ pub mod pallet { pub type Season = u32; pub type Amount = u128; pub type MerkleHash = [u8; 32]; + pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"fuso/vrf"); const UNSTAKE_DELAY_BLOCKS: u32 = 14400 * 4u32; const MAX_PROOF_SIZE: usize = 10 * 1024 * 1024usize; @@ -202,6 +201,83 @@ pub mod pallet { RejectTransferIn, } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] + pub enum CommandV2 { + AskLimit { + price: Compact, + amount: Compact, + maker_fee: Compact, + taker_fee: Compact, + base: Compact, + quote: Compact, + broker: Option, + }, + BidLimit { + price: Compact, + amount: Compact, + maker_fee: Compact, + taker_fee: Compact, + base: Compact, + quote: Compact, + broker: Option, + }, + Cancel { + base: Compact, + quote: Compact, + }, + TransferOut { + currency: Compact, + amount: Compact, + }, + TransferIn { + currency: Compact, + amount: Compact, + }, + RejectTransferOut { + currency: Compact, + amount: Compact, + }, + RejectTransferIn, + } + + impl From for CommandV2 { + fn from(cmd: Command) -> Self { + match cmd { + Command::AskLimit(price, amount, maker_fee, taker_fee, base, quote) => { + CommandV2::AskLimit { + price, + amount, + maker_fee, + taker_fee, + base, + quote, + broker: None, + } + } + Command::BidLimit(price, amount, maker_fee, taker_fee, base, quote) => { + CommandV2::BidLimit { + price, + amount, + maker_fee, + taker_fee, + base, + quote, + broker: None, + } + } + Command::Cancel(base, quote) => CommandV2::Cancel { base, quote }, + Command::TransferOut(currency, amount) => { + CommandV2::TransferOut { currency, amount } + } + Command::TransferIn(currency, amount) => CommandV2::TransferIn { currency, amount }, + Command::RejectTransferOut(currency, amount) => { + CommandV2::RejectTransferOut { currency, amount } + } + Command::RejectTransferIn => CommandV2::RejectTransferIn, + } + } + } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub struct Proof { pub event_id: u64, @@ -214,6 +290,33 @@ pub mod pallet { pub root: MerkleHash, } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] + pub struct ProofV2 { + pub event_id: u64, + pub user_id: AccountId, + pub cmd: CommandV2, + pub leaves: Vec, + pub maker_page_delta: u8, + pub maker_account_delta: u8, + pub merkle_proof: Vec, + pub root: MerkleHash, + } + + impl From> for ProofV2 { + fn from(proof: Proof) -> Self { + Self { + event_id: proof.event_id, + user_id: proof.user_id, + cmd: proof.cmd.into(), + leaves: proof.leaves, + maker_page_delta: proof.maker_page_delta, + maker_account_delta: proof.maker_account_delta, + merkle_proof: proof.merkle_proof, + root: proof.root, + } + } + } + #[derive(Clone, Encode, Decode, RuntimeDebug, Eq, PartialEq, TypeInfo)] pub enum Receipt { Authorize(TokenId, Balance, BlockNumber), @@ -225,7 +328,7 @@ pub mod pallet { pub struct Dominator { pub name: Vec, pub staked: Balance, - pub merkle_root: [u8; 32], + pub merkle_root: MerkleHash, pub start_from: BlockNumber, pub sequence: (u64, BlockNumber), pub status: u8, @@ -263,7 +366,7 @@ pub mod pallet { type Asset: ReservableToken; - type Rewarding: Rewarding, Self::BlockNumber>; + type Rewarding: Rewarding, Symbol, Self::BlockNumber>; type WeightInfo: WeightInfo; @@ -281,6 +384,8 @@ pub mod pallet { Self::BlockNumber, >; + type BrokerBeneficiary: FeeBeneficiary; + #[pallet::constant] type DominatorOnlineThreshold: Get>; @@ -681,7 +786,13 @@ pub mod pallet { let proofs: Vec> = Decode::decode(&mut TrailingZeroInput::new(uncompress_proofs.as_ref())) .map_err(|_| Error::::ProofFormatError)?; - Self::verify_batch(dominator_id, &dominator, proofs) + let proofs: Vec> = + proofs.into_iter().map(|p| p.into()).collect::>(); + let beneficiary: T::AccountId = Self::dominator_settings(&dominator_id) + .map(|setting| setting.beneficiary) + .flatten() + .unwrap_or(dominator_id.clone()); + Self::verify_batch(dominator_id, beneficiary, &dominator, proofs) } #[pallet::weight((::WeightInfo::verify(), DispatchClass::Normal, Pays::No))] @@ -696,7 +807,60 @@ pub mod pallet { dominator.status == DOMINATOR_ACTIVE, Error::::DominatorInactive ); - Self::verify_batch(dominator_id, &dominator, proofs) + let proofs: Vec> = + proofs.into_iter().map(|p| p.into()).collect::>(); + let beneficiary: T::AccountId = Self::dominator_settings(&dominator_id) + .map(|setting| setting.beneficiary) + .flatten() + .unwrap_or(dominator_id.clone()); + Self::verify_batch(dominator_id, beneficiary, &dominator, proofs) + } + + #[pallet::weight((::WeightInfo::verify(), DispatchClass::Normal, Pays::No))] + pub fn verify_compress_v2( + origin: OriginFor, + compressed_proofs: Vec, + ) -> DispatchResultWithPostInfo { + let dominator_id = ensure_signed(origin)?; + let dominator = Dominators::::try_get(&dominator_id) + .map_err(|_| Error::::DominatorNotFound)?; + ensure!( + dominator.status == DOMINATOR_ACTIVE, + Error::::DominatorInactive + ); + let (uncompress_size, input) = + lz4_flex::block::uncompressed_size(compressed_proofs.as_ref()) + .map_err(|_| Error::::ProofDecompressError)?; + ensure!(uncompress_size < MAX_PROOF_SIZE, Error::::ProofTooLarge); + let uncompress_proofs = lz4_flex::decompress(input, uncompress_size) + .map_err(|_| Error::::ProofDecompressError)?; + let proofs: Vec> = + Decode::decode(&mut TrailingZeroInput::new(uncompress_proofs.as_ref())) + .map_err(|_| Error::::ProofFormatError)?; + let beneficiary: T::AccountId = Self::dominator_settings(&dominator_id) + .map(|setting| setting.beneficiary) + .flatten() + .unwrap_or(dominator_id.clone()); + Self::verify_batch(dominator_id, beneficiary, &dominator, proofs) + } + + #[pallet::weight((::WeightInfo::verify(), DispatchClass::Normal, Pays::No))] + pub fn verify_v2( + origin: OriginFor, + proofs: Vec>, + ) -> DispatchResultWithPostInfo { + let dominator_id = ensure_signed(origin)?; + let dominator = Dominators::::try_get(&dominator_id) + .map_err(|_| Error::::DominatorNotFound)?; + ensure!( + dominator.status == DOMINATOR_ACTIVE, + Error::::DominatorInactive + ); + let beneficiary: T::AccountId = Self::dominator_settings(&dominator_id) + .map(|setting| setting.beneficiary) + .flatten() + .unwrap_or(dominator_id.clone()); + Self::verify_batch(dominator_id, beneficiary, &dominator, proofs) } #[transactional] @@ -825,18 +989,28 @@ pub mod pallet { #[derive(Clone)] struct ClearingResult { - pub users_mutation: Vec>>, + pub makers: Vec>>, + pub taker: TakerMutation>, pub base_fee: Balance, pub quote_fee: Balance, } #[derive(Clone)] - struct TokenMutation { + struct TakerMutation { + pub who: AccountId, + pub unfilled_volume: Balance, + pub filled_volume: Balance, + pub filled_amount: Balance, + pub base_balance: Balance, + pub quote_balance: Balance, + } + + #[derive(Clone)] + struct MakerMutation { pub who: AccountId, - pub matched_volume: Balance, - pub matched_amount: Balance, - pub base_value: Balance, - pub quote_value: Balance, + pub filled_volume: Balance, + pub base_balance: Balance, + pub quote_balance: Balance, } impl Pallet @@ -983,14 +1157,16 @@ pub mod pallet { fn verify_batch( dominator_id: T::AccountId, + beneficiary: T::AccountId, dominator: &Dominator, BlockNumberFor>, - proofs: Vec>, + proofs: Vec>, ) -> DispatchResultWithPostInfo { let mut known_root = dominator.merkle_root; let mut incr: BTreeMap, (Balance, Balance)> = BTreeMap::new(); for proof in proofs.into_iter() { let trade = Self::verify_and_update( &dominator_id, + &beneficiary, known_root, dominator.start_from.clone(), proof, @@ -1015,9 +1191,10 @@ pub mod pallet { #[transactional] fn verify_and_update( dominator_id: &T::AccountId, + beneficiary: &T::AccountId, known_root: MerkleHash, claim_at: T::BlockNumber, - proof: Proof, + proof: ProofV2, ) -> Result, Balance>, DispatchError> { let mp = smt::CompiledMerkleProof(proof.merkle_proof.clone()); let (old, new): (Vec<_>, Vec<_>) = proof @@ -1045,7 +1222,15 @@ pub mod pallet { vol: Zero::zero(), }; match proof.cmd { - Command::AskLimit(price, amount, maker_fee, taker_fee, base, quote) => { + CommandV2::AskLimit { + price, + amount, + maker_fee, + taker_fee, + base, + quote, + broker, + } => { Self::check_fee(taker_fee.into(), maker_fee.into())?; let (price, amount, maker_fee, taker_fee, base, quote): ( u128, @@ -1078,26 +1263,69 @@ pub mod pallet { dominator_id, &proof.leaves, )?; - if cr.users_mutation.len() > 1 { - for d in cr.users_mutation.iter() { - Self::clear(&d.who, dominator_id, base.into(), d.base_value)?; - Self::clear(&d.who, dominator_id, quote.into(), d.quote_value)?; - T::Rewarding::save_trading(&d.who, d.matched_volume, current_block)?; - } - if let Some(t) = cr.users_mutation.last() { - trade.token_id = base.into(); - trade.amount += t.matched_amount; - trade.vol += t.matched_volume; - } + for d in cr.makers.iter() { + Self::clear(&d.who, dominator_id, base.into(), d.base_balance)?; + Self::clear(&d.who, dominator_id, quote.into(), d.quote_balance)?; + T::Rewarding::consume_liquidity( + &d.who, + (base.into(), quote.into()), + d.filled_volume, + current_block, + )?; } + Self::clear( + &cr.taker.who, + dominator_id, + base.into(), + cr.taker.base_balance, + )?; + Self::clear( + &cr.taker.who, + dominator_id, + quote.into(), + cr.taker.quote_balance, + )?; + T::Rewarding::put_liquidity( + &cr.taker.who, + (base.into(), quote.into()), + cr.taker.unfilled_volume, + current_block, + ); + + trade.token_id = base.into(); + trade.amount += cr.taker.filled_amount; + trade.vol += cr.taker.filled_volume; + Self::put_profit(dominator_id, current_season, quote.into(), cr.quote_fee)?; + // 50% to broker if exists if cr.base_fee != Zero::zero() { - T::Asset::try_mutate_account(&base.into(), dominator_id, |b| { - Ok(b.0 += cr.base_fee) + let base_fee = if let Some(broker) = broker { + let near_half = Permill::from_percent(50).mul_ceil(cr.base_fee); + T::Asset::try_mutate_account( + &base.into(), + &T::BrokerBeneficiary::beneficiary(broker), + |b| Ok(b.0 += near_half), + )?; + cr.base_fee + .checked_sub(&near_half) + .ok_or(Error::::Overflow)? + } else { + cr.base_fee + }; + T::Asset::try_mutate_account(&base.into(), beneficiary, |b| { + Ok(b.0 += base_fee) })?; } } - Command::BidLimit(price, amount, maker_fee, taker_fee, base, quote) => { + CommandV2::BidLimit { + price, + amount, + maker_fee, + taker_fee, + base, + quote, + broker, + } => { Self::check_fee(taker_fee.into(), maker_fee.into())?; let (price, amount, maker_fee, taker_fee, base, quote): ( u128, @@ -1130,31 +1358,70 @@ pub mod pallet { dominator_id, &proof.leaves, )?; - if cr.users_mutation.len() > 1 { - for d in cr.users_mutation.iter() { - Self::clear(&d.who, dominator_id, base.into(), d.base_value)?; - Self::clear(&d.who, dominator_id, quote.into(), d.quote_value)?; - T::Rewarding::save_trading(&d.who, d.matched_volume, current_block)?; - trade.token_id = base.into(); - } - if let Some(t) = cr.users_mutation.last() { - trade.token_id = base.into(); - trade.amount += t.matched_amount; - trade.vol += t.matched_volume; - } + for d in cr.makers.iter() { + Self::clear(&d.who, dominator_id, base.into(), d.base_balance)?; + Self::clear(&d.who, dominator_id, quote.into(), d.quote_balance)?; + T::Rewarding::consume_liquidity( + &d.who, + (base.into(), quote.into()), + d.filled_volume, + current_block, + )?; } + + Self::clear( + &cr.taker.who, + dominator_id, + base.into(), + cr.taker.base_balance, + )?; + Self::clear( + &cr.taker.who, + dominator_id, + quote.into(), + cr.taker.quote_balance, + )?; + T::Rewarding::put_liquidity( + &cr.taker.who, + (base.into(), quote.into()), + cr.taker.unfilled_volume, + current_block, + ); + + trade.token_id = base.into(); + trade.amount += cr.taker.filled_amount; + trade.vol += cr.taker.filled_volume; + Self::put_profit(dominator_id, current_season, quote.into(), cr.quote_fee)?; if cr.base_fee != Zero::zero() { - T::Asset::try_mutate_account(&base.into(), dominator_id, |b| { - Ok(b.0 += cr.base_fee) + let base_fee = if let Some(broker) = broker { + let near_half = Permill::from_percent(50).mul_ceil(cr.base_fee); + T::Asset::try_mutate_account( + &base.into(), + &T::BrokerBeneficiary::beneficiary(broker), + |b| Ok(b.0 += near_half), + )?; + cr.base_fee + .checked_sub(&near_half) + .ok_or(Error::::Overflow)? + } else { + cr.base_fee + }; + T::Asset::try_mutate_account(&base.into(), beneficiary, |b| { + Ok(b.0 += base_fee) })?; } } - Command::Cancel(base, quote) => { + CommandV2::Cancel { base, quote } => { let (base, quote): (u32, u32) = (base.into(), quote.into()); - Self::verify_cancel(base, quote, &proof.user_id, &proof.leaves)?; + let unfilled = Self::verify_cancel(base, quote, &proof.user_id, &proof.leaves)?; + let _ = T::Rewarding::remove_liquidity( + &proof.user_id, + (base.into(), quote.into()), + unfilled.into(), + ); } - Command::TransferOut(currency, amount) => { + CommandV2::TransferOut { currency, amount } => { let (currency, amount) = (currency.into(), amount.into()); let r = Receipts::::get(dominator_id, &proof.user_id) .ok_or(Error::::ReceiptNotExists)?; @@ -1195,7 +1462,7 @@ pub mod pallet { _ => {} } } - Command::TransferIn(currency, amount) => { + CommandV2::TransferIn { currency, amount } => { let (currency, amount) = (currency.into(), amount.into()); let r = Receipts::::get(dominator_id, &proof.user_id) .ok_or(Error::::ReceiptNotExists)?; @@ -1229,7 +1496,7 @@ pub mod pallet { )?; Receipts::::remove(dominator_id, &proof.user_id); } - Command::RejectTransferOut(currency, amount) => { + CommandV2::RejectTransferOut { currency, amount } => { let (currency, amount): (u32, u128) = (currency.into(), amount.into()); let r = Receipts::::get(&dominator_id, &proof.user_id) .ok_or(Error::::ReceiptNotExists)?; @@ -1253,7 +1520,7 @@ pub mod pallet { // needn't step forward return Ok(trade); } - Command::RejectTransferIn => { + CommandV2::RejectTransferIn => { let r = Receipts::::get(&dominator_id, &proof.user_id); if r.is_none() { return Ok(trade); @@ -1344,7 +1611,7 @@ pub mod pallet { ensure!(bid_delta == tb_delta, Error::::ProofsUnsatisfied); let mut mb_delta = 0u128; let mut mq_delta = 0u128; - let mut delta = Vec::new(); + let mut maker_mutation = Vec::new(); for i in 0..maker_accounts as usize / 2 { // base first let maker_base = &leaves[i * 2 + 1]; @@ -1381,36 +1648,32 @@ pub mod pallet { mq_delta += quote_decr; // the accounts should be owned by same user ensure!(maker_b_id == maker_q_id, Error::::ProofsUnsatisfied); - delta.push(TokenMutation { + maker_mutation.push(MakerMutation { who: maker_q_id, - matched_volume: quote_decr.into(), - matched_amount: base_incr.into(), - base_value: mb1.into(), - quote_value: mq1.into(), + // this includes the maker fee + filled_volume: quote_decr.into(), + base_balance: mb1.into(), + quote_balance: mq1.into(), }); } - // FIXME ceil let base_charged = maker_fee.mul_ceil(tb_delta); ensure!( mb_delta + base_charged == tb_delta, Error::::ProofsUnsatisfied ); - // FIXME ceil let quote_charged = taker_fee.mul_ceil(mq_delta); ensure!( - mq_delta - == tq_delta - .checked_add(quote_charged) - .ok_or(Error::::Overflow)?, + tq_delta + quote_charged == mq_delta, Error::::ProofsUnsatisfied ); - delta.push(TokenMutation { + let taker_mutation = TakerMutation { who: taker_b_id, - matched_volume: mq_delta.into(), - matched_amount: tb_delta.into(), - base_value: (tba1 + tbf1).into(), - quote_value: (tqa1 + tqf1).into(), - }); + unfilled_volume: tqf1.into(), + filled_volume: mq_delta.into(), + filled_amount: tb_delta.into(), + base_balance: (tba1 + tbf1).into(), + quote_balance: (tqa1 + tqf1).into(), + }; let best_price = &leaves[maker_accounts as usize + 3]; let (b, q) = best_price.try_get_symbol::()?; ensure!(b == base && q == quote, Error::::ProofsUnsatisfied); @@ -1456,7 +1719,7 @@ pub mod pallet { Error::::ProofsUnsatisfied ); } else { - // filled or conditional_canceled + // filled or conditionally_canceled let vanity_maker = leaves.last().unwrap(); let (b, q, p) = vanity_maker.try_get_orderpage::()?; ensure!(b == base && q == quote, Error::::ProofsUnsatisfied); @@ -1492,7 +1755,8 @@ pub mod pallet { } } Ok(ClearingResult { - users_mutation: delta, + makers: maker_mutation, + taker: taker_mutation, base_fee: base_charged.into(), quote_fee: quote_charged.into(), }) @@ -1555,7 +1819,7 @@ pub mod pallet { ensure!(taker_b_id == taker_q_id, Error::::ProofsUnsatisfied); let mut mb_delta = 0u128; let mut mq_delta = 0u128; - let mut delta = Vec::new(); + let mut maker_mutation = Vec::new(); for i in 0..maker_accounts as usize / 2 { // base first let maker_base = &leaves[i * 2 + 1]; @@ -1593,15 +1857,17 @@ pub mod pallet { mq_delta = mq_delta .checked_add(quote_incr) .ok_or(Error::::Overflow)?; - delta.push(TokenMutation { + let filled_vol = Permill::one() + .checked_sub(&maker_fee) + .ok_or(Error::::Overflow)? + .saturating_reciprocal_mul_ceil(quote_incr); + maker_mutation.push(MakerMutation { who: maker_b_id, - matched_volume: quote_incr.into(), - matched_amount: base_decr.into(), - base_value: mb1.into(), - quote_value: mq1.into(), + filled_volume: filled_vol.into(), + base_balance: mb1.into(), + quote_balance: mq1.into(), }); } - // FIXME ceil let quote_charged = maker_fee.mul_ceil(tq_delta); ensure!( mq_delta @@ -1610,7 +1876,6 @@ pub mod pallet { == tq_delta, Error::::ProofsUnsatisfied ); - // FIXME ceil let base_charged = taker_fee.mul_ceil(mb_delta); ensure!( tb_delta @@ -1626,13 +1891,14 @@ pub mod pallet { Error::::ProofsUnsatisfied ); } - delta.push(TokenMutation { + let taker_mutation = TakerMutation { who: taker_b_id, - matched_volume: tq_delta.into(), - matched_amount: mb_delta.into(), - base_value: (tba1.checked_add(tbf1).ok_or(Error::::Overflow)?).into(), - quote_value: (tqa1.checked_add(tqf1).ok_or(Error::::Overflow)?).into(), - }); + unfilled_volume: tqf1.into(), + filled_volume: tq_delta.into(), + filled_amount: mb_delta.into(), + base_balance: (tba1.checked_add(tbf1).ok_or(Error::::Overflow)?).into(), + quote_balance: (tqa1.checked_add(tqf1).ok_or(Error::::Overflow)?).into(), + }; let best_price = &leaves[maker_accounts as usize + 3]; let (b, q) = best_price.try_get_symbol::()?; ensure!(b == base && q == quote, Error::::ProofsUnsatisfied); @@ -1712,7 +1978,8 @@ pub mod pallet { } } Ok(ClearingResult { - users_mutation: delta, + makers: maker_mutation, + taker: taker_mutation, base_fee: base_charged.into(), quote_fee: quote_charged.into(), }) @@ -1786,7 +2053,7 @@ pub mod pallet { quote: u32, account: &T::AccountId, leaves: &[MerkleLeaf], - ) -> Result<(), DispatchError> { + ) -> Result { ensure!(leaves.len() == 5, Error::::ProofsUnsatisfied); let (b, q) = leaves[0].try_get_symbol::()?; ensure!(b == base && q == quote, Error::::ProofsUnsatisfied); @@ -1819,18 +2086,13 @@ pub mod pallet { ); let before_cancel = leaves[4].split_old_to_sum(); let after_cancel = leaves[4].split_new_to_sum(); + let delta = before_cancel - after_cancel; if cancel_at >= best_ask0 && best_ask0 != 0 { - ensure!( - ask_delta == before_cancel - after_cancel, - Error::::ProofsUnsatisfied - ); + ensure!(ask_delta == delta, Error::::ProofsUnsatisfied); } else { - ensure!( - bid_delta == before_cancel - after_cancel, - Error::::ProofsUnsatisfied - ); + ensure!(bid_delta == delta, Error::::ProofsUnsatisfied); } - Ok(()) + Ok(delta) } fn has_authorized_morethan( diff --git a/pallets/verifier/src/mock.rs b/pallets/verifier/src/mock.rs index 9a44c83..9f91a13 100644 --- a/pallets/verifier/src/mock.rs +++ b/pallets/verifier/src/mock.rs @@ -1,4 +1,5 @@ use crate as pallet_fuso_verifier; +use frame_support::pallet_prelude::DispatchResult; use frame_support::traits::{ConstU32, SortedMembers}; use frame_support::{construct_runtime, parameter_types}; use frame_system as system; @@ -123,7 +124,7 @@ parameter_types! { pub struct PhantomData; -impl fuso_support::traits::Rewarding for PhantomData { +impl fuso_support::traits::Rewarding for PhantomData { type Balance = Balance; fn era_duration() -> BlockNumber { @@ -138,13 +139,30 @@ impl fuso_support::traits::Rewarding for Phanto 0 } - fn save_trading( - _trader: &AccountId, - _amount: Balance, - _at: BlockNumber, - ) -> frame_support::pallet_prelude::DispatchResult { + fn put_liquidity(_maker: &AccountId, _symbol: (u32, u32), _vol: Balance, _at: BlockNumber) {} + + /// when liquidity is took out, the liquidity provider will get the reward. + /// the rewards are calculated in the formula below: + /// contribution ƒi = vol * min(current - from, era_duration) / 720 + /// rewards of contribution ∂ = ƒi / ∑ƒi * era_rewards + /// NOTE: `vol` should be volume rather than amount + fn consume_liquidity( + _maker: &AccountId, + _symbol: (u32, u32), + _vol: Balance, + _current: BlockNumber, + ) -> DispatchResult { Ok(()) } + + /// remove liquidity + fn remove_liquidity( + _maker: &AccountId, + _symbol: (u32, u32), + _vol: Balance, + ) -> Result { + Ok(1) + } } impl pallet_fuso_indicator::Config for Test { @@ -154,6 +172,7 @@ impl pallet_fuso_indicator::Config for Test { impl pallet_fuso_verifier::Config for Test { type Asset = TokenModule; + type BrokerBeneficiary = (); type Callback = RuntimeCall; type DominatorCheckGracePeriod = DominatorCheckGracePeriod; type DominatorOnlineThreshold = DominatorOnlineThreshold; diff --git a/pallets/verifier/src/tests.rs b/pallets/verifier/src/tests.rs index 64697e7..3b0e3dd 100644 --- a/pallets/verifier/src/tests.rs +++ b/pallets/verifier/src/tests.rs @@ -549,3 +549,15 @@ fn run_to_block(n: u32) { Verifier::on_initialize(System::block_number()); } } + +#[test] +pub fn decimal_scale_check() { + use sp_runtime::traits::CheckedSub; + use sp_runtime::Permill; + let fee = Permill::from_parts(1000); + let filled_vol = Permill::one() + .checked_sub(&fee) + .unwrap() + .saturating_reciprocal_mul_ceil(999_000_000000000u128); + assert_eq!(1_000000_000000000u128, filled_vol); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index edeb738..1cbb84d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 159, + spec_version: 161, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 3, @@ -286,7 +286,6 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; - //TODO type WeightInfo = pallet_balances::weights::SubstrateWeight; } @@ -758,6 +757,7 @@ parameter_types! { pub const EraDuration: BlockNumber = DAYS; // TODO move this to storage and automatically calculate by the $tao price pub const RewardsPerEra: Balance = 6575 * DOLLARS; + pub const TimeCoefficientZoom: u32 = 720; pub const RewardTerminateAt: BlockNumber = 1825 * DAYS; } @@ -767,6 +767,7 @@ impl pallet_fuso_reward::Config for Runtime { type RewardTerminateAt = RewardTerminateAt; type RewardsPerEra = RewardsPerEra; type RuntimeEvent = RuntimeEvent; + type TimeCoefficientZoom = TimeCoefficientZoom; } parameter_types! { @@ -782,6 +783,7 @@ const_assert!(DAYS % 20 == 0); impl pallet_fuso_verifier::Config for Runtime { type Asset = Token; + type BrokerBeneficiary = Market; type Callback = RuntimeCall; type DominatorCheckGracePeriod = DominatorCheckGracePeriod; type DominatorOnlineThreshold = DominatorOnlineThreshold;