Skip to content

Conversation

j4m1ef0rd
Copy link
Contributor

@j4m1ef0rd j4m1ef0rd commented Oct 4, 2025

Pull Request

Closes: PRO-2545

Checklist

Please conduct a thorough self-review before opening the PR.

  • I am confident that the code works.
  • I have written sufficient tests.

Summary

To prevent spam, we have added 3 new minimums in the config:

  • minimum_loan_amount_usd: Minimum equivalent amount of principal that a loan must be left with after expanding or repaying (unless fully repaying).
  • minimum_update_loan_amount_usd: Minimum equivalent amount of principal that can be used to expand or repay an existing loan (unless fully repaying).
  • minimum_update_collateral_amount_usd: Minimum equivalent amount of collateral that can be added or removed from a loan account (unless fully removing).

Added tests to cover all new code. Added to the config RPC. Added to the update config extrinsic.

@j4m1ef0rd j4m1ef0rd requested a review from msgmaxim October 4, 2025 02:56
@j4m1ef0rd j4m1ef0rd self-assigned this Oct 4, 2025
@j4m1ef0rd j4m1ef0rd requested a review from dandanlen as a code owner October 4, 2025 02:56
Copy link

coderabbitai bot commented Oct 4, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/lending-minimum-loan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

codecov bot commented Oct 4, 2025

@j4m1ef0rd j4m1ef0rd changed the title Feat lending minimum loan feat: lending minimum loan Oct 4, 2025
@j4m1ef0rd j4m1ef0rd marked this pull request as draft October 6, 2025 01:57
@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch 2 times, most recently from 1daee51 to ab3dd07 Compare October 8, 2025 03:50
@j4m1ef0rd j4m1ef0rd marked this pull request as ready for review October 8, 2025 03:50
@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch from ab3dd07 to c0bfb53 Compare October 8, 2025 05:20

