Skip to content

Commit

Permalink
feat: benchmark foreign investments worst case (#1565)
Browse files Browse the repository at this point in the history
* feat: orderbook bench helper

* feat: add bench_fill_order_full

* refactor: bench_create_pool

* refactor: orderbook bench helper

* feat: add InvestmentIdBenchmarkHelper

* feat: support single pallet check benchmark

* fix: use fixed weight for fn process_msg()

* fix: mock_bench_default_investment_id

* fix: order book bench

* refactor: use Ratio in FI

* feat: ForeignInvestmentBenchmarkHelper

* bench: fund pool account in create setup

* wip: bench lp inbound msg

* feat: bench inbound collect redeem

* tmp: present generated weights

* fix: Quantity remnant

* refactor: defensive weights

* refactor: add FI bench setup return type

* refactor: explicit return type

---------

Co-authored-by: Frederik Gartenmeister <[email protected]>
  • Loading branch information
wischli and mustermeiszer authored Sep 25, 2023
1 parent e72dfe0 commit 255f8fd
Show file tree
Hide file tree
Showing 14 changed files with 501 additions and 113 deletions.
53 changes: 53 additions & 0 deletions libs/traits/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,56 @@ pub trait OrderBookBenchmarkHelper {
/// Fulfills the given swap order from the trader account
fn bench_fill_order_full(trader: Self::AccountId, order_id: Self::OrderIdNonce);
}

/// A representation of information helpful when doing a foreign investment
/// benchmark setup.
pub struct BenchForeignInvestmentSetupInfo<AccountId, InvestmentId, CurrencyId> {
/// The substrate investor address
pub investor: AccountId,
/// The investment id
pub investment_id: InvestmentId,
/// The pool currency which eventually will be invested
pub pool_currency: CurrencyId,
/// The foreign currency which shall be invested and thus swapped into pool
/// currency beforehand
pub foreign_currency: CurrencyId,
/// Bidirectionally funded to fulfill token swap orders
pub funded_trader: AccountId,
}

/// Benchmark utility for updating/collecting foreign investments and
/// redemptions.
pub trait ForeignInvestmentBenchmarkHelper {
type AccountId;
type Balance;
type CurrencyId;
type InvestmentId;

/// Perform necessary setup to enable an investor to invest with or redeem
/// into a foreign currency.
///
/// Returns
/// * The substrate investor address
/// * The investment id
/// * The pool currency id
/// * The foreign currency id
/// * A trading account which can bidirectionally fulfill swap orders for
/// the (foreign, pool) currency pair
fn bench_prepare_foreign_investments_setup(
) -> BenchForeignInvestmentSetupInfo<Self::AccountId, Self::InvestmentId, Self::CurrencyId>;

/// Perform necessary setup to prepare for the worst benchmark case by
/// calling just a single subsequent function.
///
/// NOTE: For the time being, the worst case should be collecting a
/// redemption when there is an active invest swap from foreign to pool. The
/// redemption collection will initiate a swap from pool to foreign such
/// that there is a swap merge conflict to be resolved.
fn bench_prep_foreign_investments_worst_case(
investor: Self::AccountId,
investment_id: Self::InvestmentId,
foreign_currency: Self::CurrencyId,
pool_currency: Self::CurrencyId,
);
}
159 changes: 159 additions & 0 deletions pallets/foreign-investments/src/impls/benchmark_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2023 Centrifuge Foundation (centrifuge.io).
// This file is part of Centrifuge Chain project.

// 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 cfg_traits::{
benchmarking::{
BenchForeignInvestmentSetupInfo, ForeignInvestmentBenchmarkHelper,
InvestmentIdBenchmarkHelper, OrderBookBenchmarkHelper, PoolBenchmarkHelper,
},
investments::{ForeignInvestment, OrderManager},
};
use cfg_types::{
fixed_point::Ratio,
orders::{FulfillmentWithPrice, TotalOrder},
tokens::CurrencyId,
};
use frame_benchmarking::Zero;
use frame_support::assert_ok;
use sp_runtime::{DispatchError, FixedPointNumber, Perquintill};

use crate::{Config, Pallet};

pub const CURRENCY_POOL: CurrencyId = CurrencyId::ForeignAsset(1);
pub const CURRENCY_FOREIGN: CurrencyId = CurrencyId::ForeignAsset(2);
pub const DECIMALS_POOL: u32 = 12;
pub const DECIMALS_FOREIGN: u32 = 6;
pub const INVEST_AMOUNT_POOL_DENOMINATED: u128 = 1_000_000_000_000;
pub const INVEST_AMOUNT_FOREIGN_DENOMINATED: u128 = INVEST_AMOUNT_POOL_DENOMINATED / 1_000_000;

