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: 120/123
Findings: 1
Award: $19.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
This block of code can be outside of the second for loop: NeoTokyoStaker.sol#L1329-L1337
Change from
if (lastPoolRewardTime < window.startTime) { uint256 currentRewardRate = pool .rewardWindows[i - 1] .reward; /* Iterate forward to the present timestamp over any unclaimed reward windows. */ for (uint256 j = i; j < windowCount; ) { // If the current time falls within this window, complete. if (block.timestamp <= window.startTime) { unchecked { uint256 timeSinceReward = block.timestamp - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } // We have no forward goto and thus include this bastardry. i = windowCount; break; // Otherwise, accrue the remainder of this window and iterate. } else { unchecked { uint256 timeSinceReward = window.startTime - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } currentRewardRate = window.reward; lastPoolRewardTime = window.startTime; /* Handle the special case of overrunning the final window by fulfilling the prior window and then jumping forward to use the final reward window. */ if (j == windowCount - 1) { unchecked { uint256 timeSinceReward = block.timestamp - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } // We have no forward goto and thus include this bastardry. i = windowCount; break; // Otherwise, iterate. } else { window = pool.rewardWindows[j + 1]; }...
To
if (lastPoolRewardTime < window.startTime) { uint256 currentRewardRate = pool .rewardWindows[i - 1] .reward; if (block.timestamp <= window.startTime) { unchecked { uint256 timeSinceReward = block.timestamp - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } // We have no forward goto and thus include this bastardry. break; // Otherwise, accrue the remainder of this window and iterate. } /* Iterate forward to the present timestamp over any unclaimed reward windows. */ for (uint256 j = i; j < windowCount; ) { // If the current time falls within this window, complete. unchecked { uint256 timeSinceReward = window.startTime - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } currentRewardRate = window.reward; lastPoolRewardTime = window.startTime; /* Handle the special case of overrunning the final window by fulfilling the prior window and then jumping forward to use the final reward window. */ if (j == windowCount - 1) { unchecked { uint256 timeSinceReward = block.timestamp - lastPoolRewardTime; totalReward += currentRewardRate * timeSinceReward; } // We have no forward goto and thus include this bastardry. i = windowCount; break; // Otherwise, iterate. } else { window = pool.rewardWindows[j + 1]; }...
For example, we should check for amount being 0 so the function does not run when its not gonna do anything:
Check _amount not equal to 0:
BYTES2.sol#L93-L95
function upgradeBytes ( uint256 _amount ) external {
function _assetTransfer ( address _asset, address _to, uint256 _amount ) private {
Check address _to
not equal to address(0)
BYTES2.sol#L114-L116
function getReward ( address _to ) external {
+
operation
NeoTokyoStaker.sol#L977
pool.totalPoints += citizenStatus.points;
pool.totalPoints += citizenStatus.points;
NeoTokyoStaker.sol#L1078-L1080
citizenStatus.stakedBytes += amount; citizenStatus.points += bonusPoints; pool.totalPoints += bonusPoints;
NeoTokyoStaker.sol#L1099-L1101
citizenStatus.stakedBytes += amount; citizenStatus.points += bonusPoints; pool.totalPoints += bonusPoints;
NeoTokyoStaker.sol#L1160-L1164
stakerLPPosition[msg.sender].amount += amount; stakerLPPosition[msg.sender].points += points; // Update the pool point weights for rewards. pool.totalPoints += points;
-
operation
pool.totalPoints -= stakedCitizen.points;
NeoTokyoStaker.sol#L1626-L1630
lpPosition.amount -= amount; lpPosition.points -= points; // Update the pool point weights for rewards. pool.totalPoints -= points;
NeoTokyoStaker.sol#L1517-L1521
stakedCitizen.stakedBytes = 0; stakedCitizen.timelockEndTime = 0; stakedCitizen.points = 0; stakedCitizen.hasVault = false; stakedCitizen.stakedVaultId = 0;
NeoTokyoStaker.sol#L1582-L1584
stakedCitizen.stakedBytes = 0; stakedCitizen.timelockEndTime = 0; stakedCitizen.points = 0;
If the if statement has a logical AND and is not followed by an else statement, it can be replaced with 2 if statements.
NeoTokyoStaker.sol#L910
if (citizenVaultId != 0 && vaultId != 0) {
} else if (citizenVaultId != 0 && vaultId == 0) {
} else if (citizenVaultId == 0 && vaultId != 0) {
if (j != 0 && _inputs[i].rewardWindows[j].startTime <= lastTime) {
You can cut out 10 opcodes in the creation-time EVM bytecode if you declare a constructor payable. Making the constructor payable eliminates the need for an initial check of msg.value == 0 and saves 13 gas on deployment with no security risks.
BYTES2.sol#L75-L85
constructor ( address _bytes, address _s1Citizen, address _staker, address _treasury ) { BYTES1 = _bytes; S1_CITIZEN = _s1Citizen; STAKER = _staker; TREASURY = _treasury; }
constructor ( address _bytes, address _s1Citizen, address _s2Citizen, address _lpToken, address _identity, address _vault, uint256 _vaultCap, uint256 _noVaultCap ) {
If a function modifier or require such as onlyOwner-admin is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
BYTES2.sol#L140-L143
function burn ( address _from, uint256 _amount ) hasValidPermit(UNIVERSAL, BURN) external {
Foundry's script.sol and solmate LibRlp.sol contracts can do this => Reference: Here and here
_assetTransferFrom(S1_CITIZEN, msg.sender, address(this), citizenId);
_assetTransferFrom(VAULT, msg.sender, address(this), vaultId);
_assetTransferFrom(S2_CITIZEN, msg.sender, address(this), citizenId);
_assetTransferFrom(LP, msg.sender, address(this), amount);
_assetTransferFrom(BYTES, msg.sender, address(this), amount);
if (stakedCitizen.stakedVaultId != 0) { _assetTransferFrom( VAULT, address(this), msg.sender, stakedCitizen.stakedVaultId ); }
_assetTransferFrom(S1_CITIZEN, address(this), msg.sender, citizenId);
_assetTransferFrom(S2_CITIZEN, address(this), msg.sender, citizenId);
new StakedS1CitizenOutput[](_stakerS1Position[_staker].length); for (uint256 i; i < _stakerS1Position[_staker].length; ) {
new StakedS2CitizenOutput[](_stakerS2Position[_staker].length); for (uint256 i; i < _stakerS2Position[_staker].length; ) {
for (uint256 i; i < _stakerS1Position[_recipient].length; ) {
for (uint256 i; i < _stakerS2Position[_recipient].length; ) {
for (uint256 stakedIndex; stakedIndex < oldPosition.length; ) {
=
to update points variable instead of +=
Change
points += stakerLPPosition[_recipient].points;
To
points = stakerLPPosition[_recipient].points
{ version: '0.8.11', settings: { optimizer: { enabled: true, runs: 200, details: { yul: true } } } }, { version: '0.8.19', settings: { optimizer: { enabled: true, runs: 200, details: { yul: true } } } }
Change
mapping ( address => mapping ( AssetType => uint256 )) public lastRewardTime;
To
uint256 public lastRewardTime;
#0 - c4-judge
2023-03-17T03:57:07Z
hansfriese marked the issue as grade-b