Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

The `block.timestamp` on `Arbitrum` and `Ethereum` can return different values

Summary

The block.timestamp on Arbitrum can not represent right the current time.

Vulnerability Details

The project will be deployed on Arbitrum and Ethereum. Also, the Streets::unstake function relies on block.timestamp to check the time for which the token is staked (stakedDuration):

function unstake(uint256 tokenId) external {
require(stakes[tokenId].owner == msg.sender, "Not the token owner");
@> uint256 stakedDuration = block.timestamp - stakes[tokenId].startTime;
uint256 daysStaked = stakedDuration / 1 days;
// Assuming RapBattle contract has a function to update metadata properties
IOneShot.RapperStats memory stakedRapperStats = oneShotContract.getRapperStats(tokenId);
emit Unstaked(msg.sender, tokenId, stakedDuration);
delete stakes[tokenId]; // Clear staking info
// Apply changes based on the days staked
if (daysStaked >= 1) {
stakedRapperStats.weakKnees = false;
credContract.mint(msg.sender, 1);
}
if (daysStaked >= 2) {
stakedRapperStats.heavyArms = false;
credContract.mint(msg.sender, 1);
}
if (daysStaked >= 3) {
stakedRapperStats.spaghettiSweater = false;
credContract.mint(msg.sender, 1);
}
if (daysStaked >= 4) {
stakedRapperStats.calmAndReady = true;
credContract.mint(msg.sender, 1);
}
// Only call the update function if the token was staked for at least one day
if (daysStaked >= 1) {
oneShotContract.updateRapperStats(
tokenId,
stakedRapperStats.weakKnees,
stakedRapperStats.heavyArms,
stakedRapperStats.spaghettiSweater,
stakedRapperStats.calmAndReady,
stakedRapperStats.battlesWon
);
}
// Continue with unstaking logic (e.g., transferring the token back to the owner)
oneShotContract.transferFrom(address(this), msg.sender, tokenId);
}

But the block.timestamp on Arbitrum works differently.

In the Arbitrum documentation is said:

Block timestamps on Arbitrum are not linked to the timestamp of the L1 block. They are updated every L2 block based on the sequencer's clock. These timestamps must follow these two rules:
- Must be always equal or greater than the previous L2 block timestamp
- Must fall within the established boundaries (24 hours earlier than the current time or 1 hour in the future). More on this below.
Furthermore, for transactions that are force-included from L1 (bypassing the sequencer), the block timestamp will be equal to either the L1 timestamp when the transaction was put in the delayed inbox on L1 (not when it was force-included), or the L2 timestamp of the previous L2 block, whichever of the two timestamps is greater.

https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/block-numbers-and-time#block-timestamps-arbitrum-vs-ethereum

Impact

Due to the way in which the block.timestamp works on Arbitrum it is possible the following scenario:

Bob stakes his token at 15:00 (3 PM) on February 27 and attempts to unstake at 17:00 (5 PM) on February 28, the real-world elapsed time is indeed 1 day and 2 hours. The contract is deployed on Arbitrum and the block.timestamp can return value that is 24 hours earlier than the current time. If the block.timestamp on Arbitrum is lagging by up to 24 hours due to the delay in rollup batch confirmations, the smart contract might see the current time as 17:00 on February 27, only 2 hours after the original stake.

As a result, when Bob tries to unstake, the contract calculates the staked duration based on the block.timestamp, which it perceives to be only 2 hours past the staking time. This would lead to the contract not rewarding Bob any tokens, as it incorrectly assumes that the staking period was less than a day.

Also, it is possible the scenario when the block.timestamp on Arbitrum returns the time that is 1 hour in the future. In that case Bob will be able to unstake his token and receive his cred tokens 1 hour early.

Tools Used

Manual Review

Recommendations

Ensure that the return value of the stakedDuration in Streets::unstake function is calculated properly on every chain that the project is deployed.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

arbitrum timestamp

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.