Testing smart contracts on testnet

Before a mainnet release, the cost of an error is at its highest: fixes are already applied to live operations. We set up turnkey smart contract testing on testnet — scenarios, repeated runs, and a report with reproducible results. The team gets verified logic and a clear list of risks before release.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1375
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1349
  • B2B Advance company logo design
    B2B Advance company logo design
    794
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1087
  • AIDER company logo development
    AIDER company logo development
    1032
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1129

Smart Contract Testing: Where Code Breaks Before Release

A smart contract breaks not on arithmetic, but where its behavior diverges from the expectations of the calling party.

Errors in contract logic almost always live at the trust boundary: an address is assumed to be "own" and receives control, although behind it there may be foreign code or a contract substituted after deployment. These are exactly the places smart contract testing checks first.

The second bottleneck is the order of calls. If state changes after an external call, a participant manages to re-enter the function before final balance settlement.

Transaction reverts return contract state, but do not cancel what the external recipient has already executed, so moving all checks to "before" is cheaper than later explaining a discrepancy in accounting.

The third is behavior when an external data source fails. While the source responds, the logic looks correct; as soon as the response is delayed or stale, it turns out the error branch simply is not written.

A reliable contract in this case either halts the operation entirely or degrades predictably, but does not count on a stale value.

Separately we look at the computational load limit: a loop over an array that passes on a test network of ten records hits the limit on real volume and drops the operation.

Pre-release checks must cover this too — otherwise transaction reverts turn into a normal scenario for the user. We examine how these places are covered before release.

What Smart Contract Testing on Testnet Gives

Testnet is a copy of mainnet with real gas economics and the same rules for including transactions in a block, but without the risk of losing funds.

Therefore it checks what a local testbed does not reproduce: the order of calls between contracts, gas limits on large data arrays, role permissions, the operation of oracles and external services, and actual gas consumption per operation.

The first thing testnet removes is a whole class of failures that simply does not manifest in a local environment.

These are re-entrancy into a function during an external call, a race of two transactions for the same state, lack of gas on a loop over a growing list, and absence of rights to pause or update logic.

Each such failure on mainnet means a protocol halt and manual investigation — on testnet it is found before rollout.

We build scenario coverage not around the "successful" path, but around failures: unavailable data source, partially rejected transaction, repeated send, boundary amounts, and empty sets.

One run in Hardhat with rollback to a state snapshot takes minutes, so a fix is checked in one cycle, not a day of rebuilding the local testbed.

Hence two measurable consequences. Iteration time to a working version is reduced from dozens of runs to a few, and mainnet rollout becomes a repetition of an already rehearsed sequence: the same migration steps, the same deployment order, the same post-launch checks.

Comparison of Testing Environments: Local Testbed, Testnet, Network Fork

No testing environment gives speed, realism, and reproducibility at once, so the choice depends on what exactly we check. If we need to run hundreds of call variants and ensure invariants hold on any input, instant state rewind and full control over block time matter more.

When a contract exchanges data with third-party protocols, access to their real state becomes decisive — then a mainnet fork becomes the working environment.

In practice this is not choosing one environment, but a ladder of checks: logic is covered on a local testbed, interaction — on a fork, public testnet remains for integration scenarios and demonstration. Below is a comparison of testing environments by suitability and environment limitations.

Testing environment When suitable Environment limitations
Local testbed Running logic, boundary values, gas consumption, branch coverage; step-by-step debugging No real counterparties and their liquidity; behavior under block competition is not reproduced
Public testnet Integration scenarios, role and deployment checks, demonstration to client State cannot be rolled back, nodes may be overloaded, prices and pools do not correspond to mainnet
Mainnet fork Checking integration with live protocols at the current block, replay of real transactions Requires access to an archive node; snapshot becomes stale when the network updates, external services may be unavailable

Bottom line: local testbed gives speed and repeatability, testnet — publicity, fork — realism of conditions. By combining them, we get coverage where a found defect is reproduced with one command, not "sometimes on testnet".

How We Build the Verification Process from Audit to Handover

A smart contract cannot be rolled back after publication: an error in access rights or payout calculation remains on the network forever and costs frozen funds.

Therefore we conduct verification not in one burst, but as a sequence of steps where each next relies on the results of the previous. Below is how this chain looks from logic review to handing you a ready set of checks.

  1. Logic audit. We read the contract together with your team: analyze roles, rights, points where state changes and funds move. We record invariants — statements that must be true under any development of events. Output — a risk map with priorities, not a general list of remarks.

  2. Run scheme. From the risk map we assemble a matrix: which scenario we run, in which environment, what we consider success, and what failure. We cover not percentages of code lines, but all paths where funds and access are involved.

  3. Environment setup. We bring up a local node and a copy of mainnet at the required state: the same balances, the same external data sources, the same point in time. The run repeats real conditions, not hothouse ones.

  4. Failure scenarios. We check not only the successful path: a call from under another role, a repeated transaction, an unavailable external data source, exhaustion of the computation limit. Each scenario — a separate reproducible run with a pre-described expected result.

  5. Suite stabilization. We run the matrix repeatedly with random input data to catch rare combinations. A found defect is closed with a fix and a re-run of the entire suite as a whole — the fix must not break neighboring paths.

  6. Handover of results. We deliver a report: covered scenarios, found defects and their status, launch commands, deployment scheme, and a post-publication check-list. The suite remains with you — it can be run before every contract change.

Documentation, Diagrams, and Regulations for Repeated Runs

