Setting Up Tenderly Simulator: Where Transaction Verification Breaks
Checks pass on the test network, the release goes to mainnet — and the very first transaction reverts.
You have to investigate using the event in the explorer, where only an error line is visible without context: which internal call returned it and why the state at the time of sending differed from the expected one.
Every such failure after deployment means a halted scenario, manual investigation, and redeployment.
The test network does not reproduce the main thing: the current state of contracts, liquidity in pools, oracle responses, and transaction ordering in the queue. A local run adds its own issues — an outdated state snapshot, different addresses, different gas limits. As a result, the check confirms code correctness but not behavior under real conditions.
Transaction simulation on a mainnet fork removes this discrepancy.
Tenderly Simulator replays the call at a specific block against the real state: you can see exactly where execution stops, how much gas each nested call consumes, and which external addresses respond differently than expected.
Gas profiling immediately shows the margin up to the block limit — it is clear in advance which scenarios will fail on a congested network.
For debugging contracts, this changes the workflow: an error is reproduced and fixed before deployment, not after. We make simulation a mandatory step in the build pipeline — key scenarios are run on every commit, and post-release failures stop being a surprise.
What Simulation Before Sending Gives You: Time, Gas Consumption, Failures
A failure on mainnet is expensive: the transaction passes local checks, goes to the network, and fails on a call to an external contract — gas is spent, state is unchanged, and investigation takes hours.
Based on our engineers' experience, most such failures are reproducible in advance if you run the call against a copy of the current network state.
Pre-send verification is built on simulation: Tenderly spins up a network fork with real balances, storage, and contract code — the transaction executes exactly as the node will see it.
You can substitute the wallet balance, disable a specific external call, roll back state to the desired depth, and see where the logic breaks. The class of failures involving calls to third-party contracts almost completely disappears.
Gas profiling turns consumption into a measurable value: you can see which call or loop takes the most, how the cost grows as input data volume increases, and what margin to reserve. Instead of guessing “by eye” with repeated overpayment — a calculation for a specific scenario.
The verification cycle is shortened as follows: simulation is integrated into continuous integration, and every set of changes is automatically run on the fork.
A report on gas consumption and deviations arrives within minutes, without manual debugging on a testnet; incident investigation follows reproduction steps, not guesses.
Tenderly Simulator Operating Modes: Fork and Local Testbed
operates in several modes, and they differ not by interface but by where the state comes from and whether it can be rolled back.
A mainnet fork takes a copy of the real state at a specific block: actual balances, liquidity in pools, and the behavior of already deployed contracts are visible. A local testbed starts from scratch — predictable accounts, clean history, instant snapshots.
A public testnet is closer to production conditions in terms of participants, but its state is not yours and changes without warning. Your own node gives full control over the environment but requires resources for synchronization. Choosing a mode is always a choice between environment fidelity and run reproducibility.
When to Use Which Mode
| Mode | When It Fits | Limitations |
|---|---|---|
| Mainnet fork | Checking integrations with external contracts, investigating an incident at a specific block | State snapshot: recent mainnet changes are not visible in it, a new fork is needed |
| Local testbed | Debugging logic, failure and edge-case scenarios | No real external dependencies — their behavior is replaced with stubs |
| Public testnet | Deployment rehearsal, checking a bundle of services in a shared environment | Shared access, instability, limited data set |
| Your own node | Regular heavy runs without external limits, closed-source code | Resources for synchronization and maintenance, no ready-made gas profiling tools |
In practice, the modes are combined: logic is checked on the testbed, integrations on the fork, and regular heavy runs on your own node.
Implementation Stages: From Contract Audit to Handover of Procedures
The order of work here is not a formality: if you start by setting up the simulator without understanding external calls, runs will pass successfully on incomplete data, and discrepancies will surface on mainnet.
That is why we go from inventory to procedures — each stage relies on the artifacts of the previous one.
- Audit of contracts and external calls. We build a map of addresses, proxies and their implementations, libraries, oracles, inter-contract calls, and calls to external services. We separately mark places where gas consumption depends on state — simulation is needed there first.
- Environment scheme. We fix networks and mainnet forks, the blocks at which we take state, key storage locations, and access levels. Without this, run results are not reproducible.
- Environment setup. We spin up Tenderly Simulator, connect it to an archive node, publish contracts, and set transaction parameters: sender, gas limits, value.
- Scenario verification. We run successful, failure, and edge paths — permission changes, pause, repeated call, gas exhaustion. For each call, we capture a gas consumption profile and compare it with the calculated one; exceeding the threshold is considered a defect.
- Integration into the continuous integration pipeline. Simulations run on every contract change and check gas consumption, permissions, and expected events. Regression blocks the merge until causes are analyzed.
- Handover of procedures. We provide documentation on environments, run checklists, rules for responding to deviations, and the procedure for updating state snapshots — after that, the team runs the process on its own.
What We Hand Over: Configurations, Diagrams, Procedures
We deliver not screenshots from a screen but a reproducible environment based on Tenderly Simulator: your team deploys the environment, runs the same scenario, and gets the same result.
Everything we configured is in the repository next to the contract code — with change history and without parts closed to you.
- Environment configuration files: separate profiles for mainnet, testnet, and local fork, as well as quick and full run modes.
- Contract relationship diagrams: a map of calls between contracts, proxies, and libraries, points of external dependencies and oracles.
- Access policies: panel roles, keys for running simulations in continuous integration, and permissions to edit configurations.
- Simulation scenarios: a set of transactions on the fork — successful paths, failures, gas consumption by method, and edge states.
- Description of checks: which conditions are considered blocking before deployment and how to read the report on deviation from the expected result.
- Pre-deployment procedure: run procedure, gas consumption threshold, who confirms the result, and what is recorded in history.
- Operations guide: how to update the fork when mainnet changes and what to do in case of a discrepancy.
We hand over a walkthrough session along with the environment: our engineers go through the configurations and scenarios and answer questions. After that, you make changes to the checks yourself — without contacting us for every change.
Practice: We Caught a Failure on a Network Copy Before Sending
Before simulation was implemented, failures were discovered after the fact: the transaction went to a live node, failed on gas exhaustion or a revert in an internal call — and only then did investigation begin.
Reproducing the state took hours of engineers' work, and failed calls reaching the network numbered several dozen per week.
The run was moved to a mainnet copy in Tenderly: state, balances, and oracles are pulled in as of the latest block, and scenarios are reproduced with the same calls that come from the interface.
The gas profiler breaks down consumption by each step, so the bottleneck is visible before deployment, not in logs after it.
What Moving Checks into the Build Achieved
Gas measurements became part of the build: if consumption for a scenario exceeds the reserved margin, the build does not pass the check and the change is returned to the author.
The same run catches failures — an incorrect gas limit, a call to a stopped contract, a broken call order in a batch operation.
Over a quarter, failed calls reaching the network dropped from dozens per week to a handful, and there were no deployment rollbacks at all.
One failure was caught on the copy: a new branch increased gas consumption by a third, and on a live node the operation would have definitely failed — the change was rewritten before deployment.
How Does a Mainnet Fork Differ from a Local Testbed?
A fork spins up a copy of mainnet at a selected block number: state is read from an archive node, so tokens, pools, and oracles are immediately available with real balances and values in contract storage.
A local testbed starts from an empty genesis — it contains only what you deployed and configured. Hence the first difference: on the testbed, integration requires a stub, while on the fork it behaves as in the production network.
State isolation is organized differently. In a fork, writes are local and roll back after the run, but incoming data comes from outside: if you do not fix the block number, a repeated run will produce a different result. The testbed is fully deterministic, but it does not reflect what actually sits on-chain.
The main difference is external calls. An oracle on the fork returns the last published value and does not update, so there is no way to check a scenario involving a sharp price movement.
Bridges, cross-chain messages, and calls to external services do not respond: on the testbed they are replaced with a stub with predefined behavior, and this is suitable for negative checks but not for estimating pool depth and slippage.
What to choose for the task. Contract logic, access rights, edge values — the testbed: runs are faster, gas consumption is predictable, and nothing is pulled from outside.
Integration with third-party protocols, gas profiling on real state, liquidations, and arbitrage scenarios — only the fork, otherwise the numbers are not about the network.
Practical takeaway: the discrepancy arises not because of the mode but because of frozen external sources — so fix the fork block number, and anything that depends on live oracles should either be emulated or marked as unverified.
Common Technical Objections About Transaction Simulation
Before work starts, the client usually asks three questions: whether simulation matches the real network, what to do with already deployed contracts, and how to integrate checks into the current process. Below are short answers that most often resolve doubts at the estimate stage.
How closely does the simulation result match the real network?
The run happens on a fork of the current state: contract code, balances, and storage are taken from the network, not from an approximate model. The discrepancy remains in two places — private transactions in the mempool and competition for gas between blocks.
That is why we run the scenario with a fee margin and across several consecutive blocks: if the outcome depends on transaction ordering, it is recorded as a risk before release, not after.
Contracts are already deployed — will simulation work?
Yes. The fork is spun up from the current contract address; there is no need to rebuild and redeploy. We connect the published code, add verifiable scenarios, and profile gas for each call.
If the sources do not match the bytecode — a common situation with third-party contracts — we report it immediately: this is a limitation of the method, not a setting that can be closed later.
How do we integrate checks into our process and how long will it take?
Checks are formalized as a separate build step: a set of scenarios runs on the fork in the continuous integration pipeline, and a failure stops the release before deployment. From you, we need repository access and the ability to run containers — we cover the rest.
In terms of scope: the basic environment on an existing project is deployed within a few days, and expanding coverage to critical paths proceeds in iterations as changes occur.
Let's Discuss the Task: We'll Build a Simulation Scheme for Your Pipeline
To make the estimate rely on facts rather than general words, we need a brief snapshot of your environment: networks and forks, repository, current check scheme, and what already runs automatically.
Describe the task in free form — we will come back with a staged implementation plan and a working verification scheme.
What we clarify at the start:
- Which networks and forks we cover with simulation, and which we leave for live transaction sending.
- How the pipeline is structured now: what runs on every commit, what runs before release, and where manual steps remain.
- Which scenarios are critical: transfers, liquidations, calls to external contracts, proxy upgrades.
- Which access permissions exist and which are needed: a key to Tenderly Simulator, repository and build agent rights, environment restrictions.
- What we consider a check failure: gas consumption threshold, state discrepancy, absence of an expected event in logs.
- Which artifacts must be preserved: gas profile, simulation log, state difference report.
In response, we send an implementation plan with stages and timelines, an estimate of work by blocks, and a verification scheme with rollback points. Next comes a conversation with the engineer who will lead the work: 40–60 minutes to the point, without presentations. Describe the task — we will come back with an estimate and a plan.







