Assessment reports>Suilend>Discussion>Desync between reserve and obligation interest

Desync between reserve and obligation interest

In the protocol interest is tracked in two places. The per-pool interest calculations are performed in reserve. Per-user accounting happens in obligation.

The interest for obligation is calculated every time compound_debt() is called. The function is shown below.

/// Compound the debt on a borrow object
fun compound_debt<P>(borrow: &mut Borrow, reserve: &Reserve<P>) {
    let new_cumulative_borrow_rate = reserve::cumulative_borrow_rate(reserve);

    let compounded_interest_rate = div(
        new_cumulative_borrow_rate,
        borrow.cumulative_borrow_rate
    );

    borrow.borrowed_amount = mul(
        borrow.borrowed_amount,
        compounded_interest_rate
    );

    borrow.cumulative_borrow_rate = new_cumulative_borrow_rate;
}

The function saves the cumulative interest rate to calculate the amount of interest that has accrued since the last time compound_debt() was called. The cumulative interest rate is updated every time compound_interest() is called in reserve.

The functions are called with different frequencies, and in most cases, compound_interest() in reserve will be called more often. Depending on the length of time between calls, the rounding error will be different. Since compound_interest() is called more often, the accounting error is in favor of the protocol. The user ends up paying slightly more interest than is accounted for in the reserve.

Assuming that every five seconds compound_interest() is called, and that compound_debt() is called yearly, the difference in the interest rates is graphed below. Note that this is an extreme case, and that the scale of the y-axis is very small.

Graph showing the interest discrepancy

Zellic © 2025Back to top ↑