Platform: Code4rena
Start Date: 26/01/2023
Pot Size: $60,500 USDC
Total HM: 7
Participants: 31
Period: 6 days
Judge: berndartmueller
Total Solo HM: 3
Id: 207
League: ETH
Rank: 24/31
Findings: 1
Award: $45.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: NoamYakov
Also found by: 0xSmartContract, 0xackermann, Aymen0909, Deivitto, Diana, IllIllI, RaymondFam, ReyAdmirado, Rolezn, antonttc, arialblack14, c3phas, cryptostellar5, matrix_0wl, nadin, oyc_109
45.4256 USDC - $45.43
Position.update
logicThe current implementation in update
introduces more branches than necessary. Also some variables like tokensOwed
and sizeNext
are not always used and can only be declared in a certain scope (block).
Updated (optimized) version:
if (_positionInfo.size > 0) { uint256 tokensOwed = newTokensOwed(_positionInfo, rewardPerPosition); positionInfo.tokensOwed = _positionInfo.tokensOwed + tokensOwed; } if (sizeDelta == 0) { if (_positionInfo.size == 0) revert NoPositionError(); // don't need to assign sizeNext } else { positionInfo.size = PositionMath.addDelta(_positionInfo.size, sizeDelta); } positionInfo.rewardPerPositionPaid = rewardPerPosition;
gas saving:
testAddPosition() (gas: 406462)
⇒ 406080
testAddPositionEmpty() (gas: 330006)
⇒ 329767
deposit
In Lendgine.deposit()
: there’s a cached _totalPositionSize
variable, so in this check
if (totalLiquiditySupplied == 0 && totalPositionSize > 0) revert CompleteUtilizationError();
it would be cheaper to use the cached variable.
gas saving
deposit
: 100 from additional SLOAD. (from forge gas report: avg 128547
⇒ 128473
)Lendgine.withdraw
In the withdraw function, these 2 lines are not necessary:
Position.Info memory positionInfo = positions[msg.sender]; // SLOAD // ... if (size > positionInfo.size) revert InsufficientPositionError();
Because in position.update
it will revert anyway if size > existing size.
This additional copy to memory is actually 3 SLOAD from storage, removing these 3 lines can reduce the gas cost dramatically
Gas saving
withdraw
: avg: 72877 ⇒ 70405lendgine
address on the fly in LiquidityMananger.removeLiquidity
In LiquidityManager.removeLiquidity
, it uses the library to calculate lendgine address like this
address lendgine = LendgineAddress.computeAddress( factory, params.token0, params.token1, params.token0Exp, params.token1Exp, params.upperBound );
But this is not actually necessary because all the “state” variable is stored under a mapping positions[msg.sender][lendgine]
, so even if the user put in a fake lendgine
address, it won’t affect the actual accounting of real pools, and the malicious won’t be able to steal liquidity from any other pool.
This current code structure is only needed for addLiquidity
where you have a callback (pairMintCallback) invoked, and you need those parameters to check the msg.sender is legit. In removeLiquidity
, there’s no such need, and it would be easier to just pass in lendgine.
Optimization:
Let the function pass in lendgine
directly instead of calculating with 5 parameters (params.token0, params.token1, params.token0Exp, params.token1Exp, params.upperBound)
#0 - c4-judge
2023-02-16T11:19:55Z
berndartmueller marked the issue as grade-b