impl<T: Config> ForeignInvestmentBenchmarkHelper for Pallet<T>
where
T::Balance: From<u128>,
T::CurrencyId: From<CurrencyId>,
T::PoolInspect: PoolBenchmarkHelper<PoolId = T::PoolId, AccountId = T::AccountId, Balance = T::Balance>
+ InvestmentIdBenchmarkHelper<PoolId = T::PoolId, InvestmentId = T::InvestmentId>,
T::TokenSwaps: OrderBookBenchmarkHelper<
AccountId = T::AccountId,
Balance = T::Balance,
CurrencyId = T::CurrencyId,
OrderIdNonce = T::TokenSwapOrderId,
>,
T::Investment: OrderManager<
Error = DispatchError,
InvestmentId = T::InvestmentId,
Orders = TotalOrder<T::Balance>,
Fulfillment = FulfillmentWithPrice<T::BalanceRatio>,
>,
T::BalanceRatio: From<Ratio>,
{
type AccountId = T::AccountId;
type Balance = T::Balance;
type CurrencyId = T::CurrencyId;
type InvestmentId = T::InvestmentId;

fn bench_prepare_foreign_investments_setup(
) -> BenchForeignInvestmentSetupInfo<Self::AccountId, Self::InvestmentId, Self::CurrencyId> {
let pool_id = Default::default();
let pool_admin: T::AccountId = frame_benchmarking::account("pool_admin", 0, 0);
<T::PoolInspect as PoolBenchmarkHelper>::bench_create_pool(pool_id, &pool_admin);

// Add bidirectional trading pair and fund both accounts
let (investor, funded_trader) =
<T::TokenSwaps as OrderBookBenchmarkHelper>::bench_setup_trading_pair(
CURRENCY_POOL.into(),
CURRENCY_FOREIGN.into(),
INVEST_AMOUNT_POOL_DENOMINATED.into(),
INVEST_AMOUNT_FOREIGN_DENOMINATED.into(),
DECIMALS_POOL.into(),
DECIMALS_FOREIGN.into(),
);
<T::TokenSwaps as OrderBookBenchmarkHelper>::bench_setup_trading_pair(
CURRENCY_FOREIGN.into(),
CURRENCY_POOL.into(),
INVEST_AMOUNT_FOREIGN_DENOMINATED.into(),
INVEST_AMOUNT_POOL_DENOMINATED.into(),
DECIMALS_FOREIGN.into(),
DECIMALS_POOL.into(),
);

// Grant investor permissions
<T::PoolInspect as PoolBenchmarkHelper>::bench_investor_setup(
pool_id,
investor.clone(),
T::Balance::zero(),
);
let investment_id =
<T::PoolInspect as InvestmentIdBenchmarkHelper>::bench_default_investment_id(pool_id);

BenchForeignInvestmentSetupInfo {
investor,
investment_id,
pool_currency: CURRENCY_POOL.into(),
foreign_currency: CURRENCY_FOREIGN.into(),
funded_trader,
}
}

fn bench_prep_foreign_investments_worst_case(
investor: Self::AccountId,
investment_id: Self::InvestmentId,
pool_currency: Self::CurrencyId,
foreign_currency: Self::CurrencyId,
) {
log::debug!(
"Preparing worst case foreign investment benchmark setup with pool currency {:?} and foreign currency: {:?}",
pool_currency,
foreign_currency
);

// Create `InvestState::ActiveSwapIntoPoolCurrency` and prepare redemption for
// collection by redeeming
assert_ok!(Pallet::<T>::increase_foreign_investment(
&investor,
investment_id,
INVEST_AMOUNT_FOREIGN_DENOMINATED.into(),
foreign_currency,
pool_currency,
));
assert_eq!(
crate::InvestmentPaymentCurrency::<T>::get(&investor, investment_id).unwrap(),
foreign_currency
);

log::debug!("Increasing foreign redemption");
assert_ok!(Pallet::<T>::increase_foreign_redemption(
&investor,
investment_id,
INVEST_AMOUNT_FOREIGN_DENOMINATED.into(),
foreign_currency,
));
assert_eq!(
crate::RedemptionPayoutCurrency::<T>::get(&investor, investment_id).unwrap(),
foreign_currency
);

// Process redemption such that collecting will trigger worst case
let fulfillment: FulfillmentWithPrice<T::BalanceRatio> = FulfillmentWithPrice {
of_amount: Perquintill::from_percent(50),
price: Ratio::checked_from_rational(1, 4).unwrap().into(),
};
assert_ok!(<T::Investment as OrderManager>::process_redeem_orders(
investment_id
));
assert_ok!(<T::Investment as OrderManager>::redeem_fulfillment(
investment_id,
fulfillment
));
log::debug!("Worst case benchmark foreign investment setup done!");
}
}
6 changes: 4 additions & 2 deletions pallets/foreign-investments/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ use crate::{
RedemptionPayoutCurrency, RedemptionState, SwapOf, TokenSwapOrderIds,
};

