Smart Contract Development: Design, Testing, Audits and Operations

How we design, test and operate Solidity contracts: upgradeability trade-offs, fuzzing and formal review, independent audits, multisig admin keys and incident response.

A chain of linked blocks, the middle one bound with bands and closed by a seal, like a deployed contract.

You need code that holds or moves value on a public blockchain, such as a token with a vesting schedule, an escrow, a vault, a marketplace or a treasury. Smart contract development isn’t like most software work. The code is public, attackers get to keep whatever it holds, and a mistake that’s been deployed may be impossible to patch. That’s why most of the effort goes into design, testing and review rather than into writing Solidity.

A well-built contract is small and has a written specification. Its invariants are fuzzed, and formally verified where the stakes justify it. It has been through at least one independent audit. Its admin powers are few and documented, and they’re held by a multisig behind a timelock. There’s also a rehearsed plan for the day something goes wrong.

What we build on-chain

  • Tokens that follow established standards, such as ERC-20 for fungible tokens and ERC-721 or ERC-1155 for NFTs, with minting rules, vesting and distribution.
  • Escrow and payment flows that release funds once agreed conditions are met.
  • Vaults and staking contracts, following ERC-4626 where it fits, with reward accounting that survives rounding and edge cases.
  • Marketplaces, auctions and royalty logic.
  • Governance and treasury contracts for voting, timelocks and controlled spending.
  • Smart accounts and modules for wallets that need spending limits, recovery or sponsored fees.

The contract is rarely the whole product. Around it you need an indexer, a backend, admin tools and an interface people can use, all covered in blockchain development, and usually a wallet, which is the subject of crypto wallet development. If you’re launching a token, cryptocurrency development covers the wider picture around it.

Choosing a chain and a language

Our default is Solidity on the EVM, meaning Ethereum, its layer-2 rollups and other EVM-compatible networks. The EVM has the most mature tooling and well-reviewed libraries such as OpenZeppelin Contracts. It also has the broadest wallet support and the largest pool of independent auditors. Vyper is a reasonable alternative if your team values its deliberately smaller language.

Other chains fit other needs. Solana suits high-frequency, low-value activity, but its Rust programs use a different account model, where missing account validation is a classic bug. Move-based chains build asset safety into the type system, though the tooling and communities around them are smaller. App-specific chains make sense when you need your own fee model or validator set, and they come with real operational work. Pick the chain where your users, assets and liquidity already are, not the one with the best headline throughput.

Sometimes the right answer is no chain at all. If every participant already trusts one operator, the data has to stay private, or transactions need to be reversible, a conventional backend with an audit log will be simpler, cheaper and easier to change.

Upgradeability or immutability

This is the first real design decision, and every option has a cost.

A balance weighing a sealed immutable contract against a proxy with swappable implementations, a timelock at its pivot.
  • Immutable contracts give users the strongest guarantee, because the rules can’t change. The flip side is that a bug is permanent, and fixing it means deploying a new contract and migrating users and funds.
  • Upgradeable proxies (transparent, UUPS or beacon) let you fix bugs and add features. In exchange, the upgrade authority becomes a trust assumption and an attack target. Storage layout also needs discipline: append-only variables or namespaced storage, initializers instead of constructors, locked initializers on implementation contracts, and automated layout checks before every upgrade.
  • Middle paths are often the best fit. That might be an immutable core whose parameters can only move within hard-coded bounds, peripheral modules that can be replaced, or upgradeability behind a timelock with a plan to remove it once the system has matured.

Pausing is a useful circuit breaker. Still, whoever holds the pause role has power over users’ funds, so that role deserves the same governance as the upgrade role.

Testing, fuzzing and formal review

We begin with a written specification and a list of invariants, the properties that must always hold. For a vault, those might be that total shares equal the sum of all balances, that no account can withdraw more than it deposited plus earned rewards, and that only the timelock can change fees. Testing then comes in layers:

A sealed contract hit by random inputs from every side while a level on top stays true, ringed by layers of tests.
  • Unit and integration tests in Foundry or Hardhat, covering every function, revert path and role.
  • Fork tests that run against real deployed contracts, such as oracles and exchanges, using live chain state.
  • Fuzzing and invariant testing with Foundry, Echidna or Medusa. These throw long random sequences of calls at the system and check that the invariants still hold.
  • Static analysis with tools such as Slither on every commit, plus mutation testing to confirm that the tests actually catch deliberately injected bugs.
  • Formal verification or symbolic testing for the few properties where a counterexample would be catastrophic, using tools such as the Certora Prover, Halmos or the Solidity compiler’s SMTChecker.

