Smart contracts
Code you cannot quietly patch afterwards.
Ordinary software is written knowing a mistake can be fixed on Tuesday. A deployed contract is public, adversarial from the first block, and costs someone money every time it runs. Nearly every practice on this page follows from those four conditions.
- Public before it is finished being read
- Immutable unless you designed otherwise
- Adversarial from the first block
- Paid for on every execution
Why it differs
What changes when the deploy is final.
None of these are exotic ideas. They are ordinary engineering disciplines that become non-negotiable when the usual escape route is closed.
Behaviour is specified before it is written
What the contract must always be true of, stated in plain language first: what can never go negative, who may ever call what, what the total must always equal. Those statements become the tests. Writing them afterwards tends to produce tests that agree with whatever the code already does.
The reader is a stranger
Someone who did not write it, is paid to find its flaws, and is working against a clock. Naming, structure and comments are written for that person, because they are also written for whoever maintains it two years from now.
Failure is not an exception path
Reverting is normal operation, not an error case bolted on at the end. Every external call is treated as capable of failing, reentering, or returning something the interface did not promise.
Nothing on chain is private
Stored values are readable whatever their visibility keyword says, and the pending transaction pool is public before anything is mined. Any design that depends on a secret being unobservable is a design that has not been finished.
Testing
What testing means when you cannot ship a fix.
Line coverage is the least interesting number in this work, and we do not quote it. What matters is whether the properties that must hold have been attacked rather than merely exercised.
Invariants, not just examples
An example test proves the contract handled the case you thought of. A property test states what must remain true for every input and lets a tool spend hours trying to break it. The interesting failures come from the second kind.
Fuzzing against the stated properties
Random and adversarial inputs, run long, with any failing case shrunk to the smallest reproduction and then kept permanently as a regression test. A bug found once should be impossible to reintroduce silently.
Forked-state tests
Executed against a copy of the real chain at a real block, with the real protocols it will interact with. A contract that passes only against idealised mocks has been tested against an environment that does not exist.
Adversarial cases as first-class tests
Reentrancy, integer edges, unexpected token behaviour, front-running of the transaction, manipulation of a price source within a single block, and what happens when a dependency is paused. These are written as tests rather than left for the audit to raise.
The deployment script is tested too
Constructor arguments, ownership transfer, initialisation of a proxy, and the order operations happen in. A contract deployed correctly and initialised wrongly is as broken as one with a flaw in its logic, and this is where a surprising share of real incidents come from.
Execution cost
Cost is a design constraint, not a tuning pass.
On chains that meter execution, every storage write and every loop is a bill somebody pays. Treating that as something to optimise at the end usually means rewriting the data model at the end.
Bounded before deployment
The expensive operations are identified from the data model rather than discovered from a testnet receipt. Where a design implies an unbounded loop over a growing array, that is caught while it is still a diagram.
Measured in the build
Cost is recorded per function and compared between commits, so a change that makes a common operation markedly more expensive is visible in review rather than in production.
Weighed against readability
Some cost savings make a contract materially harder to audit. That is a trade with a real price, and it is decided deliberately rather than by reflex, because an unreadable contract costs more in review than it saves in fees.
Upgradeability
Upgradeable means somebody can change it.
This is the decision most often made by habit. A proxy is added because it seems prudent, and the result is a contract where a single key can replace the logic holding other people’s assets.
Immutable, where the rules genuinely are fixed
Nothing to compromise, nothing to govern, and the strongest guarantee you can give a counterparty. The cost is that a flaw has to be handled by migration rather than by patch.
Upgradeable behind a delay
A timelock so that any change is visible before it takes effect and anyone affected has a window to leave. The delay is what converts an administrative power into something observable.
Control held by more than one person
A threshold of signers rather than an individual key, with the holders and the threshold documented publicly. Who can upgrade is part of the contract’s security model, not an operational detail.
Storage layout treated as permanent
Upgrading a proxy without preserving the layout of what is already stored corrupts it. Layout is documented and checked automatically between versions, because this failure is silent until it is expensive.
If a contract can be changed, say who can change it, how long the warning is, and what stops it. A published answer to those three questions is worth more than the word "audited" on its own.
Audit preparation
What makes an audit cheap.
An external audit is bounded by time. What it finds depends heavily on how much of that time is spent understanding the code rather than examining it.
Frozen scope, tagged commit
A specific commit, with the files in scope and out of scope named. Auditing a moving target produces findings against code that no longer exists.
The specification goes with it
What each contract is for, the invariants that must hold, the trust assumptions, and who is expected to be able to do what. Every hour an auditor spends reconstructing intent is an hour not spent looking for flaws.
Known risks stated up front
The places the team is already unsure about, written down and handed over. Concealing a doubt to protect the report is how it ends up in production instead.
Findings answered, not absorbed
Each finding is fixed, or accepted with a written reason. An accepted risk with an argument attached is a legitimate outcome; an unanswered finding is not.
The report is not a guarantee
An audit is evidence that competent people looked for a bounded time and reported what they found. It is not proof of correctness, and anyone presenting it as one is describing something the auditors did not claim.
Questions
The ones worth asking first.
Do you audit contracts written by someone else?
Review, yes. A review by the people who did not write the code is genuinely useful and we do it. What we would not do is review our own work and present that as an independent audit, because it is not one. Where a contract is going to hold significant value, an external firm with no involvement in the build is worth the cost.
Which chain should this be on?
That question belongs before this page rather than on it. It is decided on evidence about settlement, cost, tooling and where your counterparties already are — and sometimes the answer is that a database serves the requirement better. The blockchain page sets out how that decision is made.
Should the contract be upgradeable?
It depends entirely on what it holds and who it holds it for. If the rules are genuinely fixed, immutable is the stronger guarantee and the simpler contract. If they will need to change, the honest version is upgradeable with a timelock and multiple signers, disclosed plainly. The answer we would refuse to give is a proxy added by default with a single key behind it.
How long does an audit take, and when should it start?
The duration is the auditing firm’s to quote and depends on scope and complexity. What we can say is when to book it: after the contracts are feature-complete and the tests pass, not while the design is still moving. Booking an audit against an unfinished contract wastes most of what you pay for.
Who holds the keys after deployment?
You do. Deployment, ownership and any administrative role transfer to you, and the handover includes what each key controls, where it is held and what happens if it is lost. A contract whose administrative key sits with a supplier after delivery is a dependency nobody agreed to.
What happens if something is found after deployment?
That is planned for before it happens: how an issue is reported, who can act, what can actually be done given the upgrade decision already made, and how holders are told. A contract with no answer to this question has an incident response plan consisting of improvisation.
Tell us what the contract has to guarantee.
Describe what must always be true and who has to be able to trust it. If the honest answer is that the requirement does not need a contract at all, you will hear that on the call.
info@eorbitt.com · We reply within one business day.