A withdrawal test can pass even when the deployer still has permission to upgrade the contract. The leftover role might only show up when you run the deployment script in a test environment and check what it actually granted.
For a Solidity release on Ethereum or another Ethereum Virtual Machine (EVM) chain, independent smart contract audits examine a specific code version, its design, and the controls around it. If the contract relies on outside data, the reviewer also needs to know which price feed it uses and who can replace it.
What to Prepare Before the Review
A reviewer should be able to build the release and trace a withdrawal without guessing which contracts are in scope. The Ethereum secure development workflow and the OWASP smart contract security checklist can help you spot gaps in the handoff.
- Build. Include the commit hash, build instructions, compiler version and settings, and locked dependencies.
- Deployment. Name the target chains and include the deployment scripts, proxy and implementation contracts, and expected addresses or configuration.
- Scope. List the contracts that hold assets, control permissions, or supply prices. Call out known issues, excluded components, and trusted operators.
- Protocol rules. Map where funds move. In a lending market, for example, explain what a partial repayment should do to debt, collateral, and protocol fees.
When requesting quotes for smart contract audits, give each provider the same commit and scope. Compare public reports on similar protocols and ask who will review your dependencies. Hacken lists complexity and integrations as pricing factors. A complex application can take weeks to review, according to Chainlink. Confirm the cost and timing of retesting.
10 Security Checks for Smart Contract Audits
Smart contract audits are easier to follow when reviewers can reproduce what your team tested. Run these checks against the code and configuration you plan to ship. Pay particular attention to who can move funds, what happens when a price feed fails, and whether users can still withdraw under load.
1. Test Access Controls From the Wrong Account
A role matrix can look right on paper. The deployment script tells you who gets the keys. With OpenZeppelin’s access control modules, map who can grant or revoke each role. Include inherited functions and privileges that run through governance or a proxy admin. With a multisig or timelock, find out who can lower the threshold or bypass the delay.
Run the deployment script in your test environment. For functions that mint, withdraw, pause, upgrade, change fees, or replace an oracle, the role tests should show that:
- The intended caller succeeds.
- An account without the role gets a revert.
- A former role holder loses access. That includes the deployer if the script is supposed to hand off control.
2. Follow External Calls for Reentrancy
A callback doesn’t have to come back through the same function. If a withdrawal hands control to another contract before settling the balance, the callee could use a different entry point that reads the same balance or debt. Solidity’s checks-effects-interactions pattern puts the state update ahead of the external call where the design allows it. Try a second withdrawal and a callback through any other entry point that shares the accounting, even if one of them has a guard. In smart contract audits, a trace that shows which function ran, what it read, and how much actually left the contract makes the result easier to verify.
3. Check Rounding, Units, and Vault Accounting
A number can fit in its type and still be wrong. Write down which way deposits, withdrawals, fees, and liquidations should round. Follow the conversions between token decimals, oracle decimals, and vault shares.
The ERC-4626 inflation attack is easier to understand with small numbers. In a simplified vault with 100 asset units and one existing share, a deposit of 99 units rounds down to zero shares. A direct donation can create that kind of exchange rate before the next deposit.
Use the actual conversion formula and protections you ship to test zero, one base unit, the first deposit, a nearly empty vault, and a deposit after a donation.
4. Stress-Test Oracles and Price-Dependent Actions
A stale price can look perfectly reasonable in a log. For smart contract audits of lending markets or exchanges, follow that value into the borrow, trade, or liquidation that uses it. Record these settings for each chain:
- Approved feed address and asset pair
- Feed decimals
- Maximum acceptable age, measured using the update timestamp in Chainlink’s data feed API
Try the action with stale data. If the feed should return a positive price, test zero and negative values as well. Where a rollup integration uses a sequencer uptime feed, cover both an outage and the recovery grace period.
A fresh price can still be manipulated. If it comes from a thin market, could one transaction move it far enough to increase borrowing power or trigger a liquidation? That’s where DeFi security risks become a concrete test of your protocol’s rules.
5. Test Token Transfers and Dependency Failures
A user deposits 100 tokens, and the vault receives 99 after a transfer fee. Crediting the full 100 puts the accounting out of step with the balance. SafeERC20 can handle tokens that return no value on a successful transfer. The vault still has to decide how much credit to record. Use the tokens you support or test doubles that reproduce their behavior, and compare the credit with what actually arrived. Include a reverting transfer and any relevant fees, callbacks, or balance changes.
6. Prevent Signature and Message Replay
A signature needs to cover the signer, action, and parameters. The EIP-712 signing domain can distinguish chains and verifying contracts. Your application still has to consume a nonce or another unique identifier for one-time actions and enforce any deadline it sets.
- Submit the same signed withdrawal twice.
- Submit a different, unused authorization after its deadline so a consumed nonce can’t mask a missing expiry check.
- Change the recipient. Where applicable, also try the wrong chain or verifying contract.
For cross-chain Web3 applications, the destination needs to check the source chain and authorized sender. Keep separate tests for an unproven message and a valid message that has already been processed. Those are different ways a message can be ineligible to execute.
7. Rehearse Proxy Initialization and Upgrades
The 2022 Nomad bridge exploit started with a detail already in storage. The proxy trusted a zero root. After an upgrade changed message verification, an unproven message could resolve to that same default value and pass validation. If your release changes how proofs or permissions are checked, test what happens when the relevant mapping entry is missing.
For smart contract audits of live upgrades, a fork at a recorded block lets the reviewer run the new code against existing state. For a first release, seed representative balances, roles, and open positions in a test environment. Compare those values before and after the upgrade. Try the authorized and unauthorized paths, and check whether a migration can run twice.
OpenZeppelin’s upgrade guidance covers storage compatibility and initialization. Verify who can initialize the proxy and lock the standalone implementation where the pattern calls for it.
8. Keep Withdrawals and Other Critical Actions Executable
Five withdrawals might fit in a test transaction. A longer queue can push the same loop past the block gas limit, a failure mode described in the Solidity documentation on gas limits. A recipient that rejects payment can also stall a batch when one transaction pays everyone. Measure worst-case gas and test the rejection separately. For bounded batches or individual claims, the useful result is evidence that other users can still exit, processing can resume, and a claim can’t be paid twice.
9. Pair Static Analysis With Tests of Protocol Rules
Slither flags patterns worth a closer look. It’s one of several tools available for Web3 security reviews, and its findings need to be read in the context of your code. Fuzzing varies inputs. Foundry’s invariant tests can go further by checking what happens across a sequence of calls. In a token with a fixed supply cap, for instance, supply should stay within the cap through any valid sequence of mints and burns.
A passing test tells you very little if the runner tolerates reverts and no call changes state. Save the test configuration, the seed if available, and a minimal failing sequence so the reviewer can replay it.
10. Verify the Deployed Build and Retest Every Fix
A fix can be merged and reviewed, then miss the release because the deploy job picks up an older commit. The commit recorded during smart contract audits gives you a concrete point of comparison. Turn confirmed findings into regression tests and send material fixes back to the independent reviewer for retesting.
After deployment, source verification on Ethereum checks that the source and build settings correspond to the deployed bytecode. Then there’s the security work beyond contract code. Check the active proxy implementation, admin roles, timelock settings, and oracle addresses on the target chain. Someone on the team should own monitoring for changes and rehearse the incident actions the deployment supports.
Before You Ship
Keep the commit, the tests that reproduce findings, and the decisions from smart contract audits together. If a late change affects who controls funds, how prices are read, or how balances are calculated, reopen the affected checks before release.
Frequently Asked Questions About Smart Contract Audits
When Should Smart Contract Audits Be Reopened?
A small fix may only need targeted retesting. Send the reviewer the code diff and any configuration changes. Revisit the wider scope if the fix touches shared accounting, permissions, or price inputs.
How Do I Verify the Implementation Behind a Live Proxy?
For an ERC-1967 proxy, read the implementation slot or resolve the implementation through its beacon. At a recorded block, compare the address and bytecode with the release record. Custom proxies may resolve the address differently.
How Can I Tell if My Invariant Tests Reach Useful States?
Compare successful calls with reverts for each action. If redemptions never succeed, give the test handler a funded account, the required approvals, and valid inputs. Test expected reverts separately.
Should Stale Oracle Data Block Repayments Too?
A repay-only path that reduces debt without a price may be able to stay open. If repayment also releases collateral or changes a position’s eligibility, test those effects under stale data first.







