If Your Invariants Never Touch Fork State, You Have Not Tested the Money Path
Static analysis can narrow review. It cannot replace running invariants against real chain state before a release touches live money.
Establish the problem with technical depth
Too many teams still treat a clean scanner run like the last serious question before deployment. That is comforting, cheap, and wrong.
Slither's official docs describe a Solidity and Vyper static analysis framework that runs vulnerability detectors, prints contract details, and supports custom analyses. That is valuable. It is fast enough to run on every diff. It catches obvious bad shapes early. It gives reviewers call graphs, storage context, and detector output before they spend expensive time digging by hand.
Solidity's SMTChecker docs raise the bar further. The compiler can try to prove certain properties from require and assert statements and can produce counterexamples when assertions fail.
But the hard release question is not "does the source look suspicious?" The hard question is "can the live protocol still be pushed into a bad state with valid actions, live dependencies, and real money?"
KyberSwap's official post-mortem is the kind of example that should make every CTO slow down. KyberSwap says the November 22, 2023 exploit affected about $56,197,284.26 in user assets, and attributes the failure to a discrepancy in the tick-based swap mechanism made worse by a rounding error. Prior audit efforts did not identify the vulnerability. The protocol looked reviewable until a carefully constructed swap sequence pushed the state transition outside what the team thought it meant.
Euler's own retrospective makes the same point in a different way. Euler says the March 2023 exploit cost about $197 million and traces the root cause to a single missing health check in the donateToReserves path. That path had been introduced to fix a smaller bug and had been audited. A defensive change became a capital-loss path because nobody proved how the new function interacted with the protocol's solvency logic under hostile sequencing.
That is why this topic matters to both halves of ChainShield's audience. Founders and investors are not underwriting code quality in the abstract. They are underwriting whether a live machine can keep its promises after the next upgrade, the next parameter change, and the next adversarial transaction bundle. CTOs and Solidity engineers should care for the mirror reason: a contract can look structurally clean and still fail economically once the wrong call sequence hits the wrong live state.
The mechanism, the mistake, the misunderstanding
Static analysis asks a structural question before execution: what about this code deserves skepticism?
That includes detector findings, dangerous call patterns, surprising inheritance, risky storage layout, suspicious authorization flows, and machine-checkable assertions. Slither is good at this because it compresses broad code review into something fast enough for continuous use. SMTChecker is good at this when the team has stated the right property clearly enough for the compiler to reason about it.
Dynamic analysis asks the harder execution question: what breaks when valid calls hit realistic state in an adversarial order?
Foundry's invariant testing guide defines invariants as properties that should always hold regardless of the sequence of actions taken. An invariant is a business truth the protocol cannot afford to violate. Solvency, authorization, reward conservation, and message authenticity are invariants.
A trivial example looks like this:
function invariant_protocolRemainsSolvent() external view {
assert(totalCollateralValue() >= totalDebtValue());
}
The important part is not the syntax. It is the discipline of naming the truth that must survive every reachable path.
Foundry's fork testing guide pushes the same discipline into reality by running tests against real chain state. That matters because mocks are polite and production is not. Real tokens rebalance, skim fees, revert oddly, expose callbacks, and carry stale approvals. Real state is where clean local assumptions go to die.
That is exactly what static-only workflows miss. A detector can flag a potential reentrancy shape or a suspicious external call. It cannot automatically tell you whether a three-step sequence involving live balances, borrowed liquidity, role state, and one obscure edge case can turn false accounting into a withdrawable profit. KyberSwap and Euler failed because the intended economic property was not proved against realistic execution conditions.
The first mistake the industry makes is comparing static and dynamic analysis as if one should replace the other. That is lazy. They do different jobs. Static analysis narrows attention. Dynamic analysis pressures the actual property. You want both.
The second mistake is thinking formal or static proofs remove the need for dynamic pressure. Solidity's own SMTChecker docs are explicit that formal verification helps compare the implementation with the specification, but the specification itself can still be wrong or incomplete. A proof of the wrong thing is still wrong.
The third and most expensive misunderstanding is using a clean scanner run as release evidence. It is hygiene. It is not a shipping verdict. If the risk is economic, cross-contract, stateful, or upgrade-shaped, then the release question is behavioral by definition, and fork-state invariants are where that proof starts getting real.
What good looks like
Good looks like sequencing these tools around the actual money path instead of using them as interchangeable badges.
Run static analysis on every meaningful diff. Slither belongs in continuous integration because obvious structural regressions should die early, before human reviewers waste energy on them. If detector output changes around authorization, storage, external calls, or upgradeability, that should be treated as a real review event, not background noise.
Use formal checks when the property is crisp enough to state. SMTChecker is useful for proving targeted assertions and surfacing counterexamples early. But keep the contract with yourself honest: if the specification does not describe the business truth that matters, the proof is not enough.
Write invariants before you get clever with scenarios. An invariant forces the team to say, in executable form, what must stay true if user funds are to stay safe. For a lending protocol that may be solvency. For a staking system it may be reward conservation. For a bridge it may be authenticated message settlement. For governance it may be delayed and authorized execution only.
Then attack those invariants on fork state before you bother polishing narrative scenarios. Foundry invariants matter because they keep re-checking the property while the framework explores hostile call sequences. Fork tests matter because they stop the team from congratulating itself inside a toy environment. If a protocol depends on live approvals, live oracle behavior, live liquidity, or live dependency contracts, test against those conditions before mainnet does it for you.
Treat environmental changes as security changes even when the original contract barely moved. Euler is the warning label here. The exploitable path was introduced as a fix. Many serious failures appear when new assumptions are routed through old logic: a new market type, a new router, a new signer workflow, a new bridge adapter, a new fee path, or a new privilege edge. If the environment around a function changes, the function is back in scope whether or not the diff was small.
Keep exploit-shaped regressions forever. Once you learn an expensive lesson, encode it. If a specific route once made insolvency reachable, that route belongs in the permanent test suite. Security learning should compound instead of evaporating after the incident review call ends.
For founders and VCs, the diligence bar should change. Do not ask only which scanners the team runs. Ask which protocol truths are machine-checked on fork state before every important release, and what would fail automatically if the next upgrade or integration made the protocol unsafe.
For CTOs and Solidity engineers, the question is harsher and more useful: if an attacker stayed inside valid call paths, borrowed all the liquidity they needed, and hit your least-loved edge case at the worst possible time, where would your current pipeline prove the failure before users did?
ChainShield's angle
ChainShield treats static analysis as baseline review speed, not as release proof.
We want the detectors. We want the storage summaries. We want the fast structural triage on every diff. But the real decision is always deeper: what property makes this exact change safe enough to fund today, and what executable evidence proves that property still holds in the current state of the system?
That is why we focus on the live change surface. The current diff matters. The current role graph matters. The current approvals, dependencies, and upgrade path matter. Most of all, the current invariant set matters, because that is where a team stops gesturing at intent and starts proving behavior.
A clean scanner run is good engineering hygiene. Fork-state invariants are much closer to release evidence. Capital deserves the harder answer.
ChainShield Discovery Runs are designed to identify high-risk issues quickly, validate what matters, and give engineering teams a faster path to remediation.
Request Security Quote