Find out how your contract could be attacked — before attackers do.
A scanner answers what is wrong with this code. This answers the different
question you actually have before launch: what can an attacker actually exploit? It takes the
findings the scan produced and tries to build them into a working path — and for every path it builds,
it tells you how much of it is proven, how much is inference, and whether a proof of concept was ever
executed. It will not call something confirmed because it looks bad.
21 attack chains
10 attack families
9 critical
69 detectors behind them
13 with a documented incident
What it covers
Ten families of attack. The counts below are measured from the simulator's own pattern table when this
page is built — if a family ever emptied out, this page would fail to build rather than keep advertising it.
Access ControlA function that grants power and forgets to check who is calling.4 chains
ReentrancyGetting back inside before the contract finishes writing down what you took.1 chain
Flash-Loan Attack PathsAttacks that need no capital, because the capital is borrowed and repaid in one transaction.2 chains
Oracle ManipulationThe contract asks something else what a thing is worth, and the answer can be chosen.1 chain
Price ManipulationMoving the market the contract prices against, or transacting around somebody who is about to.2 chains
Signature / ReplayA signature that works more than once, on more than one chain, or for more than one thing.1 chain
UpgradeabilityThe code behind the address is not the code that was audited.1 chain
Privilege EscalationA path from ordinary caller to owner, or from owner to more than owner.3 chains
Business LogicEvery line is correct and the rules they add up to are not.4 chains
Economic / DeFiNothing is broken. The incentives pay an attacker more than they pay you.2 chains
What the verdicts mean
Every path this tool reports carries an exploitability verdict, and the strongest one it can reach from
reading your source is Potential. That is a deliberate ceiling, not a limitation we forgot to
remove.
Confirmeda proof of concept was executed against an isolated copy of the contract and it worked.Never produced by this lane — it executes nothing.
Potentialevery required precondition is satisfied by something the scan can point at in your code, and most of the supporting evidence is present. No proof of concept was executed — this says the path is open, not that somebody walked it.
Unverifiedthe required preconditions hold, but the chain rests largely on structural inference rather than direct detector hits. Treat it as a lead for a human reviewer.
Run it on your contract
The scan runs in your browser — the source never leaves it. Only the findings it produced
(detector ids, the function table, the access-control flags) are sent to compose the chains,
and you can watch that in the network tab.
The source has to be verified on the explorer. Unverified bytecode has no functions
to read, so there is nothing to compose a chain out of.
Reading the contract is free. Composing the chains is on the
AI Agents plan and costs 5 credits.
The playbook — 21 ways a contract loses its money
This is the complete list the simulator draws from. Nothing here was written by a language model:
each chain exists because the scanner has detectors that can prove its preconditions, and each one
says which conditions must hold before it will ever be reported against your code. Read them
in the order they are ranked — the ones at the top are the ones that end projects.
critical
Run your own code against this contract's storage
Can I choose the code that this contract executes as itself?
Delegatecall
What it costs you
Delegatecall executes someone else's code with this contract's storage, balance and identity. Overwriting the owner slot is the usual first move; after that every other power is available legitimately.
What has to be true first
must be trueThe contract delegatecalls
must be trueThe target address is influenced by the caller or by a settable variable
adds weightThe contract holds state worth corrupting
adds weightStorage layout is not protected against collision
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Deploy a contract whose storage layout lines up with the target's.
Get the target to delegatecall it, either directly or by setting the stored address.
Overwrite the ownership slot with your own address.
Use the powers that ownership now grants.
How you close it
Never delegatecall an address a caller can influence. Restrict targets to an allowlist fixed at deployment, and keep proxy storage in standardised slots so an implementation cannot collide with it.
Move a thin market with borrowed capital, inside one transaction
Does this read a price that I can move with money I do not have to own?
Oracle / price manipulation
What it costs you
The attack needs no capital of its own. Borrow, distort the price the contract trusts, transact against the distortion, unwind and repay — all before any arbitrageur can react, because it is one atomic transaction.
What has to be true first
must be trueA price is read from a source that can move within a block
must be trueThat price decides how much value leaves the contract
adds weightNo time-weighted or multi-source price defence
adds weightThe answer is used without freshness or bounds checks
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Borrow a large amount from a flash-loan provider.
Trade into the pool this contract prices against, pushing the quote far from its true value.
Call the function that uses that quote — deposit, borrow, redeem or liquidate at the distorted rate.
Reverse the trade, repay the loan in the same transaction, and keep the difference.
How you close it
Never price off a spot reading that a single transaction can move. Use a time-weighted average or an independent feed, require a fresh round, and bound the deviation you will accept between two sources.
Is voting power counted from a balance I can rent for one block?
Governance
What it costs you
Governance stops being a decision by the community and becomes a purchase — for the length of one transaction. The proposal that passes can be 'send the treasury to this address'.
What has to be true first
must be trueVoting power is read from a current balance
must be trueNo snapshot or checkpoint fixes voting power to a past block
adds weightA passed proposal can execute without a meaningful delay
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Submit the proposal in advance so it is already pending.
Flash-loan enough of the governance token to hold a majority.
Vote with the borrowed balance.
Execute immediately, because nothing forces a delay between passing and executing.
Repay the loan in the same transaction.
How you close it
Count voting power from a snapshot taken before the proposal existed, and require a timelock between a proposal passing and executing so token holders can react or exit.
Repoint the price source, then borrow against a price you chose
Is there anything I can set, with no permission check, that a money function later trusts?
Privilege → Oracle → Value
What it costs you
The attacker does not need to move a market at all. They point the contract at a source they wrote, quote whatever price makes their position solvent, and take out far more than they put in — in one transaction, with no capital.
What has to be true first
must be trueA setter for a price/oracle/router dependency has no access control
must be trueThe contract moves value on paths that can consume that dependency
adds weightA price is actually read and used in a decision
adds weightNo timelock or delay between changing the source and using it
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Deploy a contract that satisfies the expected price interface and returns a value you choose.
Call the unprotected setter to point the target at it.
Enter the position the contract now misprices — deposit a little, borrow or redeem a lot.
Optionally restore the original source so the state looks untouched afterwards.
How you close it
Put the setter behind access control AND a timelock, so a change is visible before it takes effect. Validate the new address is a contract, sanity-bound the returned price against a second source, and emit an event on every change.
Take ownership through an unprotected privileged function
Is there any path that grants control and forgets to check who is calling?
Access control
What it costs you
Every owner-only power transfers with the role in one call — minting, pausing, fee changes, upgrades, treasury access. The contract keeps working perfectly; it just answers to somebody else.
What has to be true first
must be trueA privileged function is externally callable with no permission check
must be trueThe contract has an owner or role concept worth taking
adds weightAn ungated function can also grant or move that authority
adds weightOwnership transfer is single-step, so it lands immediately
adds weightThere are real powers behind that authority
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Call the unprotected privileged function and name yourself.
Confirm the authority moved by reading the owner or role back on-chain.
Exercise whichever power is worth the most: mint, sweep, change fees, or swap the implementation.
How you close it
Every state-changing external function needs an explicit permission check — the default for a new function must be 'refuse', not 'allow'. Use two-step ownership transfer, and put the authority itself behind a multisig.
Can I get back inside this contract before it finishes writing down what I took?
Reentrancy
What it costs you
Every attempt repeats the payout against a balance that has not been reduced yet, so the loop continues until the contract's whole ether balance is gone — not just the attacker's own deposit.
What has to be true first
must be trueA function makes an external call before it updates state
must be trueThe contract sends value out
adds weightNo reentrancy guard anywhere in the contract
adds weightThe contract also takes deposits, so there is a pooled balance to drain
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Deposit a small amount from a contract you control, so you hold a legitimate balance.
Call the withdrawal for that amount.
The contract sends the value BEFORE reducing your recorded balance, which hands execution to your fallback while the old balance is still on record.
From the fallback, call the withdrawal again — the check still passes.
Repeat until the contract is empty, then let the stack unwind.
How you close it
Apply checks-effects-interactions: write the balance down BEFORE the external call. Add a `nonReentrant` guard as defence in depth, and prefer pull-payments over pushing value inside the accounting function.
This is not a theft, it is a deletion. The balance goes to whatever address the call names, and the code at that address is gone — every integration pointing at it now calls into nothing, and no upgrade can undo it because there is nothing left to upgrade.
What has to be true first
must be trueA selfdestruct path exists and is not properly guarded
must be trueThere is value or accounting to lose with it
adds weightOther contracts depend on this code being there
adds weightThe address could later be reoccupied by different code
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Call the unguarded destruction path.
Name your own address as the recipient of the remaining balance.
Everything that delegated into or called this address now executes empty code.
How you close it
Delete the selfdestruct. If an emergency shutdown is genuinely needed, use a pause flag that stops new activity while leaving withdrawals open — a pause is reversible and a deletion is not. Note that SELFDESTRUCT no longer frees storage after EIP-6780, so the funds move but the code stays: verify what your compiler emits.
Supply becomes meaningless. The attacker mints an arbitrary balance and sells or redeems it against real liquidity — every existing holder's share is diluted to nothing and the pooled value leaves.
What has to be true first
must be trueA minting entry point exists
must be true…and it has no access-control guard
adds weightNo maximum supply bounds the damage
adds weightThe minted balance is redeemable or tradeable for value
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Call the mint entry point directly, naming yourself as the recipient.
Choose an amount large enough to matter but small enough not to trip any monitoring.
Redeem or sell the minted balance against whatever real liquidity exists.
Repeat while the path stays open.
How you close it
Gate the mint behind access control, and prefer a role held by a multisig or timelock over a single owner key. Add a maximum supply that the mint checks, and emit an event that a monitor can watch.
Who can change what this contract does after everyone has already deposited?
Upgradeability
What it costs you
Nothing in the deployed code has to be exploitable. Whoever holds the upgrade key replaces it with code that does whatever they like — and every audit of the previous implementation stops describing what users are exposed to.
What has to be true first
must be trueThe contract is upgradeable
must be trueNo timelock stands between deciding to upgrade and it taking effect
adds weightAuthority looks like a single key rather than a multisig
adds weightThe upgrade authorisation itself was flagged
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Obtain or compromise the single key that authorises upgrades.
Deploy an implementation with the same storage layout and an added withdrawal path.
Point the proxy at it.
Use the new path, with users' funds still sitting at the same address they trusted.
How you close it
Put upgrades behind a timelock long enough for users to exit, and behind a multisig rather than one key. Publish the pending implementation before it lands, and emit an event a monitor can alert on.
A sell tax that was 3% at launch can be 100% by the time you try to leave. No funds are stolen in any sense the contract would recognise — the transfer succeeds, and the whole amount goes to the fee wallet.
What has to be true first
must be trueA fee or tax can be changed after deployment
must be trueNothing bounds what the new value may be
adds weightThe fee is taken out of holders' transfers
adds weightThe change takes effect on the next block
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Launch with a fee small enough that nobody objects.
Wait for holders and liquidity.
Call the setter with a value the code never bounded.
Every sale from that point forward pays out to the fee recipient instead of the seller.
How you close it
Put a hard maximum in the code — `require(newFee <= MAX_FEE)` with MAX_FEE a constant — so the ceiling is verifiable by anyone reading the source rather than promised in a document. Emit an event on every change, and put the setter behind a timelock.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
high
Let them buy, then stop them selling
Is there a switch that decides whether MY transfer is allowed?
Owner power / token
What it costs you
Buying works. Selling is what stops working. The contract does not have to be broken for this — it is behaving exactly as written, and the write-up will read 'the owner disabled transfers', which is not a bug anyone can refund.
What has to be true first
must be trueTransfers are gated by a state the owner controls
must be trueThe contract is a token, so the gate applies to holders
adds weightThe switch answers to one key, with no delay and no committee
adds weightThe same authority holds other powers over the same balance
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Launch with the gate open, so early buys and sells both succeed and the chart looks normal.
Let liquidity and holders accumulate.
Flip the gate — blacklist the sellers, or switch trading off — from the owner key.
Exit through whatever path stays open to the owner.
How you close it
If the freeze exists for a legal reason, say so on the site and put it behind a timelock and a multisig so a holder can see it coming and leave. If it does not, delete the mapping and the check — a transfer function that consults an owner-controlled list is indistinguishable from a honeypot to everyone reading it from outside.
Read this before you accuse anybody
A freeze switch is a capability, not an intent. Regulated tokens carry one on purpose. What this says is that somebody other than you decides whether your sale goes through.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
high
Extract value from a rounding error, repeatedly
Does the maths lose a fraction, and can I make it lose that fraction to me?
Arithmetic
What it costs you
Each call is individually negligible and entirely legitimate. Automated and repeated thousands of times, the residue adds up — and because every transaction is valid, nothing looks like an attack until the accounting no longer balances.
What has to be true first
must be trueDivision before multiplication, or a rounding direction that favours the caller
must be trueThe path can be called repeatedly with no per-call cost that exceeds the gain
adds weightThe result decides an amount of value
adds weightArithmetic is unchecked somewhere
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Find the input size where the rounding favours you most — usually the smallest amount that still produces a non-zero result.
Call the path with that amount.
Repeat automatically, batching to keep gas below the extracted value.
How you close it
Multiply before dividing. Always round in the protocol's favour, never the caller's. Reject amounts that compute to zero, and assert the invariant that should hold after the operation.
Reuse a signature that was only meant to work once
Does anything stop me submitting the same signed message twice?
Signature handling
What it costs you
One legitimate authorisation becomes unlimited authorisations. The same signed message is submitted repeatedly — or on another chain, if the message never committed to a chain id.
What has to be true first
must be trueThe contract authorises actions with signatures
must be trueNo nonce, or no domain separator, binds the message to one use
adds weightThe verification itself was flagged as weak
adds weightA signed action moves value or grants rights
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Observe a valid signed message being submitted.
Resubmit the identical message and signature.
Nothing marks it as consumed, so it is honoured again.
Where the message omits a chain id or contract address, replay it against a deployment on another chain.
How you close it
Bind every signed message to a nonce that is consumed on use, and to an EIP-712 domain carrying the chain id and the contract address. Reject high-s signatures, and check the recovered address is not zero.
Is there a function that lets an admin move balances that are not theirs?
Owner power
What it costs you
This is the shape of most rug pulls, and it needs no bug at all — it is the contract working as written. The admin calls a function labelled 'rescue' and the pooled deposits leave.
What has to be true first
must be trueA rescue, sweep or withdraw-all path exists
must be trueThe contract custodies balances that belong to users
adds weightNothing restricts the rescue to non-user assets
adds weightThe power is immediate
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Wait until deposits have accumulated.
Call the rescue or sweep path as the privileged account.
Move the balance out — the transaction is entirely legitimate as far as the contract is concerned.
How you close it
A rescue function must exclude the assets users deposited — restrict it to tokens the protocol does not account for. Better, put it behind a timelock so a withdrawal is visible before it can complete.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
high
Make the owner sign it for you
Does this check who STARTED the transaction rather than who is calling?
Access control
What it costs you
The attacker never needs a key. They need the owner to touch one contract — a mint page, an airdrop claim, anything. The owner's wallet starts the transaction, so tx.origin is the owner all the way down the call stack, and the privileged function agrees.
What has to be true first
must be trueAuthorisation compares against tx.origin
must be trueThe permission it grants reaches value or a privileged action
adds weightA named owner or role system is present
adds weightThe same path does not also check msg.sender
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Deploy a contract with an attractive reason for the owner to call it.
Inside its function, call the target's privileged path.
Get the owner to make that one call — the transaction they started is now the transaction that satisfies the check.
The target sees tx.origin == owner and executes.
How you close it
Authenticate with `msg.sender`, always. `tx.origin` identifies whoever paid for the transaction, not whoever is calling you, and there is no case where it is the right check for permission.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
high
Inflate the share price so the next depositor's money rounds to you
Can I make one share so expensive that a real deposit buys zero of them?
Accounting / rounding
What it costs you
A depositor receives zero shares for a real deposit, and the value stays in the pool backing the attacker's single share. The victim's money is not stolen by a transfer — it is absorbed by the arithmetic.
What has to be true first
must be trueDeposits are converted into shares against a total
must be trueThe backing balance can be increased without minting shares
adds weightShare issuance rounds in the protocol's favour, to zero
adds weightNo minimum initial deposit or dead-share defence
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Be the first depositor and mint exactly one share.
Send a large amount directly to the contract, so the backing balance rises without new shares.
Wait for a genuine deposit smaller than the new per-share price.
Their share calculation rounds down to zero; redeem your one share for the whole pool.
How you close it
Mint a fixed number of dead shares at deployment, or track the backing internally instead of reading the raw balance. Round share issuance in the depositor's favour, and reject deposits that would mint zero shares.
Is the 'random' number something I can read, or influence, first?
Randomness
What it costs you
The lottery is not random for anyone who reads the same chain state you compute from. An attacker calls only when they have already computed that they win, and reverts otherwise — so from the outside the draw looks fair, and they never lose.
What has to be true first
must be trueRandomness comes from a value on the chain
must be trueThat number decides who receives something
adds weightThe draw can be read in the same transaction it is used
adds weightA validator could nudge the input
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Compute the outcome from the same block values the contract uses.
Enter only when that computation says you win.
Wrap the entry in a contract that reverts if the precondition fails, so a losing attempt costs gas and nothing else.
Repeat every block.
How you close it
Nothing on-chain and readable can be a random number. Use a commit-reveal with an economic penalty, or a verifiable randomness service (Chainlink VRF) where the answer arrives in a later transaction than the request.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
medium
Wedge the contract so nobody can withdraw
Can I make one step fail forever, so the whole queue stops?
Denial of service
What it costs you
Nothing is stolen and nothing can be recovered either. Funds stay in the contract, permanently, because the only path out reverts on an entry the attacker controls.
What has to be true first
must be trueA path can be made to revert or run out of gas for everyone
must be trueThe affected path is shared, so one failure blocks all users
adds weightThere is no independent per-user withdrawal to fall back on
adds weightNo emergency pause to contain it
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Enter the shared structure — a recipient list, a queue, a loop over holders.
Make your own entry fail: a contract that rejects payment, or an entry that pushes the loop past the block gas limit.
Every subsequent call to the shared path reverts before reaching anyone else.
How you close it
Use pull payments so each user withdraws independently and one failure is isolated. Bound every loop, and never let a transfer to one address decide whether everyone else gets paid.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
medium
Read the pending transaction and get there first
Does seeing a transaction before it lands let me profit from it?
Transaction ordering
What it costs you
The person who was supposed to benefit gets a worse result, or none. Where the pending transaction reveals a secret — an answer, a bid, a claim — the observer simply uses it first.
What has to be true first
must be trueAn outcome depends on transaction order or on a revealed value
must be trueThe action is submitted publicly before it takes effect
adds weightGetting there first is worth something
adds weightNo commit-reveal or deadline protects the action
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Watch the public mempool for the relevant call.
Read the parameters, which are visible before the transaction is mined.
Submit the same or an opposing action with a higher fee so it is ordered first.
How you close it
Use commit-reveal for anything whose value depends on secrecy. Let callers set a minimum-received bound and a deadline, so an unfavourable ordering reverts rather than executing at any price.
The swap always succeeds, so nothing looks wrong — it just returns less every time. A bot buys before it, sells after it, and the difference is paid by whoever sent the transaction.
What has to be true first
must be trueA swap or liquidity operation sets no minimum output
must be trueThe unbounded path is reachable by an ordinary user
adds weightThe transaction has no useful deadline
adds weightOrdering dependence was flagged elsewhere too
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Watch the mempool for a call into this path with no minimum-output bound.
Buy the same asset first, at a higher gas price, moving the price against the pending swap.
Let the victim's swap execute at the price you just set.
Sell back in the same block and keep the spread.
How you close it
Pass a minimum-received amount computed by the caller, and let them set it. A deadline of `block.timestamp` is not a deadline — it is satisfied in every block, including one mined an hour later.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
medium
Use the door beside the timelock
Is the delay the only way in, or just the advertised one?
Governance
What it costs you
The delay is what the documentation points at, and it is real — for the path it covers. The second path takes effect immediately, so the protection that made holders comfortable never applied to the action that matters.
What has to be true first
must be trueThe contract has a timelock
must be true…and an admin path that does not go through it
adds weightThere are real powers on the other side
adds weightThe bypass answers to one key
The simulator will not report this chain against your contract unless
all 2 of those conditions are
satisfied by something it can point at in the code.
How the attack runs
Read which functions the timelock actually wraps.
Find the same state reachable from an admin function that does not.
Use that one — the change lands in the next block with no announcement.
How you close it
Make the timelock the only holder of the privileged role, so there is no second path by construction. If an emergency path must exist, restrict it to pausing — an action that can only stop things, never move value.
No incident in our Hall of Hacks is a close enough match to cite here.
We would rather say that than attach a famous name to a different bug.
Where the testing happens
Everything on this page is static and offline. The source is analysed in your browser, the chains
are composed from the evidence that analysis produced, and no transaction is constructed, signed or sent
at any point — against mainnet or anywhere else. Where a future release executes a proof of concept, it
will run against a local fork or a testnet, never against a live contract holding real money, and
the verdict it produces will say so.
That is a product boundary as much as a safety one. A tool that ran real exploit attempts against
arbitrary mainnet addresses on request would be an attack service with an audit page attached, and it is
not what this is.
What this cannot tell you
Composed from what the scan could prove about this source: detector hits, the parsed function table, access-control flags and the call graph. A scenario appears only when every one of its preconditions is satisfied, and each one shows its evidence. Confidence is how much of the chain the scan PROVED — it is not a probability that an attack would succeed, which depends on live state (balances, liquidity, who holds the keys) that static analysis cannot see. Steps are conceptual, not runnable exploits.
Silence is not a certificate. A contract that produces no chain here has produced no chain
from the evidence a static read can gather — business logic, economic design and who
actually holds the keys are outside that. Those are what a
full audit is for.