A failed Web3 deployment does not usually begin with a spectacular exploit. It begins with a green build, a passing unit test, a deployment script that nobody wants to touch, and an assumption that the production chain will behave like the local node.
That assumption is where the financial damage starts. A pricing contract reads stale data. A forked test environment silently differs from mainnet. An upgrade authority is left with excessive privileges. An indexer misses an event and the application displays a balance that never existed. By the time the failure reaches a user, the engineering team is no longer debugging software. It is reconstructing an attack surface while capital moves through it.
I have audited enough protocols to distrust the phrase “production-ready” when it is used without evidence. A serious Web3 development agency should be able to show how code moves from a developer’s machine to a live network, which controls stop a reentrancy attack or flash loan manipulation, and how the team proves that its assumptions survive contact with real chain state.
The pipeline is the product. The Solidity is only one component.
The post-Truffle era: why the toolchain changed
For years, Truffle and Ganache were the default entry point for EVM development. They were familiar, documented, and embedded in countless tutorials. That familiarity became a liability when the surrounding ecosystem moved faster than the tools.
Consensys archived the legacy Truffle and Ganache development suites on December 20, 2023. That decision did not make older projects disappear, but it changed the baseline for new work. A team hiring Web3 developers today should be asking why a delivery pipeline still depends on abandoned defaults, not treating legacy familiarity as evidence of competence.
The modern stack is more fragmented, but also more explicit. Hardhat remains a practical TypeScript-centered environment for teams that want a configurable JavaScript ecosystem, plugin support, deployment scripting, and integration with existing application tooling. Foundry takes a different route. It is a Rust-based toolchain built around fast compilation and command-line workflows:
- Forge handles unit tests, fuzz testing, and invariant testing.
- Cast provides a command-line interface for reading from and writing to EVM contracts.
- Anvil runs a local Ethereum node and can fork existing chain state.
- Chisel acts as a Solidity REPL for quick experiments and low-level inspection.
The choice is not simply “Hardhat versus Foundry.” In practice, a competent Web3 engineering team may use both. Hardhat can fit naturally into a TypeScript-heavy repository with frontend integration and deployment plugins. Foundry can execute large fuzzing campaigns and invariant suites at a speed that makes repeated testing less painful.
The question is whether the combination is deliberate or accidental.
A blockchain software house that claims to use a modern stack should be able to explain:
1. Which framework owns deployment configuration and network settings.
2. Which framework owns the authoritative test suite.
3. How contract artifacts are generated and versioned.
4. How deployment addresses are recorded across testnets and mainnet.
5. Which checks run before a pull request can be merged.
6. How a failed deployment is rolled back or isolated.
7. Who can authorize an upgrade after the code is deployed.
A repository with three frameworks, four deployment scripts, and no clear source of truth is not sophisticated. It is an incident waiting for a timestamp.
Hardhat and Foundry solve different problems
Hardhat is often easier to introduce into a full-stack product because TypeScript is already present in the frontend, backend, and automation layers. Developers can write deployment and integration logic in the same language used for API clients and test orchestration. That lowers friction for teams building a complete application around contracts.
Foundry is more uncompromising. Its command-line workflow encourages developers to interact directly with the EVM rather than hide every operation behind a framework abstraction. This is valuable during security work. When I investigate a suspicious behavior, I want to know exactly which calldata was sent, which storage slot changed, and which account signed the transaction. Abstractions are useful until they conceal the attack vector.
A mature pipeline may therefore use:
| Delivery concern | Hardhat | Foundry |
|---|---|---|
| TypeScript application integration | Strong fit | Possible, but less native |
| Fast Solidity unit testing | Adequate | Strong fit |
| Fuzz and invariant testing | Available through tooling | Core workflow through Forge |
| Local EVM node | Plugin-based or external | Anvil included |
| Transaction inspection | Scripts and plugins | Cast provides direct CLI access |
| Developer ergonomics | Familiar to JavaScript teams | Favored by low-level EVM workflows |
| Main risk | Plugin and configuration sprawl | Teams may under-document command-line conventions |
Neither framework prevents privilege escalation. Neither detects every oracle failure. Neither turns an inexperienced team into a secure one. Tools reduce the cost of disciplined engineering; they do not manufacture discipline.
A modern stack is not defined by the number of tools in the repository. It is defined by how little ambiguity remains when a test fails.
Automated security: CI/CD as a gate, not a dashboard
The most revealing artifact in a Web3 development engagement is often not the architecture diagram. It is the CI configuration.
A serious pipeline should make insecure code difficult to merge, deploy, or upgrade. That means automated workflows—often through GitHub Actions or an equivalent CI system—run on every meaningful change, not only before a public launch. The exact sequence varies, but the security logic should be visible.
A typical workflow includes:
1. Compilation and formatting checks.
The pipeline verifies compiler versions, dependency resolution, formatting, and reproducible builds. Compiler drift is not a cosmetic problem when bytecode, optimizer behavior, or ABI output changes.
2. Unit and integration tests.
These cover expected behavior across token transfers, access control, liquidation paths, oracle updates, withdrawals, and failure conditions. A test that only checks the happy path is a decorative object.
3. Fuzz testing.
Inputs are varied beyond the examples a developer thought to write. Amounts become zero, maximal, negative at the interface boundary, oddly rounded, or deliberately inconvenient. Addresses are repeated. Call order changes. The aim is to expose assumptions.
4. Invariant testing.
The team defines properties that must remain true across arbitrary sequences of calls. Total liabilities should not exceed collateral. Unauthorized accounts should not change critical configuration. A user should not withdraw more than their recorded claim.
5. Static analysis.
Tools such as Slither and Mythril can identify common classes of vulnerability, suspicious control flow, reentrancy risks, arithmetic problems, and dangerous usage patterns. They are valuable precisely because they are tireless and unsentimental.
6. Dependency and compiler checks.
The pipeline tracks imported contracts, compiler versions, and known issues in dependencies. A contract can be locally correct and still inherit risk from an unexamined library or a careless upgrade.
7. Deployment simulation.
Scripts run against a controlled environment before any production transaction is signed. Parameters, roles, initialization calls, and expected addresses are verified rather than copied from a terminal history.
Static analysis is not a substitute for a manual smart contract audit. It cannot understand every economic invariant, governance assumption, oracle dependency, or cross-contract interaction. It also produces findings that require judgment. Blindly suppressing warnings is not triage. It is evidence destruction.
The same applies to test coverage. A high coverage percentage can coexist with a completely untested economic exploit. If the test suite executes a line but never challenges the authority model around that line, the metric is giving the team emotional support, not security evidence.
What a delivery pipeline should record
When I review a pipeline, I look for artifacts that allow another engineer to reconstruct what happened:
- The exact commit used to compile the deployed bytecode.
- Compiler and optimizer settings.
- Dependency versions and lockfiles.
- Test commands and their exit codes.
- Static-analysis reports, including accepted findings and their rationale.
- Deployment parameters and network identifiers.
- Contract addresses and emitted events.
- The account or multisig that performed each privileged action.
- The transaction hash for deployment, initialization, and role assignment.
- A clear distinction between testnet and mainnet secrets.
This is where many outsourcing blockchain development arrangements become vague. The client receives a dashboard, a repository snapshot, and a promise that the deployment was successful. Then a production issue appears, and nobody can state which artifact was deployed, which initializer ran first, or whether the proxy administrator was transferred.
An agency does not need to publish every internal document. It does need to maintain an audit trail that survives staff turnover and a hostile question from a security reviewer.
Mainnet state simulation: local success is cheap
Local development networks are useful because they are fast and controllable. They are also dangerously polite.
A clean local chain has no years of accumulated state, no adversarial token balances, no abandoned approvals, no malformed historical data, and no external protocol that changes behavior at an inconvenient moment. A contract that behaves perfectly in that environment may still fail when connected to a live ecosystem.
This is why mainnet state simulation matters. Tools such as Anvil and Tenderly allow teams to fork a network state and simulate transactions against conditions that resemble reality. Developers can inspect the result without publishing the transaction. They can impersonate specific addresses in a controlled environment, test interactions with existing protocols, and observe whether the transaction reverts, drains value, or changes storage in an unexpected way.
The fork is not magic. It is a model with boundaries. RPC providers may prune or limit historical state. External systems may depend on off-chain processes that a fork does not reproduce. A simulated transaction cannot guarantee that a live transaction will receive the same gas conditions, oracle response, mempool ordering, or liquidity.
But refusing to simulate against realistic state is worse. It means the team is testing a theory of the protocol rather than the protocol’s operating environment.
A practical fork-based investigation
Suppose a contract accepts collateral, reads a price feed, calculates a borrowing limit, and allows the user to withdraw an asset. The obvious unit test deposits collateral and checks the expected borrowing amount. The useful fork test asks more hostile questions:
1. What happens when the price feed is stale but not empty?
2. What happens when the reported price is valid according to its type but economically absurd?
3. Can a user manipulate a spot market immediately before the oracle read?
4. Does a flash loan change the state used by the price calculation?
5. Does the contract round in favor of the protocol or the borrower?
6. Can a privileged account alter the oracle address without a timelock?
7. Does the system remain solvent when several users execute the same path in one block?
8. What happens if an external token returns no boolean value or behaves inconsistently with ERC-20 expectations?
This is where flash loan manipulation becomes more than a phrase in a security report. The attack vector is often not a single vulnerable function. It is the interaction between temporary liquidity, a weak price source, and a state transition that assumes the market cannot move quickly.
Tenderly-style simulation is also useful for debugging failed transactions. A trace can expose the internal call sequence, the exact revert location, and the point at which storage diverged from expectation. That is the difference between “the transaction failed” and “the authorization check in the upgrade path rejected the caller after the proxy implementation had already been selected.”
The latter is actionable. The former is a support ticket.
If the test environment contains no adversarial state, the team is not testing security. It is rehearsing a product demo.
Simulation must include privilege boundaries
Many teams simulate user flows and ignore administrative flows. That is backwards. The highest-impact failures often sit behind privileged functions:
- Changing the oracle or data-feed endpoint.
- Updating risk parameters.
- Pausing or unpausing withdrawals.
- Assigning roles.
- Upgrading an implementation.
- Migrating funds.
- Changing fee recipients.
- Modifying allowed collateral or supported chains.
For each privileged action, the simulation should identify the authorized account, the required transaction sequence, the expected event, and the failure mode for unauthorized callers. If the role is controlled by a multisig, the test should reflect the operational process rather than pretending that one private key is the production governance model.
A security boundary that exists only in a diagram is not a security boundary.
Indexing and data retrieval: the read side is part of the protocol
A dApp can have correct contracts and still show users incorrect information. The failure often sits in the read path.
On-chain state is deterministic, but querying application-relevant history directly from an RPC endpoint can be slow, inconsistent across providers, and expensive to reconstruct. Web3 teams therefore build indexed data layers for balances, positions, transfers, governance events, and protocol activity.
The Graph protocol is one common approach. A subgraph typically defines:
- A
subgraph.yamlmanifest describing data sources, networks, contract addresses, ABIs, and event handlers. - A
schema.graphqlfile describing the entities and relationships exposed to the application. - Mapping logic that transforms emitted events into indexed entities.
- Deployment and synchronization procedures for testnet and production environments.
This looks like backend plumbing until an application makes a financial decision based on it. Then indexing becomes part of the trust boundary.
A missed event can hide a deposit. A duplicate handling bug can inflate a balance. A reorg can leave an entity in a state that no longer matches the canonical chain. A schema that stores a token amount in an insufficient numeric representation can corrupt the user interface without producing an obvious exception.
I have seen teams treat subgraphs as disposable presentation layers. That is defensible for a marketing page. It is reckless for liquidation dashboards, accounting interfaces, reward claims, and admin consoles.
The indexer needs its own threat model
The read side should be tested against chain behavior, not only against expected event fixtures. A practical review asks:
- What happens when a block is reorged?
- How are removed or invalidated events handled?
- Is the indexer idempotent when a handler is retried?
- Can an entity be reconstructed from canonical events?
- Does the frontend cross-check critical values against direct RPC reads?
- Are pending and finalized states distinguished?
- What happens when the subgraph lags behind the head of the chain?
- Are contract upgrades reflected in the manifest and ABI?
- Is there a recovery procedure for a failed deployment?
For high-value operations, the application should not blindly trust a single indexed response. A user may see a withdrawal as available before the chain state confirms it. An operator may act on a stale liquidation position. A governance interface may display an obsolete quorum calculation.
The solution is not to abandon indexing. It is to define where indexed data is authoritative, where it is merely a cache, and where a direct chain read is required before a transaction is signed.
This is also an area where an agency’s architecture can become unnecessarily expensive. Teams sometimes introduce multiple indexing systems without specifying the ownership of data. The result is a distributed disagreement: the frontend reads one source, the analytics layer reads another, and the operations team trusts a third. When numbers diverge, engineers compare dashboards instead of inspecting events.
That is not decentralization. It is uncoordinated caching.
OpenZeppelin and the limits of standardized code
OpenZeppelin Contracts is the baseline I expect to see in most production smart contract systems. Its implementations cover widely used standards and patterns, including ERC-20 fungible tokens, ERC-721 non-fungible tokens, ERC-1155 multi-token contracts, access control, pausability, and upgradeable designs.
Using a recognized implementation reduces the amount of original code a team must defend. That is valuable. Every custom token transfer hook, bespoke role system, and improvised proxy pattern creates another place for an assumption to fail.
But “we use OpenZeppelin” is not a security argument. It is a starting condition.
The integration can still be wrong:
- An initializer may be omitted or callable by the wrong account.
- A role may be granted to an externally owned account with no operational backup.
- An upgradeable contract may expose an unsafe upgrade path.
- A token hook may create reentrancy across an external call.
- A pause mechanism may protect transfers but not withdrawals or administrative migration.
- A contract may use a library version whose behavior does not match the team’s assumptions.
- Storage layout may be corrupted during an upgrade.
- A standard interface may be implemented while economic behavior remains unsafe.
Upgradeable contracts deserve particular suspicion. They create a privilege escalation surface that is often described as flexibility. In production, flexibility means someone can change the code after users have deposited funds. That may be necessary, but it must be constrained by role separation, multisig control, timelocks, monitoring, and a documented emergency procedure.
The implementation address, proxy administrator, upgrade authority, and initialization state should all be visible in the deployment record. If the agency cannot answer who can upgrade the contract at 02:00 UTC on a Sunday, the system is not operationally mature.
Standards do not cover economic exploits
ERC-20 compliance does not prove that a lending market is solvent. ERC-721 compliance does not protect a marketplace from signature replay. ERC-1155 compliance does not make batch accounting safe. A standard defines an interface and, in some cases, expected behavior. It does not define the complete economic model surrounding that interface.
This distinction is routinely lost in sales language for smart contract development services. “Built on audited libraries” can quietly become “the protocol is secure.” Those are not equivalent statements.
The real review has to move through layers:
1. Code correctness: Does the function perform the intended state transition?
2. Access control: Can only the intended actors invoke it?
3. Composability: What happens when another protocol calls it, reenters it, or wraps its asset?
4. Economic integrity: Can market conditions or temporary liquidity make the intended behavior unsafe?
5. Operational control: Can the team pause, upgrade, migrate, or recover without creating a second exploit?
6. Observability: Will a failure be detected before funds are irreversibly lost?
A library addresses parts of the first two layers. The rest still belongs to the engineering team.
What a professional Web3 delivery pipeline looks like
When a client evaluates a web3 development agency, the most useful questions are not “Which chains do you support?” or “How quickly can you launch?” Those questions invite a sales answer. Ask for the path from requirement to deployed state.
The exact pipeline differs by project, but the following sequence is a credible minimum:
1. Threat model before implementation.
The team identifies trust assumptions, privileged actors, oracle dependencies, external protocols, user-controlled inputs, and worst-case loss scenarios. If the threat model begins after the code is written, it is mostly an explanation of history.
2. Reproducible local setup.
New engineers can install dependencies, compile contracts, run tests, start a local node, and deploy a known state without relying on an undocumented workstation. Environment setup is not glamorous; it is how teams prevent invisible divergence.
3. Focused contract development.
Solidity changes are accompanied by unit tests, failure-path tests, fuzz cases, and invariants. The test suite describes properties, not just examples.
4. Automated analysis on every change.
Static analysis, formatting, compilation, dependency checks, and test execution run through CI/CD. Findings are reviewed and tracked rather than buried in a report generated once before launch.
5. Fork-based integration testing.
The system is tested against realistic chain state using Anvil, Tenderly, or comparable infrastructure. External calls, existing token contracts, oracle behavior, and privileged workflows are exercised.
6. Deployment rehearsal.
The exact production scripts run against a staging network or fork. Addresses, constructor parameters, initialization calls, roles, and ownership transfers are checked before the real transaction is signed.
7. Independent review.
Manual review examines business logic and interactions that automated tools cannot reliably understand. This is where reentrancy through unusual call paths, oracle manipulation, and upgrade risks receive human scrutiny.
8. Controlled release.
Deployment keys, multisigs, timelocks, pause controls, monitoring, and incident contacts are ready before the contract holds meaningful value. “We will configure monitoring later” is how the first incident becomes the configuration test.
9. Post-deployment verification.
The team verifies bytecode, confirms roles and ownership, checks emitted events, compares deployed addresses with release artifacts, and records transaction hashes. Production is not complete when the transaction succeeds. It is complete when the resulting state is known.
10. Maintenance with consequences.
Dependencies, compiler versions, indexers, RPC providers, and external protocols change. A delivery partner must define how those changes are assessed, tested, and released. Otherwise the project slowly decays under the illusion that immutable bytecode means immutable risk.
A capable outsourcing blockchain development partner should be comfortable showing the uncomfortable parts: failed tests, rejected static-analysis findings, simulation traces, deployment rehearsals, and explicit limitations. Polished screenshots prove very little. A post-mortem is more informative than a roadmap because it reveals how the team behaves when its assumptions are wrong.
For readers comparing infrastructure vendors, the same evidence standard applies to RPC nodes, Web3 API endpoints, testnets, and indexing providers. Latency figures matter, but so do rate-limit policies, archive access, chain reorganization behavior, failover procedures, and the visibility of degraded service. A smart contract can be flawless and still fail operationally because its application cannot retrieve the state it needs.
Even the language used to describe data should be precise. “Real-time” might mean a websocket notification, a recently indexed block, or a finalized state. Those are different guarantees. I would apply the same skepticism to any data claim that I would apply when reading food science and nutrition research: provenance, measurement conditions, and limitations matter more than a confident label.
The part agencies prefer to keep off the landing page
There is no universally correct division between Hardhat and Foundry. No static analyzer catches every exploit. No fork perfectly reproduces mainnet. No subgraph is automatically trustworthy. No OpenZeppelin contract can compensate for an unsafe integration. And no audit report can remove the need for operational controls.
That is not pessimism for its own sake. It is the basic arithmetic of distributed systems handling adversarial value.
The best web3 engineering team is not the one that promises an absence of risk. It is the one that makes risk observable, limits privilege, reproduces deployments, attacks its own assumptions, and leaves enough evidence for the next engineer to understand every critical decision.
I have watched protocols fail because a team skipped one apparently boring step: an initialization check, a stale-price test, a role transfer, a reorg scenario, a dependency pin, a direct read against indexed data. The exploit was often technically simple. The expensive part was the confidence surrounding it.
A professional Web3 development agency should deliver more than contracts and a launch date. It should deliver a pipeline that can withstand hostile inputs, realistic chain state, staff turnover, provider failure, and the unpleasant question every serious auditor eventually asks:
What, exactly, prevents the next transaction from turning this assumption into a loss?




