Smart Contracts

Smart Contract Auditing: Red Asgard Methodology

How Red Asgard reads a smart contract the way an attacker will read it the minute it is deployed. The pre-read, the pattern catalog, the three passes, the tools, the severity rubric, and what we will not lie about in the report.

Red Asgard Research·Updated May 2026

How an attacker reads your contract

A smart contract audit is reading code with the same lens an attacker will use the minute it is deployed, and faster once source is verified. That framing is the whole methodology. Everything below is plumbing.

The cost of a missed bug is paid in user funds. The cost of a finding marked Low when it should have been High is paid the same way.

What we audit

We audit smart contracts across the EVM ecosystem (Solidity and Vyper, both targeting the EVM) and the major non-EVM ecosystems: Solana (Rust, Anchor), Move-based chains (Sui, Aptos), Cairo (StarkNet), CosmWasm (Cosmos), and FunC/Tact (TON). The methodology below applies across languages; the pattern catalog adapts per ecosystem. Ecosystem-specific patterns are covered in the Solana, Move, Cairo, CosmWasm, and TON papers. The multi-chain audit catalog lists the 37-chain scope.

We audit deployment scripts and upgrade controllers. We audit the integration surface where on-chain logic meets off-chain infrastructure (oracle feeds, relayer policies, multisig signers, sequencer assumptions). We do not audit the economic design (token economics is a separate engagement) or off-chain infrastructure beyond the integration surface (cloud config and key management are penetration-testing and infrastructure-hardening engagements). We say so in the kickoff.

What we look at before we open a file

Three things, in this order.

The trust-boundary diagram. Every external call, every oracle dependency, every privileged role, every upgrade authority. The diagram is not a marketing artifact. It is the map we navigate during manual review. If the team does not have one, we build one in the kickoff and they keep it.

The team's actual threat model. Not the one on the website. The one in the engineers' heads. We extract it during the kickoff call. The gap between what a team assumes about its users and what its contracts let those users do is the most productive bug class in the entire field.

The prior audits, if any. We do not re-list prior findings as our work; we verify whether they remain fixed and whether the prior conclusions still hold given the code as it stands today. If a critical finding from a prior audit is unresolved, that is where we start.

For non-trivial scopes, we do not start manual review until those three are written down.

What we look for

Bug classes are not the bug. The bug is in this specific contract, with these specific assumptions, on this specific deployment path. The classes are how we know where to look first.

A reviewer hunting through a non-trivial DeFi protocol carries a working pattern catalog. The catalog is not abstract. Each entry has a story attached.

Reentrancy, single-contract and cross-protocol. Single-contract reentrancy is well understood, but not gone. New variants appear through receiver hooks (ERC-777, ERC-721), multicall patterns, and read-only reentrancy across composed protocols. We start anywhere an external call happens before accounting, authorization, or invariant settlement.

Oracle drift. An oracle is a contract calling another contract calling a function whose return value we are about to trust with user money. Everything between the source of truth and the consuming contract is in scope: the source's update cadence, the staleness check, the deviation tolerance, the fallback when the oracle stops responding. A working oracle that we cannot defend against a one-block manipulation is security-equivalent to a broken oracle for that consumer and value-at-risk.

MEV-able primitives. Any operation whose execution order can be observed and reordered before inclusion is exposed. Some of those are intentional (DEX trades). The dangerous ones are accidental: liquidations that pay the wrong actor, governance votes that aren't time-locked, rebases that can be sandwiched, refunds that can be front-run.

Storage collisions in proxy patterns. A proxy is one contract's storage being interpreted by another contract's code through convention. The convention is enforced by humans. Humans are not the world's best storage-layout enforcers, especially across an upgrade. Every upgradeable system gets a storage-layout diff from us against the prior implementation.

Access-control fences that do not fence what they think they fence. onlyOwner on a setter is not a complete access-control story if the setter installs a strategy, executor, oracle, library, module, or delegate target that later moves funds. onlyRole(ADMIN) is not meaningful unless the role exists in the storage context that actually gates execution, and unless the admin path that can grant, revoke, or upgrade that role is itself constrained. We trace privileged calls to the authority they create downstream, not just the modifier on the entrypoint.

Initialization mistakes in upgradeable systems. Constructors do not initialize proxy storage. Initialization must run exactly once against the proxy, and implementation contracts must disable direct initialization. We check deployment scripts, initializer guards, implementation constructors or _disableInitializers() equivalents, proxy admin paths, and on-chain logs. The failure mode is not one bug: an uninitialized proxy can be taken over; an initialized implementation can expose UUPS or ownership hazards; a reinitializer can reopen privileged configuration after it should have been closed.

Integer rounding that compounds. A 1-wei rounding loss per share is harmless. A 1-wei rounding loss per share, compounded across many small deposits before a single large withdrawal, is a thin-but-real attack on the protocol's accounting. We look at the rounding direction at every divide-then-multiply.

