Closed sherlock-admin2 closed 6 months ago
The protocol team fixed this issue in PR/commit https://github.com/napierfi/napier-v1/pull/170.
Escalate
This issue should be high, though only Tranche's last user would be affected, but with the consideration there would be many Tranche instances existing concurrently based on different adapters and different maturity, and new Tranches
would also be continuously created after maturity of old ones. Therefore, in the whole protocol level, many users would suffer this risk, and this vulnerability would be easily triggered. It doesn't meet Shrelock's rule for Medium(a loss is highly constrained
), but is suitable to treate as High (loss of funds without extensive limitations
)
Escalate This issue should be high, though only Tranche's last user would be affected, but with the consideration there would be many Tranche instances existing concurrently based on different adapters and different maturity, and new
Tranches
would also be continuously created after maturity of old ones. Therefore, in the whole protocol level, many users would suffer this risk, and this vulnerability would be easily triggered. It doesn't meet Shrelock's rule for Medium(a loss is highly constrained
), but is suitable to treate as High (loss of funds without extensive limitations
)
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.
Escalate
When the Tranche.withdraw
function is rounded down, this means that for each withdrawal, there will be 1 wei PT less that is not being burned. If the withdrawal is executed 10000 times, there will be 10000 wei PT less that is not being burned. So, if someone attempts to claim the last 10000 wei PT, it might revert because there might be insufficient target tokens left. However, if the loss is limited to only 10000 wei PT, it is insignificant as 1 PT = 1 underlying asset on maturity.
I checked the POC, and it shows that the last user (Bob) attempted to call the collect
function, which is a function to claim yield. It does not appear to be related to the reported issue where the last user cannot exit. Could you please further elaborate on this?
Escalate
When the
Tranche.withdraw
function is rounded down, this means that for each withdrawal, there will be 1 wei PT less that is not being burned. If the withdrawal is executed 10000 times, there will be 10000 wei PT less that is not being burned. So, if someone attempts to claim the last 10000 wei PT, it might revert because there might be insufficient target tokens left. However, if the loss is limited to only 10000 wei PT, it is insignificant as 1 PT = 1 underlying asset on maturity.I checked the POC, and it shows that the last user (Bob) attempted to call the
collect
function, which is a function to claim yield. It does not appear to be related to the reported issue where the last user cannot exit. Could you please further elaborate on this?
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.
Escalate
When the
Tranche.withdraw
function is rounded down, this means that for each withdrawal, there will be 1 wei PT less that is not being burned. If the withdrawal is executed 10000 times, there will be 10000 wei PT less that is not being burned. So, if someone attempts to claim the last 10000 wei PT, it might revert because there might be insufficient target tokens left. However, if the loss is limited to only 10000 wei PT, it is insignificant as 1 PT = 1 underlying asset on maturity.I checked the POC, and it shows that the last user (Bob) attempted to call the
collect
function, which is a function to claim yield. It does not appear to be related to the reported issue where the last user cannot exit. Could you please further elaborate on this?
Napier is a typical interest market, there would be two types of investors: PT holders (interest seller) and YT holders (interest buyer), if the last quit user is a YT holder, he/she can only call collect()
to quit, then it would be stuck, as collect()
must quit with all or nothing
. This is the core point of this finding.
Napier Tranche is a typical interest market, there would be two types of investors: PT holders (interest seller) and YT holders (interest buyer), if the last quit user is a YT holder, he/she can only call
collect()
to quit, then it would be stuck, ascollect()
must quit withall or nothing
. This is the core point of this finding.
Basically YT holder would call collect()
and can exit with redeemWithYt
which requires the same amount of PT.
it would be stuck, as collect() must quit with all or nothing.
all or nothing
or "loss fund permanently" can be avoided as follow:
e.g.) Alice is the last user (100 YT) in her address A
she transfers some wei of YT to her another address B
. Then she calls collect()
function from address A
. she can get yield with insignificant loss.
Also, typically accrued yield is so small because it is not principal user deposited. e.g. 1 YT-cDAI (maturity 1 year and 1% annual rate) would accrue 0.01 DAI. The actual amount stuck could be smaller because the YT holder can collect the yield even before maturity.
Agree with sponsor @massun-onibakuchi, similar issue applies for #56, albeit the nature of rounding differs, but affected loss amounts is minute and core functionality of interested collection will not be permanently DoSed since there is a workaround.
Napier Tranche is a typical interest market, there would be two types of investors: PT holders (interest seller) and YT holders (interest buyer), if the last quit user is a YT holder, he/she can only call
collect()
to quit, then it would be stuck, ascollect()
must quit withall or nothing
. This is the core point of this finding.Basically YT holder would call
collect()
and can exit withredeemWithYt
which requires the same amount of PT.it would be stuck, as collect() must quit with all or nothing.
all or nothing
or "loss fund permanently" can be avoided as follow:e.g.) Alice is the last user (100 YT) in her
address A
she transfers some wei of YT to her anotheraddress B
. Then she callscollect()
function fromaddress A
. she can get yield with insignificant loss.Also, typically accrued yield is so small because it is not principal user deposited. e.g. 1 YT-cDAI (maturity 1 year and 1% annual rate) would accrue 0.01 DAI. The actual amount stuck could be smaller because the YT holder can collect the yield before maturity.
I have different idea, from the view of my self as a interest investor:
let's say current USD APY is 3%, and current 1-year YT-USD price is $0.01, and i expect that the APY would increase to 6%, then i would buy lots of YT-USD (such as 10M which is worth $100K at beginning) from market and hold YT only to earn 500% potential profit at maturity. But if i hold both PT and YT, i only get 5% profit.
I think interest speculation is the fancy part of Napier, if I only want to hold PT and YT together, why i don't hold the underlying asset such as cTokens
directly? With no additional fee charged by Napier and suffering no vulnerability risk at Napier end.
All in all, what i want to say is:
1) we can't make sure how users use the protocol, we can expect that both YT only holders (affected
), PT only holders (not affected
) and PT+YT holders (might affected while YT > PT
) would be existing.
2) although the ratio of YT/PT might be low, but the absolute YT tokens affected might still be high, as YT is the speculation trade target, users might hold large YT tokens only.
To sum up:
Your point is that if the bug WAS
known, then users have solution to decrease losses, but my point is that the issue could be easily triggered but can't be easily found, and once it's triggered before discovering, the LOSS is unconstrained
, might be low, might be very large .
P.S. the current cDAI APY is 32.05%, not 1%, the YT's value would be much larger than expected reference: https://v2-app.compound.finance/
This APY chart is more fair. https://defillama.com/yields/pool/cc110152-36c2-4e10-9c12-c5b4eb662143
Also, Some of users would deposit PT into Curve finance Tricrypto pool or Napier Pool (Napier pool is a metapool of the Tricrypto pool). Napier pool creates "dead shares", inspired by well-known Uniswap v2. Virtually that means there is a fraction of PT that can never be withdrawn. Given this background, the likelihood that users will actually be unable to withdraw is quite small.
Clearly the PoC assumes issued PT and YT are never deposited into a pool. It can be said that the PoC makes an assumption that greatly differ from how Napier and general Fixed-rate DeFi would be used. PT is a kind of zero-coupon bond, must be traded on a market and users buy or sell based on the market price and their prediction. Users can't speculate on interest if a market liquidity doesn't exist.
Here I want to say that some PTs are definitely permatnently locked in a pool, the last user would be actually a dead address that holds the dead shares.
To sum up: The finding overlooks how Napier and fixed interest rates market works. Considering the following two points, I think that the vulnerability is considered low.
address(1)
). Also, Some of users would deposit PT into Curve finance Tricrypto pool or Napier Pool (Napier pool is a metapool of the Tricrypto pool). Napier pool creates "dead shares", inspired by well-known Uniswap v2. Virtually that means there is a fraction of PT that can never be withdrawn. Given this background, the likelihood that users will actually be unable to withdraw is quite small.
Clearly the PoC assumes issued PT and YT are never deposited into a pool. It can be said that the PoC makes an assumption that greatly differ from how Napier and general Fixed-rate DeFi would be used. PT is a kind of zero-coupon bond, must be traded on a market and users buy or sell based on the market price and their prediction. Users can't speculate on interest if a market liquidity doesn't exist.
Here I want to say that some PTs are definitely permatnently locked in a pool, the last user would be actually a dead address that holds the dead shares.
There is no guarantee that deadshare's value always > Tranche.withdraw()
's cumulative round lost, it's very small too (1000wei for uniswap). However, based your additional information, I agree this issue is not high. But according sherlock's rule: Causes a loss of funds but requires certain external conditions or specific states
, it's still a Medium issue.
Severity will depend on how @Czar102 interprets the following rule:
The losses must exceed small, finite amount of funds, and any amount relevant based on the precision or significance of the loss.
From the discussion, I think this issue should remain a Medium.
Since the loss is highly constrained as there exists a trivial method to cap it (see https://github.com/sherlock-audit/2024-01-napier-judging/issues/16#issuecomment-1985628392), I'm planning to consider it a low/informational issue.
Result: Low Unique
Escalations have been resolved successfully!
Escalation status:
The Lead Senior Watson signed off on the fix.
KingNFT
high
The last user can't quit
Tranche
and loss fund permanentlySummary
After maturity of
Tranche
, all users are intended to quit and get back their fund and profit. But actually the last user might fail to quit and permanently loss fund due to rounding error in theTranche.withdraw()
. With the consideration there would be manyTranche
instances based on differentadapters
and differentmaturity
, many users would suffer this risk, and this vulnerability would be easily triggered to cause users fund loss.Vulnerability Detail
The issue arises on L338 of
withdraw()
function and L676 \~ L680 of_computePrincipalTokenRedeemed()
,principalAmount
is rounded down in favor of users rather than the protocol, which makes no enough principal token burned . This would cause the protocol have no enoughtarget
token to burn for last user.The coded PoC shows more specific procedures how this would occur:
And the logs:
Impact
The last user would fail to quit and permanently loss fund
Code Snippet
https://github.com/sherlock-audit/2024-01-napier/blob/main/napier-v1/src/Tranche.sol#L668
Tool used
Manual Review
Recommendation