Closed c4-bot-3 closed 5 months ago
raymondfam marked the issue as sufficient quality report
raymondfam marked the issue as duplicate of #57
See #57.
hansfriese marked the issue as unsatisfactory: Invalid
It seems duplicated but actually it's not, The issue #57 has different root cause and different impact, the root cause of #57 issue that if (dethTotalNew <= dethTotal) return;
while mine #242 is different which is if (dethYieldRate == 0) return , The warden at #57 issue is focusing on and describing the values that are related to dethYieldRate and dethCollateralRate, in my case I am mentioning specifically the inconsistence of updateYield that will affect _ethConversion in the withdraw as impact.
_ethConversion would return wrong amount in withdraw
which will be unfair for other users, since in _ethConversion it will return the same amount instead of jumping to the else block applying the correct accounting. In other words, the loss (negative yield) won't be proportionally incurred by all users.
and then the withdrawal will get the whole amount instead of applying the right accounting for it amount.mul(dethTotalNew).divU88(dethTotal).
on the other hand the warder's issue #57 is presenting a future impact:
future bridges which do not use bridge credit system would allow deth to be exchanged where 1 deth = 1 eth if system returns to yeild in zero or positive state
My issue is presenting an impact which is not related to the protocol design as the sponsor explained in #57 issue. In fact, my issue #242 has a practical impact on withdrawals and can be prevented (not necessarily is important to the sponsors). I appreciate the judge reviewing this issue again, this is my opinion anyway.
Vault.dethTotal is not set until the very end precisely to avoid yield being lost. For example, if a user (or an attacker) called updateYield repeatedly when only small amounts of yield had been generated, the resulting zethYieldRate could round down to 0 but Vault.dethTotal would slowly creep up and users would never receive yield.
The proposed mitigation here would actually cause the problems mentioned
ditto-eth (sponsor) disputed
@evokid I want to hear your reply.
@ditto-eth Thank you for your feedback and explaining the case. I totally agree with the sponsor, after digging deep in the issue I found my mitigation is not strong, and will cause other problems. However, the issue still stand, and I would suggest a different mitigation:
I believe this mitigation fix the issue without exposing the protocol to the mentioned risks by the sponsor. much appreciation for the judge @hansfriese.
@evokid Can you provide an example with ethConversion()
? vault.dethTotal can only be set during positive yield accrual, otherwise when users attempt to withdraw() then they will take away extra yield that hasn't been realized by the system. In the rare case of negative yield it seems safer to default to lower withdrawal amounts while the system recovers
@ditto-eth Thank you for feedback. Sure, we should assume that Vault.uptodateDethTotal should act always as Vault.dethTotal in the protocol, except it will be always updated in updateYield method. Now In the rare case of negative yield:
amount.mul(dethTotalNew).divU88(x)
amount.mul(dethTotalNew).divU88(y)
This is safer and goes with your measure to default to lower amounts on withdrawal.
I agree with the sponsor. Furthermore, I've noticed LibVault.sol
is out of scope.
hansfriese marked the issue as not a duplicate
hansfriese marked the issue as unsatisfactory: Out of scope
Hi @hansfriese, contracts/facets/BridgeRouterFacet.sol is in scope which include deposit() that calls maybeUpdateYield, also BridgeRouterFacet.sol contains _ethConversion as well, The sponsor asked for an example and I provided it as well, for the mitigation that he is looking for. thank you :)
The sponsor will mitigate if it's valid.
According to the contest info, LibVault.sol
should be treated as out of scope although it's used by other in scope contracts.
I respect your decision, thank you for your time and looking into it.
Lines of code
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibVault.sol#L99
Vulnerability details
Impact
updateYield
is not always updated (inaccurate).TAPP.ethEscrowed
is not up to date.Vault.dethTotal
is not up to date,_ethConversion
would return wrong amount inwithdraw
.Proof of Concept
if (dethYieldRate == 0) return this return is intentional, since we don't have
dethYieldRate
, no need to updateVault.dethYieldRate
. However we have a case that could causeVault.dethTotal
not to be up to date.Assuming updateYield gets called, if there is no generated yield from dethTotalNew then a return will be done at if (dethTotalNew <= dethTotal) return; since dethTotalNew will equal dethTotal, which makes sense.
Lets assume there a generated yield and the code continue to execute until it gets at dethYieldRate = yield.divU80(dethCollateral);. Now we have
dethYieldRate
value, since we have yield generated and existingdethCollateral
amount comes from bid and short orders. so what's the case?The Case
if
dethYieldRate
value resulted as zero and a return after, then we could have an issue. Remember we said we have already generated yield? yes that's the issue there, this amount of generated yield should be updated intoVault.dethTotal
all the time whenever we have new yield. but since we had a return at if (dethYieldRate == 0) return no update is being done toVault.dethTotal
.this case also apply the same scenario on
TAPP.ethEscrowed
, clearly not being updated but only after the return which is unreachable.is that possible that
dethYieldRate
== zero and we still have yield?Yes :) please check POC.
The last impact which is important as well is
_ethConversion
method,_ethConversion
is used in withdraw method, it's responsible to return the right amount for the withdrawal. However the impact is when s.vault[vault].dethTotal is used in the comparison if (dethTotalNew >= dethTotal) to determine the amount that will be returned to withdrawal. IfVault.dethTotal
is not correctly up to date inupdateYield
then we will have an issue in the returned amount since it is possible that dethTotalNew > dethTotal if dethTotal didn't get updated in last updateYield, and then the withdrawal will get the whole amount instead of applying the right accounting for it amount.mul(dethTotalNew).divU88(dethTotal).POC CODE: To run the poc code please add the code in a file with updateYieldIssue.t.sol under
test\updateYieldIssue.t.sol
//OUTPUT
Tools Used
Manual Review
Recommended Mitigation Steps
Always update
TAPP.ethEscrowed
andVault.dethTotal
in updateYield method.Assessed type
Other