#[cfg(feature = "runtime-benchmarks")]
mod benchmark_utils;
mod invest;
mod redeem;

Expand Down Expand Up @@ -795,7 +797,7 @@ impl<T: Config> Pallet<T> {
swap_order_id,
swap.amount,
// The max accepted sell rate is independent of the asset type for now
T::DefaultTokenSellRate::get(),
T::DefaultTokenSellRatio::get(),
// The minimum fulfillment must be everything
swap.amount,
)?;
Expand Down Expand Up @@ -824,7 +826,7 @@ impl<T: Config> Pallet<T> {
swap.currency_out,
swap.amount,
// The max accepted sell rate is independent of the asset type for now
T::DefaultTokenSellRate::get(),
T::DefaultTokenSellRatio::get(),
// The minimum fulfillment must be everything
swap.amount,
)?;
Expand Down
6 changes: 3 additions & 3 deletions pallets/foreign-investments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub mod pallet {

/// Type for price ratio for cost of incoming currency relative to
/// outgoing
type Rate: Parameter
type BalanceRatio: Parameter
+ Member
+ sp_runtime::FixedPointNumber
+ sp_runtime::traits::EnsureMul
Expand All @@ -171,7 +171,7 @@ pub mod pallet {
/// more sophisticated swap price discovery. For now, this should be set
/// to one.
#[pallet::constant]
type DefaultTokenSellRate: Get<Self::Rate>;
type DefaultTokenSellRatio: Get<Self::BalanceRatio>;

/// The token swap order identifying type
type TokenSwapOrderId: Parameter
Expand All @@ -190,7 +190,7 @@ pub mod pallet {
Balance = Self::Balance,
OrderId = Self::TokenSwapOrderId,
OrderDetails = Swap<Self::Balance, Self::CurrencyId>,
SellRatio = Self::Rate,
SellRatio = Self::BalanceRatio,
>;

/// The hook type which acts upon a finalized investment decrement.
Expand Down
2 changes: 1 addition & 1 deletion pallets/liquidity-pools-gateway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ pub mod pallet {
}

/// Process an incoming message.
#[pallet::weight(0)]
#[pallet::weight(T::WeightInfo::process_msg())]
#[pallet::call_index(5)]
pub fn process_msg(
origin: OriginFor<T>,
Expand Down
68 changes: 68 additions & 0 deletions pallets/liquidity-pools/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2021 Centrifuge Foundation (centrifuge.io).
// This file is part of Centrifuge Chain project.

// 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 cfg_traits::{
benchmarking::{BenchForeignInvestmentSetupInfo, ForeignInvestmentBenchmarkHelper},
investments::{ForeignInvestment, TrancheCurrency},
};
use frame_benchmarking::v2::*;

use super::*;
use crate::Pallet;

#[benchmarks(
where
T::ForeignInvestment: ForeignInvestmentBenchmarkHelper<AccountId = T::AccountId, Balance = T::Balance, CurrencyId = T::CurrencyId, InvestmentId = T::TrancheCurrency>,
T::Balance: From<u128>,
T::AccountId: From<[u8; 32]> + Into<[u8; 32]>,
)]
mod benchmarks {
use super::*;

#[benchmark]
fn inbound_collect_redeem() {
let BenchForeignInvestmentSetupInfo { investor, investment_id, pool_currency, foreign_currency, .. } = <T::ForeignInvestment as ForeignInvestmentBenchmarkHelper>::bench_prepare_foreign_investments_setup();

// Fund investor with foreign currency and tranche tokens
T::Tokens::mint_into(
investment_id.clone().into(),
&investor,
(u128::max_value() / 10).into(),
)?;
T::Tokens::mint_into(foreign_currency, &investor, (u128::max_value() / 10).into())?;

// Increase investment and redemption
<T::ForeignInvestment as ForeignInvestmentBenchmarkHelper>::bench_prep_foreign_investments_worst_case(investor.clone(), investment_id.clone(), pool_currency, foreign_currency);

let investor_pointer = investor.clone();
let redeeming_amount =
T::ForeignInvestment::redemption(&investor_pointer, investment_id.clone())?;
let pool_id = investment_id.of_pool();
let tranche_id = investment_id.of_tranche();
let foreign_currency_index = Pallet::<T>::try_get_general_index(foreign_currency)?.into();

#[block]
{
Pallet::<T>::handle_collect_redemption(
pool_id,
tranche_id,
investor,
foreign_currency_index,
)?;
}

assert!(
T::ForeignInvestment::redemption(&investor_pointer, investment_id)? < redeeming_amount
);
}
}
Loading

0 comments on commit 255f8fd

Please sign in to comment.