F-2025-0007·stale-state

getCurrentBlockReward May Return Outdated Reward Due to Stale currentHalvingPeriod

Fixedliquid-stakinglststaking-poolsgithub.com/matchain/contracts
TL;DR

getCurrentBlockReward reads the public currentHalvingPeriod variable directly without recomputing it via getHalvingPeriod(block.number), so external readers may see a stale reward rate before any internal call refreshes the cached period.

Severity
MEDIUM
Impact
MEDIUM
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
LOW
Exploitability
LOW
02Section · Description

Description

The MattToken contract exposes a public variable currentHalvingPeriod, which is used to determine the current halving stage of the token emission schedule. This variable is used in the public function getCurrentBlockReward() to calculate the reward per block:

solidity
function getCurrentBlockReward() public view returns (uint256) {
if (totalEmitted >= MAX_SUPPLY) return 0;
uint256 baseReward = REWARD_PER_THREE_BLOCKS / 3;
uint256 reward = baseReward >> currentHalvingPeriod; // @audit this can be outdated if getHalvingPeriod() is not called before
uint256 remainingSupply = MAX_SUPPLY - totalEmitted;
return reward > remainingSupply ? remainingSupply : reward;
}

However, this function does not check whether the halving period has changed based on the number of blocks passed since the last update. The function getHalvingPeriod(uint256 blockNumber) does perform this logic:

solidity
function getHalvingPeriod(uint256 blockNumber) public view returns (uint256) {
return (blockNumber - deploymentBlock) / BLOCKS_PER_HALVING;
}

As a result, getCurrentBlockReward() may return a reward based on an outdated halving period, especially when the currentHalvingPeriod variable hasn't been updated through another internal call.

While this does not pose a direct security risk in the current implementation (since any call that uses getCurrentBlockReward() for actual minting will have updated currentHalvingPeriod beforehand), it does lead to inaccurate reward data being returned to any external readers or integrators that rely on the public getCurrentBlockReward() or the raw currentHalvingPeriod variable.

03Section · Impact

Impact

External integrators (frontends, indexers, dashboards) reading the public getCurrentBlockReward() may display a higher reward than the contract would actually mint at the next reward distribution that crosses a halving boundary. Internal minting paths are unaffected because they update the cached period first.

04Section · Recommendation

Recommendation

  1. Make currentHalvingPeriod internal so external consumers are forced to call getHalvingPeriod() to obtain an up-to-date halving period.
  2. Modify getCurrentBlockReward() to first calculate the up-to-date halving period dynamically by calling getHalvingPeriod() internally, e.g.:
solidity
function getCurrentBlockReward() public view returns (uint256) {
if (totalEmitted >= MAX_SUPPLY) return 0;
uint256 currentHalvingPeriod_ = getHalvingPeriod(block.number);
uint256 baseReward = REWARD_PER_THREE_BLOCKS / 3;
uint256 reward = baseReward >> currentHalvingPeriod_;
uint256 remainingSupply = MAX_SUPPLY - totalEmitted;
return reward > remainingSupply ? remainingSupply : reward;
}
F-2025-0007

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx