Platform: Code4rena
Start Date: 21/08/2023
Pot Size: $125,000 USDC
Total HM: 26
Participants: 189
Period: 16 days
Judge: GalloDaSballo
Total Solo HM: 3
Id: 278
League: ETH
Rank: 83/189
Findings: 3
Award: $98.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xrafaelnicolau
Also found by: 0x111, 0xCiphky, 0xMosh, 0xWaitress, 0xc0ffEE, 0xkazim, 0xnev, 0xvj, ABAIKUNANBAEV, Aymen0909, Baki, ElCid, HChang26, HHK, Inspex, Jorgect, Kow, Krace, KrisApostolov, LFGSecurity, MiniGlome, Nyx, QiuhaoLi, RED-LOTUS-REACH, Talfao, Toshii, Vagner, Viktor_Cortess, Yanchuan, _eperezok, asui, atrixs6, bart1e, bin2chen, carrotsmuggler, chaduke, chainsnake, deadrxsezzz, degensec, dethera, dimulski, dirk_y, ether_sky, gizzy, glcanvas, grearlake, gumgumzum, halden, hals, kodyvim, koo, ladboy233, lanrebayode77, max10afternoon, minhtrng, mussucal, nobody2018, peakbolt, pontifex, qbs, ravikiranweb3, rvierdiiev, said, tapir, ubermensch, volodya, wintermute, yashar, zaevlad, zzebra83
0.0734 USDC - $0.07
Incorrect accounting of WETH leads to incorrect balances after withdrawal.
When a user delegates WETH the amount delegated is added to the variable totalWethDelegated:
function addToDelegate( uint256 _amount, uint256 _fee ) external returns (uint256) { _whenNotPaused(); // fee less than 100% _validate(_fee < 100e8, 8); // amount greater than 0.01 WETH _validate(_amount > 1e16, 4); // fee greater than 1% _validate(_fee >= 1e8, 8); IERC20WithBurn(weth).safeTransferFrom(msg.sender, address(this), _amount); Delegate memory delegatePosition = Delegate({ amount: _amount, fee: _fee, owner: msg.sender, activeCollateral: 0 }); delegates.push(delegatePosition); // add amount to total weth delegated **totalWethDelegated += _amount;** emit LogAddToDelegate(_amount, _fee, delegates.length - 1); return (delegates.length - 1);
}
However, when the user withdraws the delegated funds, the withdrawn amount is not subtracted from totalWethDelegated:
unction withdraw( uint256 delegateId ) external returns (uint256 amountWithdrawn) { _whenNotPaused(); _validate(delegateId < delegates.length, 14); Delegate storage delegate = delegates[delegateId]; _validate(delegate.owner == msg.sender, 9); amountWithdrawn = delegate.amount - delegate.activeCollateral; _validate(amountWithdrawn > 0, 15); delegate.amount = delegate.activeCollateral; IERC20WithBurn(weth).safeTransfer(msg.sender, amountWithdrawn); emit LogDelegateWithdraw(delegateId, amountWithdrawn);
}
Example of an attack:
Bob knows that the Core contract holds a balance of 10 WETH. He takes a flash loan of 20 WETH, adds it to the delegate, then withdraws his 20 WETH. Now, the contract holds a balance of 10 WETH, but totalWethDelegated is still at 20 WETH.
If someone then calls the sync() function, it will result in an underflow because balance < totalWethDelegated:
function sync() external { for (uint256 i = 1; i < reserveAsset.length; i++) { uint256 balance = IERC20WithBurn(reserveAsset[i].tokenAddress).balanceOf( address(this) ); if (weth == reserveAsset[i].tokenAddress) { balance = balance - totalWethDelegated; //underflow } reserveAsset[i].tokenBalance = balance; } emit LogSync();
}
vs
To mitigate this vulnerability, subtract the withdrawn amount from totalWethDelegated in the withdraw function, like so:
totalWethDelegated -= amountWithdrawn; to the withdraw function.
Math
#0 - c4-pre-sort
2023-09-08T13:27:59Z
bytes032 marked the issue as duplicate of #2186
#1 - c4-judge
2023-10-20T17:54:50Z
GalloDaSballo marked the issue as satisfactory
#2 - c4-judge
2023-10-21T07:38:54Z
GalloDaSballo changed the severity to 3 (High Risk)
#3 - c4-judge
2023-10-21T07:45:01Z
GalloDaSballo marked the issue as partial-50
🌟 Selected for report: juancito
Also found by: 0x3b, 0xmuxyz, 0xnev, ArmedGoose, Bauchibred, KrisApostolov, RED-LOTUS-REACH, Viktor_Cortess, ciphermarco, ginlee, ladboy233, mitko1111, nemveer
90.6302 USDC - $90.63
Executing the addLiquidity function from UniswapV2 with zero-valued amountAMin
and amountBMin
could result in a loss of funds due to MEV bot attacks.
amountAMin
and amountBMin
are the minimum amounts of tokenA
and tokenB
you are willing to add (acts as slippage protection).
286: (, , uint256 lp) = IUniswapV2Router(addresses.ammRouter).addLiquidity( addresses.tokenA, addresses.tokenB, tokenAAmountOut, amountB / 2, 0,//@Mid zero 0, address(this), block.timestamp + 10 );
Setting amountAMin
and amountBMin
to zero removes a layer of protection against slippage, potentially exposing the user to risks like MEV (Miner Extractable Value) attacks, where a miner could manipulate the transaction in a way that is disadvantageous to the user.
vs
Consider adding a function that calculates minimal amounts.
MEV
#0 - c4-pre-sort
2023-09-07T12:54:36Z
bytes032 marked the issue as duplicate of #1259
#1 - c4-pre-sort
2023-09-11T07:51:37Z
bytes032 marked the issue as sufficient quality report
#2 - c4-pre-sort
2023-09-11T07:53:18Z
bytes032 marked the issue as duplicate of #1032
#3 - c4-judge
2023-10-15T19:21:18Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: Vagner
Also found by: 0Kage, 0xCiphky, 0xnev, ABAIKUNANBAEV, Aymen0909, Evo, KmanOfficial, MohammedRizwan, T1MOH, Viktor_Cortess, Yanchuan, ak1, alexzoid, bin2chen, codegpt, hals, ladboy233, mrudenko, nemveer, oakcobalt, peakbolt, pep7siup, qbs, said, savi0ur, tapir, wintermute, zaevlad, zzebra83
7.8372 USDC - $7.84
Two identical functions exist in the UniV2LiquidityAMO and UniV3LiquidityAMO contracts, but only function from UniV3LiquidityAMO synchronizes balances of the Core contract after sending tokens to it.
Function _sendTokensToRdpxV2Core from UniV2LiquidityAMO :
function _sendTokensToRdpxV2Core() internal { uint256 tokenABalance = IERC20WithBurn(addresses.tokenA).balanceOf( address(this) ); uint256 tokenBBalance = IERC20WithBurn(addresses.tokenB).balanceOf( address(this) ); // transfer token A and B from this contract to the rdpxV2Core IERC20WithBurn(addresses.tokenA).safeTransfer( addresses.rdpxV2Core, tokenABalance ); IERC20WithBurn(addresses.tokenB).safeTransfer( addresses.rdpxV2Core, tokenBBalance ); emit LogAssetsTransfered(msg.sender, tokenABalance, tokenBBalance);
}
Function _sendTokensToRdpxV2Core from UniV3LiquidityAMO :
function _sendTokensToRdpxV2Core(address tokenA, address tokenB) internal { uint256 tokenABalance = IERC20WithBurn(tokenA).balanceOf(address(this)); uint256 tokenBBalance = IERC20WithBurn(tokenB).balanceOf(address(this)); // transfer token A and B from this contract to the rdpxV2Core IERC20WithBurn(tokenA).safeTransfer(rdpxV2Core, tokenABalance); IERC20WithBurn(tokenB).safeTransfer(rdpxV2Core, tokenBBalance); // sync token balances IRdpxV2Core(rdpxV2Core).sync(); emit LogAssetsTransfered(tokenABalance, tokenBBalance, tokenA, tokenB);
}
The lack of the call of this function causes discrepancies between the reserve balance of reserve tokens and totalWETHdelegated in the Core contract.
vs
Adding IRdpxV2Core(rdpxV2Core).sync(); to the _sendTokensToRdpxV2Core in the _sendTokensToRdpxV2Core contract.
Context
#0 - c4-pre-sort
2023-09-09T03:48:02Z
bytes032 marked the issue as duplicate of #798
#1 - c4-pre-sort
2023-09-09T04:09:25Z
bytes032 marked the issue as duplicate of #269
#2 - c4-pre-sort
2023-09-11T11:58:34Z
bytes032 marked the issue as sufficient quality report
#3 - c4-judge
2023-10-15T18:12:03Z
GalloDaSballo marked the issue as satisfactory