Summoning builders…Compiling dreams…Polishing pixels…Aligning the stars to your roadmap…Brewing the backlog…Forging gold from ideas…Charting the constellations…Waking the render spirits…

    Insights/Web3 & Emerging Tech/Solidity smart contract audits: a practical checklist for founders

    Solidity smart contract audits: a practical checklist for founders

    Marco Lopes
    February 09, 2021
    2 views
    Web3 & Emerging Tech
    Blockchain & Web3
    Solidity
    Smart Contracts
    Security Audits
    Startup Engineering
    Solidity smart contract audits: a practical checklist for founders

    Solidity smart contract audits: a practical checklist for founders

    By Marco Lopes · February 9, 2021 · Blockchain & Web3

    A smart contract audit is not a certificate that makes a blockchain product safe. It is a structured attempt to find weaknesses in a particular version of a system, under stated assumptions. For founders, that distinction matters: an impressive report cannot protect code changed afterward, a compromised administrator key, or an economic model nobody examined.

    At Kordspace, we approach contract security as part of product engineering, not a purchase made just before launch. As a remote collaborative software, AI, Web3, hardware, and design studio, we connect contract decisions with interfaces, infrastructure, and operational responsibilities.

    Here is the practical checklist we would use to prepare a Solidity project for review today, and to turn the findings into a safer release.

    1. Define what we are protecting

    Before selecting an auditor, we need a plain-language description of the product. What can users deposit, withdraw, trade, borrow, mint, or govern? Where do assets sit? Which actions are reversible, and which become final when a transaction executes?

    We then write down the properties that must remain true. These are invariants, and they turn vague security goals into reviewable requirements.

    For example, we might require that:

    • Withdrawals never exceed a user's available balance.
    • Only explicitly authorized roles can change fees.
    • Aggregate liabilities remain covered by assets under the protocol's accounting rules.
    • A paused system blocks the intended operations without accidentally trapping permitted exits.

    We should include abuse cases alongside normal user journeys. What happens if a borrower, liquidator, administrator, or token contract behaves maliciously? A useful threat model assumes adversarial behavior wherever trust has not been explicitly granted.

    2. Freeze the scope before booking the review

    An auditor needs more than a repository link. We should supply an exact commit, a contract inventory, compiler settings, dependency versions, deployment scripts, and instructions for running the tests.

    Scope should identify inherited contracts, libraries, proxy implementations, external protocols, and off-chain services that influence execution. If our application depends on a price feed or a transaction-signing backend, excluding it from the contract review does not remove its risk.

    We also need an explicit list of exclusions. Perhaps the engagement will not cover the frontend, governance economics, or cloud infrastructure. Those boundaries belong in the agreement and, eventually, in our public explanation of the audit.

    Once review starts, we should avoid unrelated changes. Necessary fixes can be tracked separately, with a clear diff for the auditor. Otherwise, we pay reviewers to chase a moving target and make it harder to know what was actually examined.

    3. Make the Solidity build reproducible

    We should pin the compiler version and document optimizer settings rather than relying on whatever happens to be installed on a developer's laptop. Dependency lockfiles and repeatable build instructions help reviewers reproduce the exact environment.

    Version-specific behavior deserves attention. Solidity 0.8.0 introduced checked arithmetic by default, but that does not eliminate numerical bugs. Earlier versions generally need deliberate overflow protection, while unchecked blocks in 0.8 require their own justification. Neither approach solves rounding errors, incorrect units, or bad accounting.

    We should inspect compiler warnings, confirm whether known compiler bugs affect our configuration, and remove dead code where practical. Small, understandable contracts are easier to review than a large inheritance tree with overlapping responsibilities.

    Established libraries, including OpenZeppelin contracts, can reduce custom security-sensitive code. We still need to review the exact version and how we use it; a familiar import is not a substitute for system-level analysis.

    4. Map permissions and upgrade powers

    For every privileged function, we should record who can call it, what they can change, and what users could lose if that authority is abused.

    This includes minting, pausing, withdrawing reserves, changing oracle addresses, setting fees, and upgrading implementations. We should test both authorized and unauthorized calls, including transitions between roles.

    An upgradeable contract adds particular questions:

    • Can anyone initialize an uninitialized deployment?
    • Can initialization run more than once?
    • Does an upgrade preserve storage layout?
    • Who controls the proxy administrator?
    • Can users react before a material change takes effect?

    Multisignature controls and timelocks may reduce specific risks, but they also introduce operational dependencies. We need a documented signer policy, recovery process, and realistic emergency procedure.

    We should describe these powers honestly in the product. If administrators can replace the contract logic, calling the system unconditionally trustless would misrepresent its security model.

    5. Examine external calls and asset handling

    Whenever our contract calls another contract, we cross a trust boundary. The recipient may revert, consume gas, return unexpected data, or attempt to call back into us.

    We should review reentrancy across functions and contracts, not just search for one vulnerable withdrawal function. Checks-effects-interactions and appropriate reentrancy guards are useful defenses, but reviewers still need to understand which state can be observed or changed during callbacks.

    Token integrations need equally careful treatment. We cannot assume every ERC-20 token behaves identically. Return values, transfer fees, rebasing balances, and callback behavior can invalidate otherwise reasonable accounting assumptions.

    Our supported-asset policy should therefore be explicit. If the design supports only particular token behavior, we should enforce or document that restriction rather than imply universal compatibility.

    We also check low-level call results, Ether-receiving paths, and denial-of-service conditions. A single reverting recipient should not unnecessarily prevent everyone else from withdrawing, and unbounded loops can make essential operations impossible as usage grows.

    6. Audit the economics, not just the syntax

    A contract can execute exactly as written and still allow an attacker to extract value. We therefore need economic assumptions in the audit scope, especially for lending, trading, staking, and collateralized products.

    For each price input, we should ask where it comes from, how fresh it must be, and what happens if it becomes unavailable. A spot price from a thinly traded pool may be manipulable within the same transaction that consumes it.

    Flash loans mean we should not assume an attacker needs to own substantial capital beforehand. We should examine whether temporary liquidity can distort prices, voting weight, collateral calculations, or reward distribution.

    Transaction ordering matters too. Can another participant profitably front-run a trade, interfere with a liquidation, or exploit a missing slippage limit? We should test minimum-output controls and deadlines wherever applicable.

    Finally, we review decimals, rounding direction, and repeated small operations. Tiny accounting differences can become meaningful when an adversary can repeat the same profitable action.

    7. Build evidence with tests and analysis tools

    Before external review, our tests should cover successful flows, rejected operations, boundary values, and sequences of actions. Testing a deposit and a withdrawal separately is not enough if repeated deposits, partial withdrawals, and fee updates break the accounting together.

    We should add property-based tests around our invariants. Echidna can help explore unexpected input sequences; Slither can identify suspicious patterns through static analysis; Mythril can provide symbolic analysis. These tools complement manual review rather than replace it.

    For integrated systems, local fork testing with tools such as Hardhat can help us exercise interactions against a snapshot of existing chain state. We still need to account for differences between that snapshot and future live conditions.

    Coverage reports are useful for locating untested code, but high coverage does not prove correctness. Our stronger question is whether the tests would fail if an important security assumption were violated.

    Every confirmed audit finding should eventually have a regression test where feasible.

    8. Choose reviewers and manage findings deliberately

    We should choose auditors for relevant expertise, not simply recognition. A team experienced with token contracts may not be the right fit for a lending market with complex liquidation incentives.

    Before agreeing to an engagement, we ask about methodology, deliverables, availability for clarification, and whether remediation review is included. Public sample reports can reveal whether findings explain exploit conditions and consequences clearly.

    The schedule should include time to fix issues and have those fixes checked. An audit ending the day before a promised launch leaves little room for responsible decisions.

    During review, we maintain a findings register with severity, affected code, proposed action, owner, and status. We should challenge unclear conclusions constructively, but not dismiss a finding merely because exploitation requires unusual conditions.

    If we accept a risk, we record why, who approved it, and what mitigation remains. A transparent unresolved limitation is more useful than quietly relabeling a difficult problem as informational.

    9. Treat deployment and operations as security work

    The reviewed source must correspond to what we deploy. We should verify constructor arguments, initializer calls, library addresses, role assignments, and proxy configuration. Publishing verified source helps others inspect the deployment, but we also need to connect it to the audited commit and build settings.

    Our launch plan should consider conservative exposure limits and staged activation where the design allows them. Limits do not repair vulnerabilities, but they can constrain the assets exposed while we validate operations.

    Monitoring should watch meaningful events: unexpected upgrades, role changes, unusual withdrawals, stale oracle data, and broken accounting signals. Each alert needs an owner and a response procedure.

    For supporting infrastructure, we generally prefer Google Cloud, while AWS or Azure may fit existing requirements. Cloud choice does not secure an on-chain contract. Access controls, protected credentials, logging, and incident readiness still matter, and ordinary application servers should not hold unrestricted administrator keys.

    Our short founder checklist

    Before approving launch, we want clear answers to these questions:

    • Scope: Do we have an exact reviewed commit and documented exclusions?
    • Assumptions: Are assets, trust boundaries, and invariants written down?
    • Control: Are administrator and upgrade powers tested and disclosed?
    • Evidence: Have negative tests, adversarial sequences, and analysis tools informed the review?
    • Remediation: Are significant findings fixed and rechecked, or explicitly accepted?
    • Deployment: Can we prove the deployed configuration matches the reviewed release?
    • Operations: Do monitoring, emergency actions, and responsible owners exist?

    If an answer is unclear, we should treat it as unfinished work rather than a documentation detail.

    Make the audit part of the product

    The best outcome is not a report with reassuring language. It is a system whose behavior we understand, whose risks we can explain, and whose operators know what to do when assumptions fail. We should budget for that process alongside engineering, design, and launch, not after them.

    At Kordspace, we can help connect contract engineering with product design and the infrastructure around it, while planning appropriately scoped independent security review. If we can help shape your next Web3 product, start a project, or explore our related services to see how we can work together.

    Conversation (0)

    No comments yet. Start the conversation.

    Join the conversation

    Create a free member account to comment.