The catalog is longer than this. The point is that we read with these patterns loaded. A pattern is a hypothesis. A finding is a hypothesis that survived an attempt to disprove it.

The three reads

A reviewer reads a non-trivial contract three times.

The first pass is structural. How the contracts compose. Where the trust boundaries fall. Which roles can do what. The reviewer is not looking for bugs yet. They are building a mental model. Bug-hunting before the model is built is how you find what the team already documented as a known limit, miss what they did not, and waste a week on both.

The second pass is adversarial. The reviewer assumes the worst about every external input, every oracle response, every upgrade authority. For each function, they ask one question: if I were attacking this, what assumption could I break?

The third pass is verification. They try to break each suspected weakness. Some hold. Some break. The ones that break are findings.

This is the slowest phase. It is also the only phase that produces work the tools cannot.

When tools earn their place

Static and pattern-based tooling is upstream of manual review, not a substitute for it.

Slither is fast and catches the patterns it was written to catch. We run the public detectors and a private ruleset we built from prior audit findings. The output is a triage list, not a finding list. Auditors close false positives, escalate true positives, and mark uncertain cases for manual.

Symbolic execution and bounded symbolic testing are expensive in compute and reviewer time. We use tools such as Mythril and Halmos, and where appropriate legacy or custom Manticore workflows, against specific high-stakes paths rather than the whole codebase. Manticore is no longer actively maintained upstream; we name it only for legacy cases or custom workflows, not as a default.

Foundry is where the auditor writes the proof-of-concept that turns a suspicion into a finding. PoCs are not the only acceptable form of evidence (see severity, below), but they are the most actionable.

Echidna and Medusa fuzz invariants we have stated but cannot exhaustively prove. The invariants come from the trust-boundary diagram. If we cannot state the invariant cleanly, the contract has a specification problem before it has a security problem.

Certora Prover is full formal verification, heavy enough to be a separate engagement. It proves stated properties against the verified contract model and bytecode assumptions, not the full real-world deployment environment. A proven property on the wrong spec is a load-bearing lie. We do not claim formal verification on engagements where the spec was not separately developed and reviewed.

How we rank findings

Severity is a function of impact, exploitability, required privileges, preconditions, blast radius, detectability, reversibility, and available mitigations. The table below is a typical shape, not a complete rubric. A Critical can come out of a privileged role if the role is reachable through governance, misconfigured, or capable of unilateral fund movement. A Critical-shaped vulnerability with a hard precondition can land as High.

SeverityReachabilityOutcome
CriticalPublic function, no privileged accessDrain of user funds, protocol freeze, governance hijack
HighSpecific market or sequencing conditionsSignificant loss, service degradation
MediumPrivileged role required, or narrow attack windowBounded loss, partial degradation
LowNo direct exploit pathDefense-in-depth gap, code-quality issue
InformationalNoneNotes the team should know

A Critical or High finding needs reproducible evidence: an executable PoC, a failing invariant, a symbolic counterexample, a deployment or governance trace, or a rigorous exploit argument. A suspicion without evidence is not a finding.

We do not inflate severity for impact. We do not deflate it because a client would prefer not to see Critical on the cover. Severity disagreements are tracked in the report; the final call is defensible against the evidence.

What an engagement looks like

The shape of the engagement follows the system, not a fixed menu.

Scope shapeTypical systemDurationArtifacts
Single-contractOne contract, well-known pattern3–10 daysReport, fix confirmation
Multi-contractComposed protocol, novel mechanism2–4 weeksReport, PoC suite, fix confirmation
Cross-chainBridges, multi-chain integrations4–8 weeksReport, PoC suite, fix confirmation, residual-risk memo
Formal verificationProperty-driven proofs against a spec4–8 weeksReport, FV spec, PoC suite, fix confirmation, residual-risk memo
Post-exploitForensic plus targeted audit on patches3–7 daysForensic timeline, root cause, patched-code audit

Most engagements straddle two of these. We scope to the work, not to a package.

The report

The report contains the engagement summary (scope, commit hash, methodology applied), the findings ranked by severity, reproducible evidence for every Critical and High, and a residual-risk section listing the assumptions the audit makes that the team must continue to hold after we leave.

We re-audit the patches after the team applies fixes. The status changes to fix confirmed only after the patch has been reviewed against the original finding. We do not mark fixes as confirmed because the team said the fix works.

The report is private to the client until they choose to publish it. If a finding is Critical and unpatched on the day we deliver, that fact stays in the report.

If a later exploit overlaps our scope, our default is to support a post-mortem comparing the exploit to the audit scope, findings, fixes, and residual risks. Where client confidentiality or legal constraints prevent publication, we publish sanitized lessons when possible. Refusing to engage with the question at all is not a defensible posture for an audit shop.

What clean means

A clean audit means we did not find what we looked for. That is not the same as a clean codebase. Real audit shops say that explicitly.

The deployment path runs through this report. The report runs through the methodology above. The methodology runs through how an attacker reads the code, not how a checklist does.

Tags
smart-contractsauditingdefimethodology