Open sherlock-admin opened 1 year ago
It seems that in that example of a loan with 3 cycles, 400 then 400 and then 200, if the borrower is more than halfway through the second installment (second 400) , they would be considered to be in the 'lastPaymentCycle' incorrectly and would 'owe' 600 instead of 400. We will investigate this more for a fix.
Escalate for 10 USDC
Issue is from out of scope contract. Both vulnerability and fix are in out-of-scope contract. Awarding issues for contracts not in scope incentivize poor scoping from protocol in the future and lead to more work for auditors with less pay (protocols pay for smaller scope and receive audits for bigger scopes).
Multiple incorrect behaviours could be spotted in MarketRegistry
or ReputationManager
as well but were not reported because they are out of scope.
Escalate for 10 USDC
Issue is from out of scope contract. Both vulnerability and fix are in out-of-scope contract. Awarding issues for contracts not in scope incentivize poor scoping from protocol in the future and lead to more work for auditors with less pay (protocols pay for smaller scope and receive audits for bigger scopes).
Multiple incorrect behaviours could be spotted in
MarketRegistry
orReputationManager
as well but were not reported because they are out of scope.
You've created a valid escalation for 10 USDC!
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.
Escalation rejected
Valid medium. The library is used in the in-scope contract and the error impacts an in-scope contract. This is a valid issue.
Escalation rejected
Valid medium. The library is used in the in-scope contract and the error impacts an in-scope contract. This is a valid issue.
This issue's escalations have been rejected!
Watsons who escalated this issue will have their escalation amount deducted from their next payout.
Fix looks good. Final repayment period is now calculated via modulo which allows it to correctly detect the final payment cycle for loans of irregular duration
immeas
medium
last repayments are calculated incorrectly for "irregular" loan durations
Summary
When taking a loan, a borrower expects that at the end of each payment cycle they should pay
paymentCycleAmount
. This is not true for loans that are not a multiple ofpaymentCycle
.Vulnerability Detail
Imagine a loan of
1000
that is taken for 2.5 payment cycles (skip interest to keep calculations simple).A borrower would expect to pay
400
+400
+200
This holds true for the first installment.
But lets look at what happens at the second installment, here's the calculation of what is to pay in
V2Calculations.sol
:https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/libraries/V2Calculations.sol#L93-L101
Simplified the first calculation says
timeleft = loanDuration - (now - acceptedTimestamp)
and then iftimeleft < paymentCycle
we are within the last payment cycle.This isn't true for loan durations that aren't multiples of the payment cycles. This code says the last payment cycle is when you are one payment cycle from the end of the loan. Which is not the same as last payment cycle as my example above shows.
PoC:
The details of this finding are out of scope but since it makes
TellerV2
, in scope, behave unexpectedly I believe this finding to be in scope.Impact
A borrower taking a loan might not be able to pay the last payment cycle and be liquidated. At the worst possible time since they've paid the whole loan on schedule up to the last installment. The liquidator just need to pay the last installment to take the whole collateral.
This requires the loan to not be a multiple of the payment cycle which might sound odd. But since a year is 365 days and a common payment cycle is 30 days I imagine there can be quite a lot of loans that after 360 days will end up in this issue.
There is also nothing stopping an unknowing borrower from placing a bid or accepting a commitment with an odd duration.
Code Snippet
https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/libraries/V2Calculations.sol#L94-L101
Tool used
Manual Review
Recommendation
First I thought that you could remove the
lastPaymentCycle
calculation all together. I tried that and then also tested what happened with "irregular" loans with interest.Then I found this in the EMI calculation:
https://github.com/sherlock-audit/2023-03-teller/blob/main/teller-protocol-v2/packages/contracts/contracts/libraries/NumbersLib.sol#L123
EMI, which is designed for mortgages, assumes the payments is a discrete number of the same amortization essentially. I.e they don't allow "partial" periods at the end, because that doesn't make sense for a mortgage.
In Teller this is allowed which causes some issues with the EMI calculation since the above row will always round up to a full number of payment periods. If you also count interest, which triggers the EMI calculation: The lender, in an "irregular" loan duration, would get less per installment up to the last one which would be bigger. The funds would all be paid with the correct interest in the end just not in the expected amounts.
My recommendation now is:
either
More math:
From the link in the comment, https://en.wikipedia.org/wiki/Equated_monthly_installment you can follow one of the links in that wiki page to a derivation of the formula: http://rmathew.com/2006/calculating-emis.html
In the middle we have an equation which describes the owed amount at a time $P_n$:
$$P_n=Pt^n-E\frac{(t^n-1)}{t-n}$$ where $t=1+r$ and $r$ is the monthly interest rate ($apy*C/year$).
Now, from here, we want to calculate the loan at a time $P_{n + \Delta}$:
$$P{n + \Delta}=Pt^nt\Delta-E\frac{t^n-1}{t-1}t_\Delta-kE$$
Where $k$ is $c/C$ i.e. the ratio of partial cycle compared to a full cycle.
Same with $t\Delta$ which is $1+r\Delta$, ($r_\Delta$ is also equal to $kr$, ratio of partial cycle rate to full cycle rate, which we'll use later).
Reorganize to get $E$ from above:
$$ E = P r \frac{t^nt\Delta}{t\Delta \frac{t^n-1}{t-1} + k} $$
Now substitute in $1+r$ in place of $t$ and $1+r\Delta$ instead of $t\Delta$ and multiply both numerator and denominator with $r$:
$$ E = P \frac{r (1+r)^n(1+r\Delta)}{(1+r\Delta)((1+r)^n - 1) + kr} $$
and $kr = r_\Delta$ gives us:
$$ E = P r (1+r)^n \frac{(1+r\Delta)}{(1+r\Delta)((1+r)^n - 1) + r_\Delta} $$
To check that this is correct, $r\Delta = 0$ (no extra cycle added) should give us the regular EMI equation. Which we can see is true for the above. And $r\Delta = r$ (a full extra cycle added) should give us the EMI equation but with $n+1$ which we can also see it does.
Here are the code changes to use this, together with changes to
V2Calculations.sol
to calculate the last period correctly:And then
NumbersLib.sol
: