The block that wasn't there

A blockchain is a database with strong opinions about history. Most of its bugs come from the places where those opinions aren't enforced.

  • blockchain
  • solidity

A blockchain is a database that refuses to forget. That's the interesting property, and it's also where most of the security work lives: not in the cryptography, but in the gap between what the protocol guarantees and what a particular contract assumes.

Consensus is not correctness

Consensus tells you a transaction happened, in a specific order, and that everyone agrees. It says nothing about whether the transaction should have been allowed. A perfectly valid, perfectly final, perfectly disastrous transfer is still a valid transfer.

So the security question is never "can this be forged?" — usually it can't. It's:

  • Who is allowed to call this?
  • What state do they need to be in for the call to be safe?
  • What happens if two of these calls land in the same block?

Same-block surprises

The block is a batching mechanism, and batching is where assumptions break. Code that is safe when called once is often not safe when called three times before anything settles:

// Assume `total` is read from storage somewhere far away,
// and balances are only reconciled at the end of the block.
for (uint i = 0; i < n; i++) {
    accrue(user[i], total / n);
}

If total can move mid-block, every entry in that loop was calculated against a number that was true exactly once, briefly, at the top.

The chain isn't lying to you. It's faithfully executing a program that assumed something the chain never promised.

What I look for first

Ordered by how often it pays off:

  1. External calls placed before state updates.
  2. Assumptions that a value can't change within a transaction.
  3. onlyOwner used as the whole security model.
  4. Anything that reads a balance and then acts on it later.

Most exploits aren't clever. They're a require that was obvious to the author and invisible to the reviewer, sitting in a file that both of them had already read twice.

That's the job: read it a third time, from the attacker's side of the glass.