sherlock-audit / 2024-08-sentiment-v2-judging

1 stars 0 forks source link

A2-security - Inconsistent Interest Accrual Mechanism in `FixedRateModel` and `LinearRateModel` Contracts #188

Closed sherlock-admin3 closed 1 month ago

sherlock-admin3 commented 1 month ago

A2-security

Medium

Inconsistent Interest Accrual Mechanism in FixedRateModel and LinearRateModel Contracts

Summary

The FixedRateModel and LinearRateModel contracts in the Sentiment protocol implement an inconsistent interest accrual mechanism. The current implementation combines aspects of simple and compound interest, leading to random interest amounts based on the frequency of interest updates.

Vulnerability Detail

Let's examine the relevant code from the FixedRateModel contract:

function getInterestAccrued(uint256 lastUpdated, uint256 totalBorrows, uint256) external view returns (uint256) {
    uint256 rateFactor = ((block.timestamp - lastUpdated)).mulDiv(RATE, SECONDS_PER_YEAR, Math.Rounding.Up);
    return totalBorrows.mulDiv(rateFactor, 1e18, Math.Rounding.Up);
}

Interest = Principal * Rate * Time

Where:

This formula, when applied repeatedly with the interest being added to the principal (totalBorrows):

For n periods:

totalBorrows_n = totalBorrows_0 * (1 + R(t))^n

Where:

This formula results in different total interest amounts based on the number of compounding periods, which is determined by how frequently the getInterestAccrued function is called.

The correct approach should either:

  1. Implement true compound interest:

    A = P * (1 + r)^t

    Where A is the final amount, P is the principal, r is the interest rate, and t is the time in years.

  2. Implement a non-compounding method: Keep a separate totalBorrowPrincipal that remains doesn't include intrestRate accrual, and calculate interest based on this fixed principal:

    Interest = totalBorrowPrincipal * Rate * Time

The current implementation falls between these two approaches, creating an inconsistent interest accrual mechanism. This issue lead to unfair and unpredictable interest charges for users, as the total interest paid can vary based on factors outside of their control, such as how often the function accrue get called.

example poc :

Consider a scenario with a fixed interest rate of 20% APR and an initial borrowed amount of 1,000,000 units:

  1. case 2 : Semi-annual updates (accrued function called twice a year)

    • Interest after 6 months: 1,000,000 (0.2 0.5) = 100,000
    • Total borrowed after 6 months: 1,100,000
    • Interest for next 6 months: 1,100,000 (0.2 0.5) = 110,000
    • Total borrowed after 1 year: 1,210,000
  2. case 3 : Quarterly updates (accrued function called four times a year)

    • First quarter: 1,000,000 (0.2 0.25) = 50,000
    • Second quarter: 1,050,000 (0.2 0.25) = 52,500
    • Third quarter: 1,102,500 (0.2 0.25) = 55,125
    • Fourth quarter: 1,157,625 (0.2 0.25) = 57,881
    • Total borrowed after 1 year: 1,215,506

This example demonstrates how the same loan over the same period results in different total interest amounts based solely on the frequency of updates, highlighting the inconsistency in the current implementation.

Impact

  1. Inconsistent interest accrual for borrowers based on update frequency.
  2. Undermined financial predictability and fairness of the lending protocol.
  3. Potential for manipulation of interest payments through timed interactions.

Code Snippet

https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/25a0c8aeaddec273c5318540059165696591ecfb/protocol-v2/src/irm/FixedRateModel.sol#L30C1-L38C6

Tool used

Manual Review

Recommendation

To address the interest calculation issue, the protocol should implement one of the following approaches:

  1. True Compound Interest: Implement a proper compound interest formula using exponential calculations. This ensures consistent interest accrual regardless of update frequency.

  2. Simple Interest with Fixed Principal: Maintain a separate totalBorrowPrincipal that remains constant. Calculate interest based on this fixed principal without compounding.

Either approach should be consistently applied in both FixedRateModel and LinearRateModel contracts.

sherlock-admin3 commented 1 month ago

1 comment(s) were left on this issue during the judging contest.

z3s commented:

Design decision

elhajin commented 1 month ago

We disagree with the judge's decision. The issue is not a design choice but an implementation flaw ,The issue at hand cannot be a design decision for the following reasons:

obou07 commented 1 month ago

Escalate, per comment

sherlock-admin3 commented 1 month ago

Escalate, per comment

You've created a valid escalation!

To remove the escalation from consideration: Delete your comment.

You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.

cvetanovv commented 4 weeks ago

@z3s can I have your opinion here?

z3s commented 4 weeks ago

I still think:

Design decisions are not valid issues. Even if the design is suboptimal, but doesn't imply any loss of funds, these issues are considered informational.

cvetanovv commented 3 weeks ago

We don't have a specific bug here, and as the Lead Judge has stated, this is a design recommendation.

Also, these draft examples that are given are unrealistic because the accrued function is called constantly every day, not several times a year.

Planning to reject the escalation and leave the issue as is.

aliX40 commented 3 weeks ago

We don't have a specific bug here, and as the Lead Judge has stated, this is a design recommendation.

Also, these draft examples that are given are unrealistic because the accrued function is called constantly every day, not several times a year.

Planning to reject the escalation and leave the issue as is.

Hi @cvetanovv and @z3s, the point is there is not a loss of fund, but a loss of yield. Also assuming that the rates will be updated constantly each day, is not optimal considering the permissionlless design of the protocol. Every one can create a pool, and it is the design of the protocol to have multiple pools for the a single asset.

To summarize our points:

cvetanovv commented 3 weeks ago

The example that is given in the issue accrue() function is called 1, 2, or 4 times a year. But this is not true. It is called on every important interaction in the pool, which makes the issue invalid.

My decision to reject the escalation remains.

WangSecurity commented 3 weeks ago

Result: Invalid Unique

sherlock-admin4 commented 3 weeks ago

Escalations have been resolved successfully!

Escalation status: