Open c4-bot-9 opened 7 months ago
bytes032 marked the issue as sufficient quality report
bytes032 marked the issue as duplicate of #196
alex-ppg marked the issue as satisfactory
The submission does not adequately focus on the Absorber vulnerability that #200 highlights, rendering it a duplicate of only #196.
This submission details the vulnerability with a greater impact than #196, and has thus been selected as primary.
alex-ppg marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/core/gate.cairo#L191-L223 https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/core/absorber.cairo#L667-L705
Vulnerability details
Impact
Both
absorber
andgate
use the same mitigation for ERC4626 first depositor front-running vulnerability, but current implementation is not sufficient. By abusing the flaw, even though malicious attacker can't benefit from the mitigation, he can cause other normal users lose asset.Proof of Concept
Because of
absorber
andgate
use the same mitigation, I will takegate
as example.Suppose the yang's decimals is 18
When sentinel.add_yang is called to add yang to shrine,
initial_yang_amt
is passed to shrine.add_yang as mitigation to the inflat issue. And initial_yang_amt is set as INITIAL_DEPOSIT_AMT which isconst INITIAL_DEPOSIT_AMT: u128 = 1000;
In sentinel.cairo#L204-L206,
yang_erc20.transfer_from
is called to transfer 1000 wei yang_erc from caller to gateAnd then the code flow will fall into shrine.add_yang, when the function is called,
initial_yang_amt
is still1000
In
shrine.add_yang
, theyang_total
will be set to1000
in shrine.cairo#L591So when the admin(which is the first depositor) calls sentinel.add_yang, he will transfer 1000 wei yang_asset and he will recevie 1000 yang_amt yang. After that, when the second user calls abbot.open_trove, gate.convert_to_yang_helper will calculate his
yang_amt
by gate.cairo#L220And gate.get_total_assets_helper is using
asset.balance_of
.So to sum up, in current implementation: 1) the first depositor(which is also the admin) will depost 1000 wei ERC, and will receive 1000 yang_amt 2) the second depostor will get his yang_amt based of (asset_amt.into() * total_yang) / get_total_assets_helper(asset).into()
Suppose in a case that the asset is DAI(18 decimals) 1) when yang_dai is created, the admin calls add_yang to add DAI into the protocol, and he will transfer 1000 wei DAI to
gate
, and will receive 1000 yang_amt 2) a malicious tranfers (100000 1e18 - 1000 wei) DAI(which is worth 100000 USD) togate
3) Alice a normal user deposit 199 1e18 DAI(which is worth 199 USD) by calling abbot.open_trove, based on gate.cairo#L220, Alice will get 199 1e18 1000 / (100000 1e18) = 1 wei 4) If Alice wants to close her trove, gate.convert_to_assets will be used to calculate the asset amount, according to gate.cairo#L191-L204, gate.cairo#L202 will be used: `((yang_amt get_total_assets_helper(asset).into()) / total_yang).val, because alice has 1 wei yang_amout, and
get_total_assets_helper()is __(100000 + 199) * 1e18__,
total_yang` will be 1001 So Alice will get 1 (100000 1e18 + 199*1e18) / (1000 + 1) = 100.0989010989011 1e18, which is 100 USD. And the remaining 991e18 DAI will be in the gate.In the above case, there will be two issue:
Tools Used
VIM
Recommended Mitigation Steps
In gate.get_total_assets_helper, don't use balance_of to calculate the amount, instead, define a new variables and record the deposited asset amount by the variables
Assessed type
ERC4626