foundry-rs / foundry-rs/foundry

anvil mines consecutive blocks with identical timestamps on default automine path

Open
#16,544 2 comments 0 reactions 1 assignee Claimed by @gomesalexandre View on GitHub
Dominant language
Rust
Stars
10.6k
Forks
2.6k
Avg merge
18h 20m
Merged PRs (30d)
510

Description

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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.