Start with the contract's promised behavior
Smart contracts can control assets, and mistakes may be difficult to correct after deployment. A review should therefore begin with a concise model of intended behavior, not a search for familiar bug patterns. Record actors, permissions, asset flows, calculation rules and possible states. For every public operation, state its inputs, effects and expected failure conditions. Without a specification, reviewers can confirm that code is internally consistent while missing that it implements the wrong product rule.
Define the exact scope: source revision, compiler version, dependencies, network and external contract addresses. Include deployment scripts, initial configuration and the web or server component that prepares calls. A wrong address, token decimal assumption or administrator role can have the same effect as a bug in transfer logic. Freeze a commit and list exceptions before review. A finding on one revision does not establish the safety of different bytecode deployed later.
Write invariants and trust boundaries
An invariant is a property that must hold across sequences of transactions. Examples include liabilities never exceeding backing, ordinary users being unable to change fee recipients, a paid claim never being paid twice, and emergency suspension never enabling transfer of another user's assets. Turn these statements into tests that explore many action orders instead of isolated happy paths. Ethereum's security guidance recommends combining unit and property-based testing. High line coverage does not prove that the right properties were chosen.
Map trust boundaries to oracles, bridges, other contracts and operators. Ask what happens with stale or wrong data, a failed external call or changed rules at a counterparty. An attacker may influence transaction ordering and create states absent from hand-written tests. Make every assumption about third-party reliability explicit. If the product cannot operate safely without a service, that dependency is a business decision and should not be hidden in code.
Review roles and key custody
Create a table of privileged functions: who can change parameters, pause operations, upgrade the implementation, withdraw fees or restore service? Verify initial assignments, transfer and removal of roles. Consider loss or compromise of an authorized key. Technically sound code controlled by one unprotected administrator key still carries major operational risk. For critical changes, assess multiple signatures, time delays and separation between proposing and approving actions.
Review the off-chain process too: key holders, backups, deployment environment access and address verification before signing. Rehearse emergency administrator replacement on a test network. Watch for temporary permissions granted during deployment and never removed. Events should make ordinary parameter changes distinguishable from incidents, but logging cannot replace proper authorization. Document who can initiate a pause and who can reverse it; an emergency switch with no available operator may be useless.
External calls and reentrancy
Solidity's security guidance describes a checks, effects, interactions pattern. If a contract sends value or calls another contract before completing its own state transition, the counterparty may enter the function again. Inspect all external-call paths, including libraries, token hooks and seemingly familiar addresses. A reentrancy guard can help, but it does not substitute for correct balance accounting and state transitions. Test a counterparty that returns normally, reverts and attempts to reenter.
Also inspect gas limits, return values, transfer failures and unconventional tokens. Assuming that every token returns the expected boolean or uses the same decimal precision is unsafe. Batch operations can exceed a gas limit and block the whole group. Combine ordinary, boundary and extreme amounts in tests, with careful attention to rounding. Time dependence and transaction ordering deserve special scrutiny wherever an actor can gain financially by moving first. Record the allowed slippage and deadline behavior for user-signed actions.
An upgrade is more than a new file
For proxy-based contracts, include implementation initialization, upgrade authority and storage compatibility in the review. OpenZeppelin explains that proxies use initialization rules different from ordinary constructors and that changed storage layouts can corrupt existing values. Check every proposed version with automated layout validation and a migration test against representative state. If upgrades are forbidden, the product still needs a plan for a flaw in immutable logic.
Before releasing an upgrade, describe behavioral differences and their effect on user assets. Prepare approval, timing, monitoring and a possible reversal path. A rollback is not automatically safe: the new implementation may already have written state the old version cannot interpret. Simulate the complete sequence in an isolated environment. Keep the user interface aligned with the live contract version so people do not sign an action with a different meaning from the one displayed.
Combine complementary tests
Unit tests verify individual rules, property tests explore input and state combinations, and integration tests join contracts, oracles and user calls. Static analysis can highlight suspicious patterns, but each finding needs context. A clean tool report is not evidence of safety. Deployment tests should confirm initial roles, configuration and published bytecode. Negative cases matter: unauthorized calls, repeated settlement, interrupted transactions and invalid oracle data. Include edge cases around time, rounding and zero values.
When the value at risk or complexity is high, obtain independent expert review with enough time to fix issues and verify changes. Ethereum notes that audits cannot catch every bug; they add another layer of inspection. The report should identify scope, commit, severity, remediation status and residual risk. Saying only “audited” is a marketing claim, not technical evidence. After remediation, the reviewer should confirm the changed code. A feature added outside the original scope requires fresh review.
Prepare for operation after deployment
Security does not end at publication. Monitor role changes, upgrades, unusual transfer volumes, oracle deviations and repeated failed calls. Define a response: who assesses an alert, who may pause a component and how users are informed. Exercise the emergency mechanism, including key access and the real time needed to act. The plan should also cover a compromised external service or a misleading user interface. Set observation windows appropriate to the chain and product rather than relying on a generic dashboard.
During an incident, preserve relevant transactions, versions and decisions. Avoid an improvised upgrade without checking consequences for state and users. After resolution, add a test that captures the cause and update the threat model. EXCOIN can assist with specification, tests, integration and an operating plan; a concrete audit and safety claims must always correspond to the actual code, scope and independent verification. The practical aim is a set of evidence and openly stated risks before anyone signs a deployment transaction.
Turn findings into a release decision
Record each finding with reproduction steps, affected revision, attack assumptions and possible impact. Separate a flaw that could lose funds from a recommendation about maintainability. Priority should follow impact and exploitability, rather than the name of the scanning tool. For each fix, record a commit and tests that demonstrate both the original issue and the new behavior. A reviewer should check that remediation did not break another safety property. If the team accepts residual risk, name the decision owner, rationale and mitigating controls. Do not close a finding merely by labeling it low priority.
Use a concrete release gate: matching specification and code revisions, passing invariant tests, verified roles and addresses, resolved critical findings, active monitoring and a rehearsed response. Someone other than the deployment-script author should read the target network, implementation address and initialization data before signing. After deployment, compare verified bytecode and configuration with the expected state. Stop if they differ. Tell users and partners exactly which revision and scope were reviewed, and never present an audit as a guarantee.