Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: benchmark foreign investments worst case #1565

Merged
merged 27 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3189ed3
feat: orderbook bench helper
wischli Sep 19, 2023
b7227b4
feat: add bench_fill_order_full
wischli Sep 19, 2023
bb563a0
refactor: bench_create_pool
wischli Sep 19, 2023
80f4c2f
refactor: orderbook bench helper
wischli Sep 19, 2023
ac3ae0c
feat: add InvestmentIdBenchmarkHelper
wischli Sep 20, 2023
6681346
Merge branch 'main' into feat/orderbook-bench-helper
wischli Sep 20, 2023
32e28bd
feat: support single pallet check benchmark
wischli Sep 20, 2023
d1b80a5
fix: use fixed weight for fn process_msg()
mustermeiszer Sep 22, 2023
2131eda
Merge branch 'main' into feat/orderbook-bench-helper
wischli Sep 22, 2023
ff04c51
fix: mock_bench_default_investment_id
wischli Sep 22, 2023
6adb198
fix: order book bench
wischli Sep 22, 2023
daddfb6
refactor: use Ratio in FI
wischli Sep 22, 2023
0af85e5
feat: ForeignInvestmentBenchmarkHelper
wischli Sep 22, 2023
1d6a5b1
bench: fund pool account in create setup
wischli Sep 22, 2023
9cd2f50
wip: bench lp inbound msg
wischli Sep 22, 2023
67aef5b
Merge remote-tracking branch 'origin/feat/orderbook-bench-helper' int…
wischli Sep 22, 2023
4fd7682
feat: bench inbound collect redeem
wischli Sep 22, 2023
99e7087
tmp: present generated weights
wischli Sep 22, 2023
a9f5b3f
Merge remote-tracking branch 'origin/main' into bench/foreign-investm…
wischli Sep 22, 2023
d34c7ce
fix: Quantity remnant
wischli Sep 25, 2023
a22054e
refactor: defensive weights
wischli Sep 25, 2023
239fa89
refactor: add FI bench setup return type
wischli Sep 25, 2023
0d1c6f3
Merge remote-tracking branch 'origin/fix/unweighted-process-msg' into…
wischli Sep 25, 2023
030c3a0
refactor: explicit return type
wischli Sep 25, 2023
1936e32
Merge remote-tracking branch 'origin/main' into bench/foreign-investm…
wischli Sep 25, 2023
04ea57a
Merge branch 'main' into bench/foreign-investments
wischli Sep 25, 2023
90eb02e
Merge branch 'main' into bench/foreign-investments
wischli Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: I really like how the bench_* methods are called inside bench_* methods, leaving a clean API to use from the benchmark side with 0 knowledge about what's happening under the hood. i.e. the pool_id used is totally transparent here ❤️


// 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
Copy link
Contributor Author

@wischli wischli Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for feature creeping. This change does not have any functional impact as it's just renaming.

+ 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
Loading