Platform: Code4rena
Start Date: 25/01/2022
Pot Size: $50,000 USDT
Total HM: 17
Participants: 39
Period: 3 days
Judge: LSDan
Total Solo HM: 9
Id: 79
League: ETH
Rank: 3/39
Findings: 4
Award: $2,598.45
🌟 Selected for report: 3
🚀 Solo Findings: 1
defsec
The call to transferFrom is done without checking the results. For certain ERC20 tokens, if insufficient tokens are present, no revert occurs but a result of “false” is returned. It’s important to check this. If you don’t, in this concrete case, some airdrop eligible participants could be left without their tokens. It is also a best practice to check this.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L133
Code Review
Check the result of transferFrom and transfer. Although if this is done, the contracts will not be compatible with non standard ERC20 tokens like USDT. For that reason, I would rather recommend making use of SafeERC20 library: safeTransfer and safeTransferFrom.
#0 - cryptofish7
2022-01-31T00:51:31Z
Duplicate of #232
#1 - dmvt
2022-02-22T19:26:18Z
duplicate of #198
🌟 Selected for report: defsec
1556.9237 USDT - $1,556.92
defsec
The TraderJOE protocol do not appear to support rebasing/deflationary/inflationary tokens whose balance changes during transfers or over time. The necessary checks include at least verifying the amount of tokens transferred to contracts before and after the actual transfer to infer any fees/interest.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeStaking.sol#L133
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L132
Code Review
#0 - cryptofish7
2022-02-01T15:07:02Z
It won’t revert as long as token’s balance doesn’t decrease (this never happens)
#1 - dmvt
2022-02-23T12:47:09Z
It is possible for someone to unknowingly use this functionality with a token that rebases down during the launch event. Just because you don't support a token type, doesn't mean that the design doesn't exist. This is a medium risk, not a low risk, because the is the potential for external interaction to cause a loss of funds.
🌟 Selected for report: defsec
518.9746 USDT - $518.97
defsec
During the code review, It has been observed that the token can be same as WAVAX. The initialize function should not allow if token is equals to wavax. That would affect all asset management.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L219 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L411
Code Review
On the Launchevent, token should not be equal to wavax.
#0 - cryptofish7
2022-01-31T23:22:43Z
defsec
All contract initializers were missing access controls, allowing any user to initialize the contract. By front-running the contract deployers to initialize the contract, the incorrect parameters may be supplied, leaving the contract needing to be redeployed.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeStaking.sol#L57
Manual Code Review
While the code that can be run in contract constructors is limited, setting the owner in the contract's constructor to the msg.sender
and adding the onlyOwner
modifier to all initializers would be a sufficient level of access control.
#0 - cryptofish7
2022-01-30T23:37:05Z
Duplicate of #8
defsec
"RocketJoeToken.sol", "RocketJoeFactory" and "RocketJoeStaking" inherit OpenZeppelin's Ownable contract which enables the onlyOwner role to transfer ownership to another address. It's possible that the onlyOwner role mistakenly transfers ownership to the wrong address, resulting in a loss of the onlyOwner role. The current ownership transfer process involves the current owner calling Unlock.transferOwnership(). This function checks the new owner is not the zero address and proceeds to write the new owner's address into the owner's state variable. If the nominated EOA account is not a valid account, it is entirely possible the owner may accidentally transfer ownership to an uncontrolled account, breaking all functions with the onlyOwner() modifier. Lack of two-step procedure for critical operations leaves them error-prone if the address is incorrect, the new address will take on the functionality of the new role immediately
for Ex : -Alice deploys a new version of the whitehack group address. When she invokes the whitehack group address setter to replace the address, she accidentally enters the wrong address. The new address now has access to the role immediately and is too late to revert
None
Implement zero address check and Consider implementing a two step process where the owner nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
#0 - cryptofish7
2022-01-31T11:13:56Z
Duplicate of #10
🌟 Selected for report: cccz
Also found by: csanuragjain, defsec, robee
defsec
Some tokens (like USDT L199) do not work when changing the allowance from an existing non-zero allowance value. They must first be approved by zero and then the actual allowance must be approved.
IERC20(token).approve(address(operator), 0); IERC20(token).approve(address(operator), amount);
https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L408
None
Approve with a zero amount first before setting the actual amount.
#0 - cryptofish7
2022-01-31T14:31:50Z
Duplicate of #22
defsec
The following contract functions performs an ERC20.approve() call but does not check the success return value. Some tokens do not revert if the approval failed but return false instead.
https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L408
None
Its recommend to using OpenZeppelin’s SafeERC20 versions with the safeApprove function that handles the return value check as well as non-standard-compliant tokens.
#0 - cryptofish7
2022-01-31T14:32:11Z
Duplicate of #154
🌟 Selected for report: sirhashalot
Also found by: 0v3rf10w, 0x1f8b, Dravee, UncleGrandpa925, cccz, defsec, gzeon
defsec
Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L158 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L171 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L178 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L185 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L192 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L218
Code Review
Consider adding zero-address checks in the discussed constructors: require(newAddr != address(0));.
#0 - cryptofish7
2022-01-31T00:52:27Z
Duplicate of #263
defsec
!= 0
is a cheaper operation compared to > 0
, when dealing with uint.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L338 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L355 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L370 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L390 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L486 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L498 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeStaking.sol#L101
Code Review
Use "!=0" instead of ">0" for the gas optimization.
#0 - cryptofish7
2022-01-30T23:49:25Z
Duplicate of #240
🌟 Selected for report: WatchPug
Also found by: Czar102, Dravee, Jujic, Meta0xNull, byterocket, defsec, p4st13r4, pauliax, robee, sirhashalot
defsec
Shortening revert strings to fit in 32 bytes will decrease deploy time gas and will decrease runtime gas when the revert condition has been met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Revert strings > 32 bytes are here:
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L311
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/LaunchEvent.sol#L621
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeFactory.sol#L224
Manual Review
Shorten the revert strings to fit in 32 bytes. That will affect gas optimization.
#0 - cryptofish7
2022-01-31T11:51:34Z
Duplicate of #242
🌟 Selected for report: WatchPug
Also found by: Dravee, Jujic, Rhynorater, TomFrenchBlockchain, defsec, hyh, ye0lde
defsec
For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.
https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeStaking.sol#L167 https://github.com/code-423n4/2022-01-trader-joe/blob/main/contracts/RocketJoeStaking.sol#L87
None
Consider applying unchecked arithmetic where overflow/underflow is not possible.
#0 - cryptofish7
2022-01-31T00:06:03Z
Duplicate of #233
17.9007 USDT - $17.90
defsec
In the JoeStaking contract, the amount check should be placed on the contract. IF the amount is more than transfer operations should be completed.
function withdraw(uint256 _amount) external { UserInfo storage user = userInfo[msg.sender]; require( user.amount >= _amount, "RocketJoeStaking: withdraw amount exceeds balance" ); updatePool(); uint256 pending = (user.amount * accRJoePerShare) / PRECISION - user.rewardDebt; user.amount = user.amount - _amount; user.rewardDebt = (user.amount * accRJoePerShare) / PRECISION; _safeRJoeTransfer(msg.sender, pending); joe.safeTransfer(address(msg.sender), _amount); emit Withdraw(msg.sender, _amount); }
Code Review
Consider to add the following check.
function withdraw(uint256 _amount) external { UserInfo storage user = userInfo[msg.sender]; require( user.amount >= _amount, "RocketJoeStaking: withdraw amount exceeds balance" ); updatePool(); uint256 pending = (user.amount * accRJoePerShare) / PRECISION - user.rewardDebt; user.amount = user.amount - _amount; user.rewardDebt = (user.amount * accRJoePerShare) / PRECISION; if(pending != 0) { _safeRJoeTransfer(msg.sender, pending); } if(_amount != 0){ joe.safeTransfer(address(msg.sender), _amount); } emit Withdraw(msg.sender, _amount); }
#0 - cryptofish7
2022-01-31T20:16:49Z
Disagree with severity, as it won't revert, but there is gas optimisation.
#1 - dmvt
2022-02-21T13:08:26Z
Agree with sponsor, this is a gas optimization.
defsec
Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.
The advantages of versions 0.8.* over <0.8.0 are:
All Contracts
None
Consider to upgrade pragma to at least 0.8.4.
#0 - cryptofish7
2022-02-11T00:33:55Z
Duplicate of #181