Skip to content

Commit

Permalink
feat: Add Pool Fees (#1633)
Browse files Browse the repository at this point in the history
* chore: init blank pool fees pallet

* chore: apply workspace #1609 to pool-fees dummy

* fix: docker-compose

* feat: add changeguard pool fees support

* wip: prepare payment logic

* refactor: use reserve instead of nav

* chore: add extrinsics

* feat: prep + pay disbursements

* refactor: Apply fees ChangeGuard to #1637

* feat: add pool fees to all runtimes

* feat: add fees pool registration

* fix: existing pool unit tests

* fix: existing integration tests

* docs: add pool fee types

* wip: init fees unit tests

* tests: extrinsics wip

* chore: add events

* tests: add pool fees unit tests

* fix: support retroactive disbursements

* refactor: add epoch transition hook

* refactor: add pool fee prefix to types

* refactor: remove redundand trait bounds

* wip: pool system integration tests

* refactor: move portfolio valuation from loans to cfg-types

* chore: add pool fee account id

* wip: pool fee nav

* wip: fix uts

* wip: fix apply review by @lemunozm

* fix: issues after rebase

* tests: add saturated_proration

* refactor: simplify pool fee amounts

* chore: aum + fix fees UTs

* chore: apply AUM to pool-system

* fix: remove AUM coupling in PoolFees

* fix: transfer on close, unit tests

* fix: use total nav

* fix: taplo

* fix: fee calc on nav closing

* feat: impl TimeAsSecs for timestamp mock

* fix: test on_closing instead of update_active_fees

* fix: clippy

* tests: fix + add missing pool fees

* refactor: make update fees result instead of void

* tests: add insufficient resource in p-system

* bench: add pool fees, apply to system + registry

* fix: tests

* refactor: explicitly use Seconds in FeeAmountProration impl

* docs: add PoolFeeAmount and NAV update

* refactor: update NAV, total assets after review from @mustermeiszer

* fix: clippy

* refactor: Add PoolFeePayable

* fix: clippy

* fix: correct epoch execution with fees (#1695)

* fix: correct epoch execution with fees

* refactor: use new nav syntax

* tests: fix auto epoch closing

* feat: epoch execution migration

* chore: add epoch migration to altair

---------

Co-authored-by: William Freudenberger <[email protected]>

---------

Co-authored-by: Guillermo Perez <[email protected]>
Co-authored-by: Frederik Gartenmeister <[email protected]>
  • Loading branch information
3 people committed Jan 29, 2024
1 parent 0077af9 commit aad505d
Show file tree
Hide file tree
Showing 58 changed files with 4,876 additions and 216 deletions.
38 changes: 37 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ members = [
"pallets/oracle-collection",
"pallets/order-book",
"pallets/permissions",
"pallets/pool-fees",
"pallets/pool-system",
"pallets/pool-registry",
"pallets/restricted-tokens",
Expand Down Expand Up @@ -63,7 +64,7 @@ smallvec = "1.6.1"
serde = { version = "1.0.119", features = ["derive"] }
parity-scale-codec = { version = "3.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.3.0", default-features = false, features = ["derive"] }
log = "0.4"
log = { version = "0.4", default-features = false }
getrandom = { version = "0.2", features = ["js"] }
static_assertions = "1.1.0"
lazy_static = "1.4.0"
Expand All @@ -78,6 +79,7 @@ futures = "0.3.25"
jsonrpsee = { version = "0.16.2", features = ["server", "macros"] }
url = "2.2.2"
tempfile = "3.1.0"
strum = { version = "0.24", default-features = false, features = ["derive"] }

# Cumulus
cumulus-pallet-aura-ext = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "polkadot-v0.9.43" }
Expand Down Expand Up @@ -272,6 +274,7 @@ pallet-oracle-feed = { path = "pallets/oracle-feed", default-features = false }
pallet-oracle-collection = { path = "pallets/oracle-collection", default-features = false }
pallet-order-book = { path = "pallets/order-book", default-features = false }
pallet-permissions = { path = "pallets/permissions", default-features = false }
pallet-pool-fees = { path = "pallets/pool-fees", default-features = false }
pallet-pool-registry = { path = "pallets/pool-registry", default-features = false }
pallet-pool-system = { path = "pallets/pool-system", default-features = false }
pallet-restricted-tokens = { path = "pallets/restricted-tokens", default-features = false }
Expand Down
9 changes: 9 additions & 0 deletions libs/mocks/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ pub mod pallet {
execute_call!(())
}
}

impl<T: Config> frame_support::traits::UnixTime for Pallet<T>
where
T::Moment: Into<u64>,
{
fn now() -> std::time::Duration {
core::time::Duration::from_millis(<Pallet<T> as Time>::now().into())
}
}
}
11 changes: 11 additions & 0 deletions libs/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ pub mod types {

/// The type for indexing pallets on a Substrate runtime
pub type PalletIndex = u8;

/// The representation of a pool fee identifier
pub type PoolFeeId = u64;
}

