#16544·foundry

anvil mines consecutive blocks with identical timestamps on default automine path

Author: gomesalexandreCreated Sep 2, 2026Updated Sep 2, 2026

This is the repro @grandizzy asked for in #9142:

@Rubilmax mind to open a ticket with a way to reproduce the problem you see? thank you!

That ticket never got filed, so here it is.

What it says on the box

Default anvil (default automine, no --block-time, no setBlockTimestampInterval tweak) mines consecutive blocks with the identical block.timestamp. Confirmed with four ordinary cast send transfers, no flags:

block 2 ts 1788310641
block 3 ts 1788310641
block 4 ts 1788310641

A forward time jump makes it worse - after anvil_mine(1, 10000), every subsequent block freezes at that same timestamp:

blocks 2-5 all ts 1788320438

Where

crates/anvil/src/eth/backend/time.rs:181-183:

rust
// Ensures that the timestamp is always increasing
if next_timestamp < last_timestamp { next_timestamp = last_timestamp + 1; }

Comment says "always increasing", comparison is <, so equality sails through untouched. Sibling set_next_block_timestamp (~line 127/131) has a doc comment saying it "fails if it's before (or at the same time) the last timestamp" but only checks < too.

Why it matters

Yellow Paper eq. 55 requires Hs > P(H)Hs (strictly increasing). geth rejects header.Time <= parent.Time with errOlderBlockTime. Hardhat and Ganache both guarantee parent+1. anvil is the odd one out on the default path.

Concretely, this makes downstream test suites go green when they should catch a real bug:

  • Uniswap V2 _update: if (timeElapsed > 0) - TWAP accumulators never accumulate across same-second blocks
  • Uniswap V3 Oracle.write: if (last.blockTimestamp == blockTimestamp) return - the observation write is silently dropped
  • Synthetix StakingRewards.rewardPerToken and similar reward-streaming contracts: zero accrual for that block

A TWAP-manipulation guard test written against anvil can pass vacuously because the timestamp never actually advances between the manipulation block and the read block.

Prior art

#13694 proposed exactly this fix (< -> <=) and got a positive review (zerosnacks: "Makes sense, thanks!"), then the stale-bot closed it 2026-06-29 for inactivity, not on merit.

The catch: #9142 itself is a legitimate feature request for same-timestamp blocks under --block-time 0, and it shipped. A naive blanket <= would break that. The distinction that needs preserving: same-timestamp is fine when it was explicitly requested (interval == Some(0) or an exact setBlockTimestampInterval/setNextBlockTimestamp override), but should not leak into the default automine path where nothing asked for it. That's plausibly why #13694 stalled rather than merged outright - it needed that narrower framing, not just the comparison flip.

Suggested fix shape

Clamp to strictly-increasing on the default path; only permit equal timestamps when the same-timestamp mode was explicitly opted into (matching what #9142 actually asked for, not a blanket relaxation).

Happy to open a PR with that narrower fix if that's the right shape - flagging as an issue first since #13694's exact form apparently needed adjustment.