Blockchain & Web3

Building Production Smart Contracts in Solidity: Security, Testing and Upgradeability

A deployed smart contract is public, holds real value and is hard to change. That makes the engineering discipline around Solidity — more than the language itself — what separates a contract that survives contact with the internet from one that doesn't.

A smart contract is one component of a blockchain system, not the system. A production product also needs a wallet and Web3 layer, a way to read contract state and events, a backend, a database, infrastructure and monitoring — and every one of those layers has its own failure modes. Rivix Systems is a full-stack engineering studio, so we approach Solidity smart contract development as part of that whole system: contracts, Web3 applications, backend services, blockchain data and indexing, infrastructure and security, designed together.

This note covers how we think about it — from architecture, to the vulnerability classes that keep recurring, to testing, upgradeability and audits — and when a blockchain is the wrong tool entirely.

Start with what actually belongs on-chain

The cheapest contract bug to fix is the one in code that never went on-chain. Put on-chain only what needs shared, tamper-evident, permissionless consensus: ownership, balances, settlement, attestations. Search, analytics, notifications and most business rules can live in ordinary cloud services, connected through indexed contract events and signed messages. If nothing in your system needs that kind of consensus, a database and an API are the right answer — and it is better to learn that before writing Solidity.

When blockchain is not the answer

  • One organization controls all the data. If everyone already trusts the operator, a database with an audit log is simpler, faster and cheaper.
  • The data is high-volume or private. Public chains are expensive and transparent by design; raw telemetry, personal data and large files do not belong there.
  • The need is a workflow, not shared consensus. Approvals, notifications and reporting are normal application problems.
  • Users need speed and simplicity above all. Wallet prompts and confirmation times are real friction.

We are engineering-led, not blockchain-for-everything: we would rather tell you in the first call that you do not need a chain than build one that adds cost without adding trust.

Architecture

From Smart Contract to Production Application

One contract is a small part of a working blockchain product. This is the system we design around it.

01

Web / Mobile

User-facing app

02

Wallet / Web3

Connection and signing

03

Solidity contracts

On-chain logic and state

04

Blockchain

Ethereum / EVM network

05

Events / Indexer

Contract events into queryable data

06

Backend / API

Business logic and integrations

07

Database

Off-chain state and analytics

Users never touch the contract directly — they touch a front end and a wallet prompt. Each layer above has a job: the app handles experience, the wallet handles keys and signatures, the contracts hold the trust-critical state, and an indexer turns contract events into data your backend can query, so the app does not scan the chain from the browser. Everything that does not need consensus stays in ordinary services and a database.

Designing these layers together matters: event shapes decided in the contract determine how easily the indexer can serve the app, and gas and confirmation behavior determine what the user interface can honestly promise.

What we build with Solidity

The kinds of systems this practice covers.

These are the categories of work we engineer — capabilities, not a list of past deployments.

Smart contracts

Custom Solidity contracts for the trust-critical logic of your product, specified and tested before deployment.

Tokens and NFTs

Fungible tokens (ERC-20), non-fungible tokens (ERC-721), multi-token contracts (ERC-1155) and tokenized vaults (ERC-4626), built on audited OpenZeppelin components.

Escrow and payments

Conditional release, milestone and settlement contracts, with clear dispute and failure paths.

Staking and governance

Staking, reward and voting mechanisms, with upgrade authority held behind a multisig and timelock.

Web3 applications

Wallet-connected web and mobile apps with signing flows and clear transaction states.

Blockchain integrations

Connecting contracts to existing products, payment flows, oracles and cloud services.

Blockchain data and indexing

Event indexers, queryable APIs and reorg-aware backends so your app reads chain state correctly.

The vulnerability classes that keep recurring

  • Reentrancy — an external call lets the callee re-enter your contract before state is updated. Follow checks-effects-interactions and use a reentrancy guard on functions that move value. See Solidity reentrancy attacks in depth.
  • Access control — missing, overly broad or misconfigured roles. Use least privilege, and never authorize with tx.origin.
  • Unchecked external calls and unsafe delegatecall — handle return values, and treat any delegatecall target as having full control of your storage.
  • Signature replay — signed messages need a domain (EIP-712), a nonce and an expiry, or they can be reused across contracts and chains.
  • Oracle and price manipulation — reading a spot price from a pool that can be moved inside a single transaction is an invitation. Use robust feeds and sanity-check inputs.
  • Front-running and MEV — anything profitable and visible in the public mempool can be reordered around. Design for it rather than hoping it does not happen.
  • Arithmetic edge cases — Solidity 0.8 and later reverts on overflow by default, but unchecked blocks and narrowing type casts can still go wrong.

Build on audited, widely used libraries such as OpenZeppelin for standards like ERC-20, ERC-721 and access control instead of re-implementing them.

Testing: invariants over coverage

Unit tests confirm the behavior you expected. The bugs that lose money come from behavior you did not expect, so add two more layers. Fuzz tests throw generated inputs at a function. Stateful invariant tests call random sequences of functions and assert properties that must always hold — for example, that total supply equals the sum of balances, or that a vault can never owe more than it holds. Foundry supports both natively — see Foundry vs Hardhat for how to choose a framework. Add fork tests to run against real network state and third-party protocols, and run static analysis such as Slither in CI. A high coverage number is a weak signal; a well-chosen invariant that survives millions of random calls is a strong one. For contracts whose value justifies it, formal verification of the critical properties is the next step.

Smart contract security

Our security engineering flow.

Each stage catches a different class of problem. None of them substitutes for the others.

01

Threat modeling