Code review goes after the known bug classes: reentrancy, broken access control, oracle and price manipulation, rounding errors, signature replay, front-running exposure, unsafe external calls, and loops that grow without bound. We build on audited libraries rather than reimplementing standards, and patterns such as checks-effects-interactions are the default.

Independent audits and gas optimization

We write and test contracts. Auditing them is a job for independent firms, because reviewing your own code is necessary but doesn’t count as an audit. We prepare the handoff carefully, with a frozen commit, the specification, documentation, the test suite and a list of known risks, since auditors who understand the system find deeper issues.

If a contract will hold significant value, we recommend following the first audit with a second independent audit or a public audit contest. The auditors review the fixes, the reports get published, and a bug bounty runs after launch. Even then, an audit lowers risk. It doesn’t certify that the code is safe.

Gas optimization only starts once the code is correct. Storage writes dominate costs, so we pack variables, use constants and immutables, prefer calldata for external inputs, use custom errors, emit events for data that’s only needed off-chain, and avoid loops that grow with the number of users. On rollups, fees include a share of the cost of publishing data to the base layer, which means compact calldata can matter as much as execution. Gas snapshots in continuous integration catch regressions. In security-critical code we won’t trade clarity for small savings, and inline assembly needs a strong reason plus extra review.

Admin keys, multisig and incident response

Each privileged role is a way to lose funds, whether it upgrades, pauses, mints, sets parameters or withdraws fees. We keep the list short, split the roles so that no single account can do everything, and never leave a role on a single externally owned account. Privileged roles sit with a multisig such as Safe. Its signers use hardware wallets held by different people in different places, and the threshold is set to survive a lost key, yet resist one compromised signer.

Upgrades and parameter changes go through a timelock, so users can see them coming and exit if they disagree. Signers verify what they’re signing independently of the web interface, since compromised interfaces have tricked multisig signers into approving malicious transactions. Signer duties and the ceremonies for generating and storing keys are documented.

We prepare incident response before launch:

  • Monitoring that alerts on admin calls, large outflows, oracle deviations and unusual call patterns.
  • A runbook that says who can pause, how quickly and with which keys.
  • A communication plan for users and partners.
  • Contacts at security response groups, exchanges and stablecoin issuers, which can sometimes freeze stolen funds.
  • A post-mortem and a remediation plan once the incident is contained.

The runbook gets rehearsed on a testnet. Nobody should be using it for the first time during a real incident.

How a smart contract development engagement works

First come the specification and a threat model, written with your team by a business analyst and the engineers who’ll build the contracts. Implementation proceeds through short iterations, with tests written alongside the code. Internal review, testnet deployment and integration with the frontend and backend follow.

Next is the external audit, then fixes and re-review, and finally a scripted mainnet deployment with verified source code and monitoring switched on. You get regular demos and written progress reports throughout. At the end you have the repository, specification, test and fuzzing suites, deployment scripts, runbooks and audit reports.

Frequently asked questions

Can you audit our existing contracts?

We can review them, fix what we find and get them ready for audit. The audit itself should come from an independent firm that didn’t write or fix the code, and we’ll help you scope it.

Should our contracts be upgradeable?

For simple, well-tested logic, immutability is a strong promise to users. A complex or young system is usually better off with a timelocked upgrade path controlled by a multisig, plus a plan to reduce that power over time.

Which chain should we deploy on?

Wherever your users, assets and partners already are. For many products that’s Ethereum or one of its layer-2 networks. We recommend other chains when their trade-offs genuinely suit the product.

What happens if a bug is found after deployment?

You follow the runbook: assess, pause if the design allows it, fix through an upgrade or a migration, and communicate clearly. What you can do at that point depends on design decisions made long before launch, which is why we make them deliberately.

When should we book an audit?

Early. Reputable auditors are often booked well in advance, so reserve a slot as soon as the scope is clear and plan the code freeze around it.

If you’re planning a contract that will hold real value, bring in the people who’ll test and operate it at the first design conversation. Send us a description of what the contract has to do, and we’ll sketch how we’d build it, including whether it needs a blockchain at all.