Platform: Code4rena
Start Date: 14/10/2022
Pot Size: $100,000 USDC
Total HM: 12
Participants: 75
Period: 9 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 171
League: ETH
Rank: 54/75
Findings: 2
Award: $0.43
🌟 Selected for report: 1
🚀 Solo Findings: 0
0.4249 USDC - $0.42
https://github.com/code-423n4/2022-10-traderjoe/blob/79f25d48b907f9d0379dd803fc2abc9c5f57db93/src/LBToken.sol#L182 https://github.com/code-423n4/2022-10-traderjoe/blob/79f25d48b907f9d0379dd803fc2abc9c5f57db93/src/LBToken.sol#L187 https://github.com/code-423n4/2022-10-traderjoe/blob/79f25d48b907f9d0379dd803fc2abc9c5f57db93/src/LBToken.sol#L189-L192
Using temporary variables to update balances is a dangerous construction that has led to several hacks in the past. Here, we can see that _toBalance
can overwrite _fromBalance
:
File: LBToken.sol 176: function _transfer( 177: address _from, 178: address _to, 179: uint256 _id, 180: uint256 _amount 181: ) internal virtual { 182: uint256 _fromBalance = _balances[_id][_from]; ... 187: uint256 _toBalance = _balances[_id][_to]; 188: 189: unchecked { 190: _balances[_id][_from] = _fromBalance - _amount; 191: _balances[_id][_to] = _toBalance + _amount; //@audit : if _from == _to : rekt 192: } .. 196: }
Furthermore, the safeTransferFrom
function has the checkApproval
modifier which passes without any limit if _owner == _spender
:
File: LBToken.sol 32: modifier checkApproval(address _from, address _spender) { 33: if (!_isApprovedForAll(_from, _spender)) revert LBToken__SpenderNotApproved(_from, _spender); 34: _; 35: } ... 131: function safeTransferFrom( ... 136: ) public virtual override checkAddresses(_from, _to) checkApproval(_from, msg.sender) { ... 269: function _isApprovedForAll(address _owner, address _spender) internal view virtual returns (bool) { 270: return _owner == _spender || _spenderApprovals[_owner][_spender]; 271: }
Add the following test to LBToken.t.sol
(run it with forge test --match-path test/LBToken.t.sol --match-test testSafeTransferFromOneself -vvvv
):
function testSafeTransferFromOneself() public { uint256 amountIn = 1e18; (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256 initialBalance = pair.balanceOf(DEV, _ids[0]); assertEq(initialBalance, 333333333333333333); // using hardcoded value to ease understanding pair.safeTransferFrom(DEV, DEV, _ids[0], initialBalance); //transfering to oneself uint256 rektBalance1 = pair.balanceOf(DEV, _ids[0]); //computing new balance assertEq(rektBalance1, 2 * initialBalance); // the new balance is twice the initial one assertEq(rektBalance1, 666666666666666666); // using hardcoded value to ease understanding }
As we can see here, this test checks that transfering all your funds to yourself doubles your balance, and it's passing. This can be repeated again and again to increase your balance.
_from != _to
because that shouldn't be useful anywayFile: LBToken.sol 189: unchecked { 190: _balances[_id][_from] -= _amount; 191: _balances[_id][_to] += _amount; 192: }
#0 - trust1995
2022-10-23T21:19:10Z
Dup of #422
#1 - GalloDaSballo
2022-10-26T16:33:41Z
Example of short and sweet high quality report
#2 - GalloDaSballo
2022-10-26T16:34:13Z
Making this main although top 3 reports are equivalent (example of "Best" being subjective)
#3 - GalloDaSballo
2022-10-26T16:34:56Z
Choosing this over the other two because it's illustrative and brief at the same time, and offers a specific test case for the Sponsor to test mitigation against
#4 - JustDravee
2022-10-27T10:06:31Z
Thanks for the honor and encouraging words! I appreciate it! But oh man, 16 dups 🤣
#5 - GalloDaSballo
2022-11-08T22:09:49Z
The Warden has shown how, due to the improper usage of a supporting temporary variable, balance duplication can be achieved.
Mitigation will require ensuring that the intended variable is changed in storage, and the code offered by the warden should help produce a test case to compare the fix against.
Because the finding pertains to duplication of balances, causing a loss for users, I agree with High Severity
#6 - c4-judge
2022-11-08T22:09:56Z
GalloDaSballo marked the issue as selected for report
#7 - c4-judge
2022-11-14T13:56:50Z
GalloDaSballo marked the issue as primary issue
#8 - Simon-Busch
2022-12-05T06:27:07Z
Marked this issue as Satisfactory as requested by @GalloDaSballo
#9 - Simon-Busch
2022-12-05T06:37:39Z
Revert, wrong action.
🌟 Selected for report: 0xSmartContract
Also found by: Aymen0909, Dravee, Josiah, M4TZ1P, Mukund, Nyx, SooYa, catchup, cccz, chaduke, csanuragjain, djxploit, hansfriese, ladboy233, leosathya, pashov, rvierdiiev, sorrynotsorry, supernova, vv7, wagmi, zzykxx
0.006 USDC - $0.01
Flashloan fees are unbounded and could even be above 100% as uint _flashLoanFee
is of type uint256
:
File: LBFactory.sol 474: function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { 475: uint256 _oldFlashLoanFee = flashLoanFee; 476: 477: if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); 478: 479: flashLoanFee = _flashLoanFee; 480: emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); 481: }
This leads to a trust issue / rug vector / potential misuse of this functionality.
Edit LBFactory.t.sol#testForSettingFlashloanFee like below and run it with forge test --match-test testForSettingFlashloanFee
. It will pass, meaning any value is OK:
File: LBFactory.t.sol - 394: function testForSettingFlashloanFee() public { + 394: function testForSettingFlashloanFee(uint256 flashFee) public { - 395: uint256 flashFee = 7e14; 396: factory.setFlashLoanFee(flashFee); 397: assertEq(factory.flashLoanFee(), flashFee); 398: vm.expectRevert(abi.encodeWithSelector(LBFactory__SameFlashLoanFee.selector, flashFee)); 399: factory.setFlashLoanFee(flashFee); 400: }
Consider adding an upper bound (maybe even a lower bound) or using a smaller unsigned_integer type for _flashLoanFee
#0 - GalloDaSballo
2022-10-27T21:15:40Z
#1 - c4-judge
2022-11-23T18:38:11Z
GalloDaSballo marked the issue as not a duplicate
#2 - c4-judge
2022-11-23T18:38:57Z
GalloDaSballo marked the issue as duplicate of #139
#3 - Simon-Busch
2022-12-05T06:33:07Z
Marked this issue as Satisfactory as requested by @GalloDaSballo