Assets, actors, trust boundaries

02

Development

Audited libraries, least privilege

03

Unit testing

Expected behavior

04

Fuzzing

Generated, unexpected inputs

05

Invariant testing

Properties across call sequences

06

Static analysis

Slither in CI

07

Security review

Senior engineer sign-off

08

Independent audit

Third party, where appropriate

09

Deployment

Scripted, multisig-owned

This is the flow we work through before a contract holds value. Threat modeling comes first because most serious contract bugs are design flaws, not typos, and no amount of later testing fixes a wrong assumption.

Internal security practices do not replace an independent audit. Where a contract will hold meaningful value, we recommend an independent third-party audit as well. An audit is a point-in-time review rather than a guarantee, and it is fastest and most valuable when it lands on a clear specification, thorough tests and documented assumptions — which is what the steps above produce.

Upgradeability is a trust decision

Making a contract upgradeable through a proxy (Transparent or UUPS patterns) lets you fix bugs — and lets whoever holds the upgrade key change what the contract does. That is a trust assumption your users take on, so decide it deliberately.

  • Prefer immutable where the logic is simple and stable.
  • Mind storage layout. Reordering or retyping state variables in a new implementation can corrupt existing storage. Tooling that validates upgrade safety belongs in CI.
  • Use initializers, not constructors. A constructor does not run in the proxy's context, and an unprotected initializer is a well-known way to lose a contract.
  • Put upgrade authority behind a multisig and a timelock, so users get notice before a change takes effect.

Gas, networks and cost

On the EVM, writing to storage dominates cost, so data layout, batching and calldata use matter. Layer 2 rollups are usually far cheaper than Ethereum mainnet, and data-availability changes such as the introduction of blobs (EIP-4844) reduced rollup costs further. Still, choose a network on its security assumptions, liquidity and ecosystem fit as well as fees — cheap transactions on a network your users do not hold assets on do not help them.

Deployment and operations

Treat mainnet deployment like a firmware release. Use scripted, reproducible deployments; test on a public testnet such as Sepolia first; verify source code on the block explorer; and give admin roles to a multisig rather than a single key. Put monitoring and alerting in place, and define a pause and incident procedure before the contract holds value. The same thinking applies to keys in general — see device identity and provisioning security for the hardware-side version of the problem.

Technology stack

What we build with.

Chosen per project against your security, cost and ecosystem needs — not applied by default.

Solidity / EVMOpenZeppelinFoundryHardhatviemethers.jswagmiEthereumBaseArbitrumPolygonNode.jsTypeScriptPythonPostgreSQLRedisSlitherFuzzingInvariant testing
IoT + blockchain

Where hardware meets the chain.

Blockchain adds value to a connected product only at specific points. We engineer those points end to end.

01

Device

Sensor or embedded product

02

Signed telemetry

Firmware signs data with a device key

03

Edge / Gateway

Aggregation and filtering

04

Cloud

Storage, validation, analytics

05

Verified event

A selected, validated business event

06

Smart contract

Proof or attestation anchored on-chain

This is where our hardware, firmware and cloud background matters. Blockchains are a poor place for raw sensor data — it is high-volume, often private and expensive to store — so high-volume telemetry stays off-chain in ordinary cloud infrastructure. Only selected proofs, attestations and business events, such as a verified delivery, a provenance record or a usage milestone, are anchored on-chain.

The trust chain is only as strong as its weakest link: a contract can prove that a signed message arrived, not that the sensor told the truth. That is why device identity, secure key storage and signing in firmware, and validation in the cloud have to be designed together with the contract. See device identity and provisioning security and secure boot for the device side.

The application layer matters as much as the contract

Users never touch the contract directly; they touch a wallet prompt and a front end. Index contract events into a queryable API instead of scanning the chain from the browser, use redundant RPC providers, wait for appropriate confirmations before treating a transaction as final, and design clear states for pending, reverted and rejected transactions. When the product also involves hardware — device identity, signed telemetry or provenance attestations — the firmware, cloud and contract design need to be done together, which is the kind of full-stack work described on our blockchain and Solidity development page.

Common questions

What should go on-chain in a blockchain application?

Only state and logic that genuinely need shared, tamper-evident, permissionless consensus — ownership, balances, settlement, attestations. Search, analytics, notifications and most business logic are better handled off-chain and connected through indexed events and signed messages.

What are the most common Solidity vulnerabilities?

Reentrancy, missing or overly broad access control, unchecked external calls, signature replay, price-oracle manipulation, unsafe delegatecall and proxy misuse, and front-running exposure. Solidity 0.8 and later reverts on arithmetic overflow by default, but unchecked blocks and type casts can still introduce errors.

How should smart contracts be tested?

With unit tests for expected behavior, fuzz tests for unexpected inputs, and stateful invariant tests that assert properties which must hold across any sequence of calls, plus fork tests against real network state and static analysis in CI. Coverage percentage alone is a weak signal.

Should a smart contract be upgradeable?

Only if the ability to change it is worth the trust assumption. Upgradeability lets whoever holds the upgrade key alter the contract's behavior, so it should sit behind a multisig and timelock. Where the logic is simple and stable, immutable contracts are safer for users.

Is a smart contract audit enough on its own?

No. An independent audit is a strong control for contracts that hold value, but it is a point-in-time review, not a guarantee. It works best on top of a clear specification, thorough tests and safe deployment and operations practices.

Next step

Building a Blockchain-Enabled Product?

Solidity smart contracts, a Web3 application, a blockchain integration into an existing product — or an honest answer on whether you need a chain at all.