/// Common constants for all runtimes
Expand Down Expand Up @@ -272,6 +275,14 @@ pub mod constants {
pub const MAX_POV_SIZE: u64 = cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64;
/// Block storage limit in bytes. Set to 40 KB.
pub const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;

/// The maximum number of fees per pool.
///
/// NOTE: Must be ge than [MAX_POOL_FEES_PER_BUCKET].
pub const MAX_FEES_PER_POOL: u32 = 200;

/// The maximum number of pool fees per pool fee bucket
pub const MAX_POOL_FEES_PER_BUCKET: u32 = 100;
}

/// Listing of parachains we integrate with.
Expand Down
2 changes: 2 additions & 0 deletions libs/traits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ scale-info = { version = "2.3.0", default-features = false, features = ["derive"
sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.43" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.43" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.43" }
strum = { workspace = true, default-features = false }

[dev-dependencies]
cfg-mocks = { path = "../mocks" }
Expand All @@ -42,6 +43,7 @@ std = [
"sp-std/std",
"cfg-primitives/std",
"scale-info/std",
"strum/std",
]
try-runtime = [
"frame-support/try-runtime",
Expand Down
25 changes: 25 additions & 0 deletions libs/traits/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_std::{fmt::Debug, vec::Vec};

use crate::fee::PoolFeeBucket;

/// Benchmark utility to create pools
pub trait PoolBenchmarkHelper {
type PoolId;
Expand Down Expand Up @@ -120,3 +126,22 @@ pub trait ForeignInvestmentBenchmarkHelper {
pool_currency: Self::CurrencyId,
);
}

/// Benchmark utility for adding pool fees
pub trait PoolFeesBenchmarkHelper {
type PoolFeeInfo: Encode + Decode + Clone + TypeInfo + Debug;
type PoolId: Encode + Decode + Clone + TypeInfo + Debug;

/// Generate n default fixed pool fees and return their info
fn get_pool_fee_infos(n: u32) -> Vec<Self::PoolFeeInfo>;

/// Add the default fixed fee `n` times to the given pool and bucket pair
fn add_pool_fees(pool_id: Self::PoolId, bucket: PoolFeeBucket, n: u32);

/// Get the fee info for a fixed pool fee which takes 1% of the NAV
fn get_default_fixed_fee_info() -> Self::PoolFeeInfo;

/// Get the fee info for a chargeable pool fee which can be charged up to
/// 1000u128 per second
fn get_default_charged_fee_info() -> Self::PoolFeeInfo;
}
73 changes: 73 additions & 0 deletions libs/traits/src/fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2023 Centrifuge Foundation (centrifuge.io).

// Centrifuge 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 (see http://www.gnu.org/licenses).

// Centrifuge 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
// GNU General Public License for more details.

use frame_support::dispatch::{Decode, Encode, MaxEncodedLen, TypeInfo};
use sp_runtime::DispatchResult;
use strum::{EnumCount, EnumIter};

/// The priority segregation of pool fees
///
/// NOTE: Whenever a new variant is added, must bump
/// [cfg_primitives::MAX_FEES_PER_POOL].
#[derive(
Debug, Encode, Decode, EnumIter, EnumCount, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone, Copy,
)]
pub enum PoolFeeBucket {
/// Fees that are charged first, before any redemptions, investments,
/// repayments or originations
Top,
// Future: AfterTranche(TrancheId)
}

/// Trait to add fees to a pool
pub trait PoolFees {
type PoolId;
type FeeInfo;

/// Add a new fee to the pool and bucket.
///
/// NOTE: Assumes call permissions are separately checked beforehand.
fn add_fee(pool_id: Self::PoolId, bucket: PoolFeeBucket, fee: Self::FeeInfo) -> DispatchResult;

/// Returns the maximum number of pool fees per bucket required for accurate
/// weights
fn get_max_fees_per_bucket() -> u32;

/// Returns the current amount of active fees for the given pool and bucket
/// pair
fn get_pool_fee_bucket_count(pool: Self::PoolId, bucket: PoolFeeBucket) -> u32;
}

/// Trait to prorate a fee amount to a rate or amount
pub trait FeeAmountProration<Balance, Rate, Time> {
/// Returns the prorated amount based on the NAV passed time period.
fn saturated_prorated_amount(&self, portfolio_valuation: Balance, period: Time) -> Balance;

/// Returns the proratio rate based on the NAV and passed time period.
fn saturated_prorated_rate(&self, portfolio_valuation: Balance, period: Time) -> Rate;
}

#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;

use super::*;

#[test]
fn max_fees_per_pool() {
assert!(
cfg_primitives::MAX_POOL_FEES_PER_BUCKET
<= (cfg_primitives::MAX_FEES_PER_POOL * PoolFeeBucket::iter().count() as u32),
"Need to bump MAX_FEES_PER_POOL after adding variant(s) to PoolFeeBuckets"
);
}
}
22 changes: 22 additions & 0 deletions libs/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub mod rewards;
#[cfg(feature = "runtime-benchmarks")]
/// Traits related to benchmarking tooling.
pub mod benchmarking;
pub mod fee;

