A Practical Threat Model for Smart Contract Engagements
Audits find bugs; threat modelling prevents whole classes of them. We walk through how we frame trust boundaries, privileged roles, and economic attacks before a single line of contract code is written.
Adrian Vance
Founder & Managing Partner
Most smart-contract losses are not the result of an exotic compiler bug. They are the result of a system that was never threat-modelled: a privileged key with too much power, an oracle that can be manipulated, an upgrade path nobody scrutinised, or an economic incentive that pays an attacker to behave badly. An audit at the end of the project can catch some of this, but the highest-leverage security work happens before the code exists, when changing the design is still cheap.
Start with assets and adversaries
The first question is not "is this function reentrant" but "what is there to steal, and who would want to steal it." We enumerate the assets the system holds or controls, such as user deposits, protocol fees, governance power and the ability to mint, then list the actors who interact with it: ordinary users, liquidity providers, the deploying team, governance, oracles, and unknown external contracts. For each asset we ask which actors can move it, under what conditions, and what happens if one of them is malicious or compromised.
Map the trust boundaries
Every external call and every privileged role is a trust boundary. We draw them explicitly. A protocol that "decentralised" but ships with an upgradeable proxy controlled by a single externally owned account has a trust boundary the size of one private key, and no amount of clever Solidity changes that. We push clients toward role separation, multisig or timelock control of privileged actions, and the principle that the most dangerous powers — minting, upgrading, draining — should be the hardest to exercise.
Treat the economics as part of the attack surface
The most expensive DeFi exploits are frequently not memory-safety failures; they are the protocol behaving exactly as written, in a state the designers did not anticipate. Flash-loan-amplified oracle manipulation, governance attacks bought with borrowed tokens, and liquidation cascades are economic attacks. We model these by asking: if an attacker had effectively unlimited capital for one transaction, what could they do? If the answer is "move the price the protocol trusts," the design needs a manipulation-resistant oracle, not a better audit.
Invariants are the bridge to verification
Threat modelling produces a list of properties that must never be violated: total collateral always backs total debt, no user can withdraw more than they deposited, supply only changes through sanctioned paths. These invariants become the specification for invariant testing and formal verification. We write them down during design and then prove the implementation upholds them, rather than discovering after the fact that nobody agreed what "correct" meant.
Plan for the day something is wrong
Finally, threat modelling includes the response. What can be paused, and who can pause it? Is there a path to migrate funds if a flaw is found? How fast can the team react, and does the timelock that protects users also prevent an emergency fix? These are design decisions, not operational afterthoughts, and a system that cannot respond to its own failure is itself a vulnerability.
Audits remain essential — we always recommend an independent one before mainnet. But the audit is far cheaper and far more effective when it is reviewing a system that was designed with its adversaries in mind, against invariants the team already agreed on.
Adrian Vance
Founder & Managing Partner
Founder of Web3Software. Twelve years building distributed systems and capital-markets infrastructure, the last six dedicated to blockchain, on-chain settlement, and quantitative trading platforms for institutional clients.
Get the next deep-dive in your inbox.
Occasional, substantive engineering write-ups from the team. No spam, unsubscribe anytime.