ERC20MintLimited is not an unlimited forge minter.
It authorizes a forge to mint only while that forge remains inside the configured capacity for the current period.
This finding breaks that second authorization boundary.
With a positive offsetSeconds, CROSS calculates the current period start incorrectly. At the native UTC boundary, PeriodManager.getPeriodStartForTime() can return the next offset boundary even though that boundary is still in the future.
ERC20PeriodMintLimit interprets the changed timestamp as a new period and restores a full LIMIT of capacity before the real configured period has ended.
The PoC demonstrates:
Configured limit
100,000 tokens per period
First mint
100,000 tokens
Remaining capacity
0
Configured +9h period ended?
NO
Capacity at UTC boundary
100,000 tokens
Second mint
100,000 tokens
Total inside the same intended period
200,000 tokens
I reported the finding as Critical through the CertiK SkyShield CROSS bug bounty program. CROSS finalized it as Low, and the report earned $109.40.

The severity dispute turns on whether forge membership alone authorizes the amount being minted. The contract does not support that interpretation.
The mint policy has two gates
The base mint path requires onlyForge:
modifier onlyForge() {
if (!isForge(_msgSender())) {
revert TokenBase__OnlyForge(_msgSender());
}
_;
}
But ERC20MintLimited routes minting through ERC20PeriodMintLimit, which also checks the available period capacity:
if (periodCapacity < amount) {
revert ERC20PeriodMintLimit__ExceedsPeriodLimit(
amount,
periodCapacity
);
}
_periodCapacity = periodCapacity - amount;
super.mint(to, amount);
The effective policy is therefore:
caller is an authorized forge
AND
amount <= remaining capacity
for the current configured period
The PoC does not claim that a random EOA bypasses onlyForge.
It proves that a forge with zero remaining capacity can receive a new full capacity before the period actually ends and mint supply that the period limit should reject.
The positive offset formula is wrong
For positive offsets, the vulnerable implementation effectively computes:
floor(timestamp / duration)
× duration
+ offsetSeconds
For an offset period, the timestamp must be shifted before flooring:
floor(
(timestamp - offsetSeconds)
/ duration
)
× duration
+ offsetSeconds
The PoC uses:
duration = 86,400 seconds
offsetSeconds = 32,400 seconds
LIMIT = 100,000 tokens
The intended period is:
[1000 days + 9 hours,
1001 days + 9 hours)
One second before the UTC boundary, the forge mints the full LIMIT. Capacity becomes zero, and an additional one wei mint is rejected.
Then the timestamp reaches:
1001 days
The configured +9h period still has nine hours remaining.
The correct current period start is still:
1000 days + 9 hours
The PoC records:
block.timestamp
86486400
expected period start
86432400
actual period start
86518800
So CROSS returns a current period start that is greater than block.timestamp.
That impossible timestamp is the root cause.
The future timestamp restores capacity
ERC20PeriodMintLimit resets capacity whenever the calculated period start changes:
uint256 currentPeriodStart =
_period.getCurrentPeriodStart();
if (currentPeriodStart != _periodStartTime) {
_periodStartTime = currentPeriodStart;
periodCapacity = _limit;
}
At the UTC boundary, the bad positive offset calculation changes currentPeriodStart even though the real configured period is still active.
Capacity is restored to LIMIT.
The same forge then mints another full LIMIT, and the call reaches the real ERC20 mint path.
This is not merely an incorrect view value. totalSupply increases.
The PoC isolates the bug
The Foundry proof uses the real ERC20MintLimited preset, ERC20PeriodMintLimit, PeriodManager, onlyForge, and ERC20 mint path.
It contains three tests:
| Test | What it proves |
|---|---|
testBug |
The full limit is consumed, capacity resets too early, and the same forge mints a second full limit before the actual period ends. |
testControl |
An extra mint is correctly rejected while capacity is legitimately zero before the false reset. |
testAccess |
A non forge caller cannot mint. |
The run completed with:
3 passed
0 failed
0 skipped
The relevant result is:
Non forge mint
REJECTED
First forge mint
LIMIT
Capacity afterward
0
Extra forge mint before UTC
REJECTED
Configured +9h period still active at UTC
YES
Returned period start in future
YES
Capacity at UTC
LIMIT
Second forge mint
LIMIT
Total minted in intended period
2 × LIMIT
Excess over configured limit
1 × LIMIT
This is why the issue cannot be reduced to a generic access control discussion. Access control is working. The quantitative mint authorization is not.
Why I classified it as Critical
At the time of submission, the CROSS SkyShield scope listed Unauthorized mint, burn, or transfer of crypto assets as a Critical smart contract impact.
My report maps the second full LIMIT mint to that category because the supply exceeds the amount authorized by the preset for the active period.
After the first mint:
remaining period capacity = 0
Before the false boundary, another mint correctly reverts.
The real configured period has not changed one second later. Only the incorrect PeriodManager result changes.
That result creates capacity that should not exist and allows another full supply increase.
If forge membership alone authorized every amount, ERC20MintLimited would add no security boundary beyond onlyForge.
But it does add one: a per period quantitative limit.
The PoC demonstrates one full LIMIT of supply above that limit.
That is the technical basis for my Critical assessment.
Why the Low rationale is not supported by the PoC
The downgrade explanation raised four points:
| Downgrade point | What the code and PoC show |
|---|---|
| Minting remains restricted to forges | Correct, but the finding is an amount authorization bypass, not a forge identity bypass. |
| The effect is bounded and timing dependent | The bound is still one full extra LIMIT in the demonstrated period. Timing is deterministic at the native boundary. |
Native aligned windows remain near LIMIT
|
The affected positive offset configuration is supported. A different configuration working correctly does not repair this one. |
DEFAULT_ADMIN_ROLE can adjust LIMIT
|
A manual parameter response does not fix the incorrect period calculation or restore automatic enforcement. |
None of those points changes the core state transition:
period not ended
capacity already 0
extra mint should revert
future period start returned
capacity reset to LIMIT
second full LIMIT mint succeeds
totalSupply increases
I therefore consider the Low rationale technically inconsistent with the behavior demonstrated by the contract and PoC.
That conclusion does not require speculating about motive. The public evidence supports a strong technical disagreement. It does not establish bad faith.
Bounded does not mean harmless
The PoC uses:
LIMIT = 100,000 tokens
and produces:
intended maximum
100,000 tokens
actual minted
200,000 tokens
excess
100,000 tokens
The issue does not need infinite minting to violate the security policy.
The entire purpose of the rate limit is to prevent the forge from creating that additional supply before the configured period ends.
The relevant question is whether the contract creates supply that its own limit logic should reject.
The PoC shows that it does.
Fix
The offset must be applied before the timestamp is floored.
Conceptually:
shifted =
timestamp - offset
periodStart =
shifted
- (shifted % duration)
+ offset
The period calculation should always preserve:
periodStart <= timestamp
and:
timestamp < periodStart + duration
For the PoC configuration, the regression should be:
mint LIMIT before UTC boundary
SUCCEEDS
mint 1 extra wei
REVERTS
reach UTC boundary
capacity remains 0
mint LIMIT
REVERTS
reach actual +9h boundary
capacity resets to LIMIT
The report also recommends normalizing offsets by the period duration so equivalent offsets map to the same canonical boundary.
Broader audit lesson
Role authorization and quantitative authorization are separate controls.
A protocol can correctly enforce:
who may call
while incorrectly enforcing:
how much that caller may do
The same reasoning applies to mint quotas, withdrawal ceilings, bridge caps, spending limits, epoch budgets, and rate limits.
When a quantity is part of the policy, bypassing that quantity is an authorization failure even if the caller identity is valid.
Time based controls add another useful invariant:
A function returning the start of the current period must never return a timestamp in the future.
That invariant would have exposed this bug directly.
Conclusion
The CROSS bug is a positive offset period accounting failure in the mint limit path.
After an authorized forge consumes its full LIMIT, capacity should remain zero until the actual offset boundary.
Instead, at the earlier UTC boundary, PeriodManager can report the next +9h boundary as the current period start.
ERC20PeriodMintLimit treats that future timestamp as a new period, restores a full LIMIT, and allows another full mint while the same configured period is still active.
The PoC proves that non forge callers remain blocked, exhausted capacity normally rejects extra minting, the period has not ended, capacity resets early, and total minting reaches 2 × LIMIT.
The relevant authorization question is not only:
is the caller a forge?
It is:
is the caller a forge
and does it still have capacity
inside the current configured period?
The second condition fails because of CROSS's period calculation.
That is why I reported the finding as Critical, and why I do not consider the Low downgrade technically supported by the demonstrated behavior.
Top comments (0)