/// A trait used for loosely coupling the claim pallet with a reward mechanism.
///
Expand Down Expand Up @@ -176,6 +177,7 @@ pub trait PoolMutate<AccountId, PoolId> {
type MaxTranches: Get<u32>;
type TrancheInput: Encode + Decode + Clone + TypeInfo + Debug + PartialEq;
type PoolChanges: Encode + Decode + Clone + TypeInfo + Debug + PartialEq + MaxEncodedLen;
type PoolFeeInput: Encode + Decode + Clone + TypeInfo + Debug;

fn create(
admin: AccountId,
Expand All @@ -184,6 +186,7 @@ pub trait PoolMutate<AccountId, PoolId> {
tranche_inputs: Vec<Self::TrancheInput>,
currency: Self::CurrencyId,
max_reserve: Self::Balance,
pool_fees: Vec<Self::PoolFeeInput>,
) -> DispatchResult;

fn update(pool_id: PoolId, changes: Self::PoolChanges) -> Result<UpdateState, DispatchError>;
Expand Down Expand Up @@ -624,6 +627,25 @@ pub trait StatusNotificationHook {
fn notify_status_change(id: Self::Id, status: Self::Status) -> Result<(), Self::Error>;
}

/// Trait to signal an epoch transition.
pub trait EpochTransitionHook {
type Balance;
type PoolId;
type Time;
type Error;

/// Hook into the closing of an epoch
fn on_closing_mutate_reserve(
pool_id: Self::PoolId,
assets_under_management: Self::Balance,
reserve: &mut Self::Balance,
) -> Result<(), Self::Error>;

/// Hook into the execution of an epoch before any investment and
/// redemption fulfillments
fn on_execution_pre_fulfillments(pool_id: Self::PoolId) -> Result<(), Self::Error>;
}

/// Trait to synchronously provide a currency conversion estimation for foreign
/// currencies into/from pool currencies.
pub trait IdentityCurrencyConversion {
Expand Down
2 changes: 1 addition & 1 deletion libs/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ bitflags = { version = "1.3", default-features = false }
hex-literal = { version = "0.3.4", default-features = false }
parity-scale-codec = { version = "3.0.0", features = ["derive"], default-features = false }
scale-info = { version = "2.3.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.119" }
serde = { workspace = true }

# substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.43" }
Expand Down
1 change: 1 addition & 0 deletions libs/types/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub const NFT_SALES_PALLET_ID: PalletId = PalletId(*b"pal/nfts");
pub const STAKE_POT_PALLET_ID: PalletId = PalletId(*b"PotStake");
pub const BLOCK_REWARDS_PALLET_ID: PalletId = PalletId(*b"cfg/blrw");
pub const LIQUIDITY_REWARDS_PALLET_ID: PalletId = PalletId(*b"cfg/lqrw");
pub const POOL_FEES_PALLET_ID: PalletId = PalletId(*b"cfg/plfs");

// Other ids
pub const CHAIN_BRIDGE_HASH_ID: [u8; 13] = *b"cent_nft_hash";
Expand Down
1 change: 1 addition & 0 deletions libs/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod oracles;
pub mod orders;
pub mod permissions;
pub mod pools;
pub mod portfolio;
pub mod time;
pub mod tokens;
pub mod xcm;
Expand Down
Loading

0 comments on commit aad505d

Please sign in to comment.