After a series of runs on testnet, the client should retain not a deployed contract, but a kit by which the environment is brought up anew and without the author. We hand it over together with smart contract testing results.

  • Environment configurations — node and compiler versions, network parameters, test accounts and their roles: the run does not depend on who launches it.
  • Contract and dependency map — which contract calls whom, deployment order, addresses on testnet, involved external services.
  • Key access policies — who signs transactions, which keys exist only for testnet, how secrets are stored when the responsible person changes.
  • Run regulations — state preparation, deployment, scenarios, post-checks, rollback procedure on failure.
  • Defect report — reproducible scenarios with expected and actual result and assessment of impact on contract logic.
  • Pre-mainnet checklist — what must pass, and what allows an exception with written justification.

Afterwards the documents work without us: a new engineer brings up the environment by instruction, a repeated run gives the same result. Open defects remain in the report with impact assessment — the decision on mainnet transfer is made on facts, not on participants' memory.

Case: How Defects Were Caught Before Mainnet Release

Project — a smart contract for distributing rewards among pool participants: share calculation, transfer of accumulated balance between periods, emergency halt of payouts.

Checks before this covered only the successful path: calculation on isolated values, without transaction sequence and without competition for the same remainder. On mainnet such a suite misses ordering errors — state manages to change between condition check and result write.

What Was Revealed on Testnet

Deployed the full circuit on Foundry: virtual chain with block time control, balance substitution, and repeated calls from different roles.

Out of 46 scenarios, 23 behaved differently than expected: discrepancy in accruals with two withdrawals in one block, bypass of restriction via pause, loss of remainder when role changes. Five defects produced incorrect distribution, three blocked withdrawal for part of participants.

What Changed After Rollout

Share of covered branches grew from 61% to 94%, the suite runs in 4 minutes instead of 40 — verification happens on every change, not before release. Each found defect received a reproducing scenario and remained in the suite as a permanent check.

During the operation period, no accrual discrepancies were recorded, version rollback was not required. Behavior on mainnet matched test: the same input gives the same result, because exactly the code that goes to the network is run.

How Does Public Testnet Differ from Local Testbed?

These are not competing options, but two environments for different questions: local testbed gives run repeatability and logic review, public testnet — realism of conditions and behavior of external dependencies that cannot be reliably modeled in isolation.

Local testbed is a copy of mainnet at a fixed block. Confirmations arrive instantly, state is captured by snapshot and returned to the same point, and failures — lack of gas, transaction revert, block reordering — we reproduce intentionally.

The same run gives the same result: a test failure points to an error in code, not to external randomness.

Public testnet answers the question whether the system will withstand real conditions. Here are real oracles, liquidity pools, bridges, and third-party contracts, the same transaction packaging order and the same competition for gas.

The price of realism — manageability: state changes between runs, an external protocol may update or become unavailable, some test tokens have to be obtained manually, and yesterday's scenario is no longer reproduced literally.

Therefore we separate environments by tasks, rather than choosing one. The bulk of runs goes locally: logic, access rights, boundary amounts, failure handling.

To testnet we move what depends on the external world — integrations with oracles, liquidity, bridges — and keep a separate set of acceptance scenarios. Testnet does not replace audit: it shows that the system works in real conditions, not that it is invulnerable.

Common Technical Doubts About Test Networks and Coverage

Line coverage answers the wrong question: continuous passage of lines in a contract without branches proves nothing. More important is branch coverage and invariants — sum of balances, role rights, impossibility of repeated withdrawal. An invariant fixed in a test catches what no code traversal scenario will catch.

Testnet does not reproduce mainnet state: different dependency addresses, unavailable external sources, different behavior under overload. Therefore the final run we do on a fork of the current state — there contracts see the same dependencies as in the production network.

How to understand that coverage is sufficient?

We look not at the percentage, but at the list of uncovered sections and at failure classes. If a path is uncovered where an external call returns an error or a role is revoked, that is a hole regardless of the overall number.

We keep a list of invariants and check them after each scenario: changing one condition must not break balance accounting.

What to do with external data sources?

On testnet an oracle may be silent or return arbitrary values — the scenario turns out unreliable. We substitute a controlled source and run deviations: price fell sharply, response hung, stale data arrived. This way protective mechanisms are checked, not only the happy path.

Can already deployed contracts be covered by tests?

Yes. We take a fork of the production network — state, addresses, and dependencies in place, no need to change code.

We run scenarios against real dependencies, then check logic update and rollback to the previous version: a deployed contract should also have a verified path back.

Let's Discuss Your Task and Come Back with a Verification Plan

To come back with an estimate and a verification plan, rather than general words, several inputs are enough. The more precisely you describe the contracts and environment, the fewer assumptions we build into stages and the faster we move to work.

What to send to start:

  • contract addresses on testnet or a repository with source code and deployment scripts;
  • description of access roles: who is owner, who is allowed to stop the contract, change parameters, withdraw funds;
  • external dependencies — oracles, bridges, companion tokens, and calls to third-party protocols that logic depends on;
  • scenarios that have already broken or raise doubts: manual operations before release, rare logic branches, state migrations;
  • current state of checks: whether there are automated tests, tests on a copy of mainnet state, static analyzers, reports of previous checks;
  • reporting requirements — whether a formal verdict on criticality of findings is needed or a list of fixes for your team;
  • accesses: testnet, deployment account keys, node request limits.

Then the first step is ours. We review the inputs, clarify disputed points, and come back with a plan: which checks we do, in what order, what exactly you get as output — a set of scenarios, environment configuration, and a report with priorities. If something is missing, we will say immediately, before work starts.