use crate::LENDING_DEFAULT_CONFIG as CONFIG;
/// Default config with minimum loan/update amounts set to zero
fn config() -> LendingConfiguration {
Copy link
Contributor

@msgmaxim msgmaxim Oct 9, 2025

Choose a reason for hiding this comment

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

Why does this have to be a function? You could still keep it as CONFIG constant iniialising it to

	LendingConfiguration {
		// Set the loan minimums to zero by default for tests
		minimum_loan_amount_usd: 0,
		minimum_update_loan_amount_usd: 0,
		..LENDING_DEFAULT_CONFIG
	}

Then most of the rest of the test code wouldn't need to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did try this already, it has an error:
destructor of general_lending::LendingConfiguration cannot be evaluated at compile-time.
Seems like it should be possible but i'm not sure how to so it.

fn test_loan_minimum_is_enforced() {
const MIN_LOAN_AMOUNT_USD: AssetAmount = 1_000;
const MIN_LOAN_AMOUNT_ASSET: AssetAmount = MIN_LOAN_AMOUNT_USD / SWAP_RATE;
const COLLATERAL_ASSET: Asset = Asset::Eth;
Copy link
Contributor

Choose a reason for hiding this comment

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

I would just use the global COLLATERAL_ASSET (which also happens to be Asset::Eth). Otherwise re-defining it makes it look like the asset is important.


T::Balance::credit_account(borrower_id, loan_asset, excess_amount);
} else {
let config = LendingConfig::<T>::get();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably read config once at the top.

pub fee_swap_max_oracle_slippage: BasisPoints,
/// If set for a pool/asset, this configuration will be used instead of the default
pub pool_config_overrides: BTreeMap<Asset, LendingPoolConfiguration>,
/// Minimum amount of principle that a loan must have at all times.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Minimum amount of principle that a loan must have at all times.
/// Minimum amount of principal that a loan must have at all times.

@msgmaxim
Copy link
Contributor

msgmaxim commented Oct 9, 2025

You will need to update RpcLendingConfig too so the new fields are correctly exposed via RPC (and update the snapshots) @j4m1ef0rd

@j4m1ef0rd j4m1ef0rd requested review from a team and nmammeri as code owners October 9, 2025 04:22
@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch from 86f8adb to 0469763 Compare October 9, 2025 05:35

const ORIGINATION_FEE: AssetAmount =
portion_of_amount(DEFAULT_ORIGINATION_FEE, PRINCIPAL) * SWAP_RATE;
let origination_fee = CONFIG.origination_fee(LOAN_ASSET) * PRINCIPAL * SWAP_RATE;
Copy link
Contributor

Choose a reason for hiding this comment

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

What was the reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

left over from when config was not const. I will revert it.

let mut total_collateral_usd = 0;
for (asset, amount) in assets_amounts {
total_collateral_usd =
total_collateral_usd.saturating_add(usd_value_of::<T>(*asset, *amount)?);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use saturating_accrue

if repayment_amount < loan.owed_principal {
ensure!(
repayment_amount >=
amount_from_usd_value::<T>(
Copy link
Contributor

@msgmaxim msgmaxim Oct 13, 2025

Choose a reason for hiding this comment

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

IMO using usd_value_of here and below would be more natural (e.g. it is slightly more efficient as it doesn't need to invert the price).

/// All fee swaps from lending will be executed with this oracle slippage limit
pub fee_swap_max_oracle_slippage: BasisPoints,
/// Minimum equivalent amount of principal that a loan must have at all times.
pub minimum_loan_amount_usd: AssetAmount,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want all amounts to be in U256 (so they are encoded as hex). See how it is done for liquidation_swap_chunk_size_usd.

@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch from 9ea4a43 to e4dda45 Compare October 13, 2025 02:41
@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch 2 times, most recently from f3aa026 to f97ebaa Compare October 13, 2025 04:33
Base automatically changed from feat/core-lending to main October 15, 2025 10:10
@j4m1ef0rd j4m1ef0rd force-pushed the feat/lending-minimum-loan branch from f97ebaa to 599839a Compare October 16, 2025 02:01

loan.owed_principal.saturating_accrue(extra_principal);

ensure!(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be done in new_loan rather than expand_loan? I know this covers an additional edge case where we have a small loan to begin with, but not sure if we should be preventing the user from addint to a small loan if we already have it (it is not making anything worse). Then putting it in new_loan would look more natural.

Error::<T>::LiquidationInProgress
);

let total_collateral_usd = total_usd_value_of::<T>(&collateral)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better to simply check collateral == loan_account.collateral instead of calculating total usd value of loan_account.collateral? I know it won't make a big difference performance wise, but from readability standopint I think it would be an improvement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but for which collateral asset do we check? it could be any of them, and checking each to see if its below the loans corresponding collateral asset would change the behaviour.

Copy link
Contributor

@msgmaxim msgmaxim Oct 19, 2025

Choose a reason for hiding this comment

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

So the current/desired behaviour is to ensure that the value of the collateral removed is at least minimum_update_collateral_amount_usd, but we make an exception for the case where the user wants to remove all collateral, right? What I'm suggesting is that we can check if the exception rule applies by comparing the map of assets provided to loan_account.collateral instead of computing their values. You still need to compute the value of added collateral in the non-exceptional case of course.

However, this does make be think that we might want to interpret empty collateral as "withdraw everything" (it might be tricky to provide the exact amount considering that we charge interest).

#[pallet::storage]
pub type LendingConfig<T: Config> =
StorageValue<_, LendingConfiguration, ValueQuery, LendingConfigDefault>;
pub type LendingConfig<T: Config> = StorageValue<_, LendingConfiguration, ValueQuery>;
Copy link
Contributor

Choose a reason for hiding this comment

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

We discussed this before I believe. Do we need this change? I think I'd rather provide LendingConfigDefault here explicitly rather than implementing Default which seems like it is not needed anywhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants