Skip to content
John Ndigirigi
WorkAboutWritingResumeContactBook a call (opens in a new tab)
  • Work
  • About
  • Writing
  • Resume
  • Contact
Book a call (opens in a new tab)
John Ndigirigi

Full-stack & AI Engineer · Nairobi, working remotely worldwide.

  • Work
  • About
  • Writing
  • Resume
  • Contact

© 2026 John Ndigirigi

Built with React + Vite + Tailwind

All writing
  • Cardano
  • eUTxO
  • Smart Contracts
  • Formal Verification

Why eUTxO Makes Smart Contracts Easier to Reason About

8 October 2025 · 4 min read

The account model makes smart contract correctness difficult to reason about, not because developers are careless, but because the execution environment was designed for flexibility, not auditability. A smart contract in the account model is a persistent object with mutable state. Understanding what it will do requires knowing its current state, all the states it might transition through, and how concurrent transactions might interleave. The state space grows combinatorially with contract complexity, which is why smart contract audits are expensive and still miss things.

eUTxO doesn't eliminate programmer error. But it changes the shape of the problem in a way that makes certain classes of mistakes structurally impossible and makes verification tractable rather than merely aspirational.

Two Mental Models for Execution

In the account model, a contract call is a state mutation. When a transaction invokes a function, the contract's storage changes. If another transaction arrives before yours is finalised, it might change the state your transaction will observe. The contract's behaviour depends on global state that you don't fully control at the time you construct your transaction.

In eUTxO, there is no contract state in this sense. There are UTxOs: discrete, immutable outputs locked at script addresses, each carrying a value and an optional datum. A smart contract is a validator, a pure function that decides whether a specific UTxO can be spent by a specific transaction. The validator runs once, at spend time. It either approves or rejects. The UTxO is not modified; it is consumed, and the transaction produces new outputs.

The validator receives three inputs: the datum attached to the UTxO being spent, the redeemer provided by the spending transaction, and the full transaction context (all inputs, outputs, signatures, and validity interval). Its output is boolean. That is the complete interface.

Determinism as a Security Property

Because a validator is a pure function with known inputs, its behaviour is deterministic. Given the same UTxO and the same transaction, it will always produce the same result. This means you can evaluate the validator off-chain, against the exact inputs it will see on-chain, and get a guaranteed preview of the outcome. There is no state to query, no race condition that might change the answer between your simulation and the chain's execution.

This property has direct implications for formal verification. When you reason about a validator, you are reasoning about a function, not a state machine. You define a specification, what the function should return for each class of input, and verify the implementation against it. The scope of the proof is the function itself plus the set of transaction shapes it might encounter. That scope is explicit and enumerable.

Formal verification of account-model contracts requires reasoning about all possible transition sequences of a state machine, including reentrancy paths and cross-contract interactions. Formal verification of a validator requires reasoning about a function over a structured input. Both are hard. But the latter is structurally more bounded, and the tools for verifying pure functions are more mature than the tools for verifying stateful concurrent systems.

What Local Reasoning Enables

When auditing an account-model contract, you generally need to trace every possible call sequence to gain confidence about invariants. A reentrancy vulnerability, for example, arises from the interaction between state mutations and external calls that can re-enter the contract mid-execution. You can't reason about a single function in isolation, you have to reason about the system.

When auditing a validator, the unit of analysis is the validator function and the set of valid transaction structures. There is no reentrancy, a validator executes once per input and cannot be re-entered. There is no mutable global state that might change between the time you construct a transaction and the time it executes on-chain. The analysis is local to the validator and its inputs.

This doesn't mean eUTxO validators are trivially safe. The primary attack surface is transaction structure: constructing a transaction that satisfies the validator's formal checks but violates its intended invariants. The double-satisfaction vulnerability is a well-known example, if two script inputs in the same transaction both check for the presence of a specific output, a single output can satisfy both checks simultaneously. The validator for each input sees the full transaction context, including the other inputs. Reasoning about what a validator allows means reasoning about the full space of transactions that could include that input, not just the "happy path" transaction you designed.

These are real design challenges. But they are challenges of specification, ensuring the validator checks everything it needs to, rather than challenges of execution environment unpredictability. That is a meaningful distinction when you're trying to build confidence in contract correctness.

The Tradeoff: Expressiveness

The constraints that make eUTxO contracts more auditable also make certain patterns harder to express. Global state, an order book, a vote counter, a shared liquidity pool, must be modelled explicitly through UTxO design patterns. Protocols that require coordination across multiple participants need careful thought about concurrency and UTxO contention: two transactions cannot spend the same UTxO, so high-throughput access to shared state requires architectural solutions that have no equivalent in the account model.

The model rewards careful upfront thinking about data representation. It penalises the "iterate quickly and see what happens" approach that works in more forgiving environments. For production financial contracts, this is arguably the correct incentive structure, the environment that forces careful design before deployment is the one you want when the stakes are high.

The correctness guarantee is only as good as the validator's specification. eUTxO makes it easier to verify that an implementation matches a specification; it doesn't write the specification for you. The discipline of stating clearly what a contract is supposed to do, and then proving that it does exactly that and nothing more, is still the hard part.

← OlderMulti-Tenant SaaS with PostgreSQL Row-Level SecurityNewer →KERI: Decentralised Identity Without the Blockchain Dependency