Platform: Code4rena
Start Date: 08/03/2023
Pot Size: $60,500 USDC
Total HM: 2
Participants: 123
Period: 7 days
Judge: hansfriese
Id: 220
League: ETH
Rank: 65/123
Findings: 2
Award: $48.97
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0x6980, 0xAgro, 0xSolus, 0xhacksmithh, 0xkazim, ABA, BPZ, BowTiedOriole, ChainReview, DadeKuma, DeFiHackLabs, Deathstore, DevABDee, Diana, Dravee, Dug, Englave, Go-Langer, Haipls, IceBear, Inspex, Jeiwan, Kek, Kresh, Madalad, MatricksDeCoder, MyFDsYours, RaymondFam, Rolezn, SAAJ, Sathish9098, Taloner, Udsen, Viktor_Cortess, atharvasama, ayden, brgltd, btk, carlitox477, catellatech, chaduke, codeislight, deadrxsezzz, descharre, erictee, fatherOfBlocks, favelanky, glcanvas, handsomegiraffe, jasonxiale, jekapi, joestakey, lemonr, luxartvinsec, martin, matrix_0wl, minhquanym, mrpathfindr, nadin, oyc_109, parsely, peanuts, pfedprog, rbserver, rokso, saian, santipu_, scokaf, slvDev, tsvetanovv, ubl4nk, ulqiorra, yamapyblack, zaskoh
29.6697 USDC - $29.67
address(0)
The getReward
function in BYTES2 does not check if address _to
is 0. Similarly claimReward
function in NeoTokyoStaker does not check if the address _recipient
is 0 either.
114: function getReward ( 115: address _to 116: ) external { 117: ( 118: uint256 reward, 119: uint256 daoCommision 120: ) = IStaker(STAKER).claimReward(_to);
contracts\staking\NeoTokyoStaker.sol
1409: function claimReward ( 1410: address _recipient 1411: ) external returns (uint256, uint256) {
Even though it is impossible for an overflow because of the maximum amount of tokens in circulation, but the order of operands could be changed for safety measures.
152: unchecked { 153: treasuryShare = _amount * 2 / 3; 154: }
can be refactored to
152: unchecked { 153: treasuryShare = _amount / 3 * 2; 154: }
AssetType
CHECK MISSES OUT ON INVALID VALUEAssetType
has ids from 0-3 each representing S1 Citizen, S2 citizen, BYTES and LP tokens respectively. uint8(_assetType) > 4
will allow an id of 4 since the expression will be false. Condition should be refactored to (uint8(_assetType) > 3)
.
contracts\staking\NeoTokyoStaker.sol
1205: if (uint8(_assetType) > 4) { 1206: revert InvalidAssetType(uint256(_assetType)); 1207: } 1668: if (uint8(_assetType) == 2 || uint8(_assetType) > 4) { 1669: revert InvalidAssetType(uint256(_assetType)); 1670: }
#0 - c4-judge
2023-03-17T02:35:55Z
hansfriese marked the issue as grade-c
#1 - c4-judge
2023-04-04T09:13:42Z
hansfriese marked the issue as grade-b
🌟 Selected for report: JCN
Also found by: 0x1f8b, 0xSmartContract, 0xSolus, 0xhacksmithh, 0xnev, Angry_Mustache_Man, Aymen0909, Diana, Flora, Inspex, Madalad, MatricksDeCoder, MiniGlome, R-Nemes, RaymondFam, ReyAdmirado, Rolezn, SAAJ, Sathish9098, Shubham, Udsen, Viktor_Cortess, arialblack14, atharvasama, ayden, c3phas, carlitox477, descharre, dharma09, durianSausage, fatherOfBlocks, ginlee, glcanvas, hunter_w3b, leopoldjoy, matrix_0wl, mrpathfindr, nadin, oyc_109, pipoca, schrodinger, slvDev, ulqiorra, volodya
19.3029 USDC - $19.30
Number | Details | Instances |
---|---|---|
1 | x += y /x -= y COSTS MORE GAS THAN x = x + y /x = x - y FOR STATE VARIABLES | 16 |
2 | CAN DECLARE VARIABLE OUTSIDE LOOP TO SAVE GAS | 12 |
3 | revert() STATEMENTS THAT CHECK INPUT ARGUMENTS SHOULD BE AT THE TOP OF THE FUNCTION | 1 |
4 | BEFORE SOME FUNCTIONS, WE SHOULD CHECK SOME VARIABLES FOR POSSIBLE GAS SAVE | 1 |
5 | TERNARY OPERATION NOT REQUIRED | 2 |
6 | UNNECESSARY CAST TO UINT8 | 2 |
x += y
/x -= y
COSTS MORE GAS THAN x = x + y
/x = x - y
FOR STATE VARIABLESUsing the addition operator instead of plus-equals saves some gas for state variables.
contracts\staking\NeoTokyoStaker.sol
977: pool.totalPoints += citizenStatus.points; 1029: pool.totalPoints += citizenStatus.points; 1078: citizenStatus.stakedBytes += amount; 1079: citizenStatus.points += bonusPoints; 1080: pool.totalPoints += bonusPoints; 1099: citizenStatus.stakedBytes += amount; 1100: citizenStatus.points += bonusPoints; 1101: pool.totalPoints += bonusPoints; 1160: stakerLPPosition[msg.sender].amount += amount; 1161: stakerLPPosition[msg.sender].points += points; 1164: pool.totalPoints += points; 1298: points += stakerLPPosition[_recipient].points; 1515: pool.totalPoints -= stakedCitizen.points; 1626: lpPosition.amount -= amount; 1627: lpPosition.points -= points; 1630: pool.totalPoints -= points;
Declaring the stack variable outside the loop will save gas that would otherwise be spent on declaring the variable over and over again.
contracts\staking\NeoTokyoStaker.sol
718: uint256 citizenId = _stakerS1Position[_staker][i]; outside loop 735: uint256 citizenId = _stakerS2Position[_staker][i];outside loop 1280: uint256 citizenId = _stakerS1Position[_recipient][i]; outside loop 1281: StakedS1Citizen memory s1Citizen = stakedS1[_recipient][citizenId]; outside loop 1289: uint256 citizenId = _stakerS2Position[_recipient][i];outside loop 1290: StakedS2Citizen memory s2Citizen = stakedS2[_recipient][citizenId];outside loop 1313: RewardWindow memory window = pool.rewardWindows[i]; outside loop 1320: uint256 currentRewardRate = pool.rewardWindows[i - 1].reward; outside loop 1331: uint256 timeSinceReward = block.timestamp - lastPoolRewardTime; outside loop 1342: uint256 timeSinceReward = window.startTime - lastPoolRewardTime; outside loop 1355: uint256 timeSinceReward = 1378: uint256 timeSinceReward = block.timestamp - lastPoolRewardTime;
revert()
STATEMENTS THAT RESULT FROM CHECKING INPUT ARGUMENTS SHOULD BE AT THE TOP OF THE FUNCTIONBy doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
contracts\staking\NeoTokyoStaker.sol
1220: uint256 timelockOption = timelockOptions[_assetType][_timelockId]; 1221: if (timelockOption == 0) { 1222: revert InvalidTimelockOption(uint256(_assetType), _timelockId); 1223: }
Before burning/minting/transfer, we should check for amount being 0 so the function doesnt run when its not gonna do anything.
101: IByteContract(BYTES1).burn(msg.sender, _amount); 102: _mint(msg.sender, _amount);
It would be slightly cheaper to declare the variable and simply use an if statement, instead of using ternary operation.
contracts\staking\NeoTokyoStaker.sol
639: string memory vaultMultiplier = (_vaultId != 0) 640: ? vault.getCreditMultiplier(_vaultId) 641: : ""; 665: string memory vaultMultiplier = (_vaultId != 0) 666: ? vault.getCreditMultiplier(_vaultId) 667: : "";
Some gas can be saved by casting to uint256 while checking instead of uint8.
contracts\staking\NeoTokyoStaker.sol
1205: if (uint8(_assetType) > 4) { 1668: if (uint8(_assetType) == 2 || uint8(_assetType) > 4) {
#0 - c4-judge
2023-03-17T03:48:46Z
hansfriese marked the issue as grade-b
#1 - c4-sponsor
2023-03-20T19:15:19Z
TimTinkers marked the issue as sponsor confirmed