How to Find Critical Vulnerabilities in Smart Contracts
Quick answer. To find critical vulnerabilities in smart contracts, understand the codebase deeply before attacking it. The GTDA method does that in four steps: set feature Goals, Tag questions, Diagram call paths, then Attack with your toolbox.
Most guides on how to find critical vulnerabilities in smart contracts start with a list of attacks. In our experience, that is the last step, not the first.
This guide covers the GTDA method developed at Guardian: Goals, Tags, Diagram and Attack. It starts with what vulnerabilities really are, then the focus habits that make an audit productive, then each step in turn.
Along the way, it answers the questions security researchers asked when they first learned the method.
What Vulnerabilities Really Are
A common belief is that security means learning the types of vulnerabilities, then spotting them in code. There is some truth to it, but it is not the core.
Knowing what a reentrancy attack or a denial of service attack looks like builds your toolbox. That toolbox is only one piece of the skill set.
Memorized edge cases will not find the interesting bugs. In a contest, a finding like "this does not use safeTransferFrom" pays almost nothing.
More often, vulnerabilities are plain bugs and logic errors. They are edge cases unique to the codebase, and they do not fit a cookie-cutter checklist.
If they were just rules to memorize, an AST parser or an AI model could solve every codebase. At the time of recording (early 2024), nobody had done that for smart contract security.
Bot racers existed, but they caught the minor issues that get duplicated anyway. The manual skill stays scarce, because one person cannot do ten audits at once.
Understanding is the key
The key is understanding the codebase at a deep level. That is the most manual, intensive part of an audit.
You need to know what the developers were thinking and why they structured the code as they did.
Once you have that context, you can find the edge cases that actually affect the system and see their net effect.
That starts with understanding the DeFi concept itself. Derive it from first principles, so you can touch and feel the formulas and know why things work as they do.
Once you have derived a CDP system yourself, another CDP codebase stands out. You notice where it differs from your own reasoning, or where it misses an edge case.
Only then can you get into any codebase quickly and use your toolbox of edge cases and attacks to break it. For a CDP primer, see what a CDP is.
Do you need to know the secure version?
Not exactly. You need to know what the protocol is trying to do, and why.
Take a perpetuals protocol. Someone can explain perpetuals, give you the formulas and walk through examples, but that is a surface understanding.
Apply it to code yourself. Then you see how it really works, and the issues you hit when you decrease a position and account for fees.
With that experience, you dive into a new codebase without the initial fog. Fog is an auditor's number one enemy.
Auditing is really the skill of reading and understanding DeFi codebases. The attacker's mindset and the toolbox come on top, and they are the easy, fun part.
If you have ever thought "this looks wrong, but I have no idea if it is a finding", you were still in the first phase.
Findings come once you understand the code and can think through its edge cases.
Make Every Session Count
Before the method itself, set up your sessions to be productive. Three habits matter.
1. Remove all distractions
With chat apps pinging you, it is very hard to focus. Five distracted hours can produce no breakthroughs, while one focused hour can crush a codebase.
Keep only the codebase window open, plus a notepad.
2. Work in timed sessions
Either you are auditing 100%, or you are not auditing at all. No TV in the background: work, then rest.
A 60-minute session with a timer works well. The timer makes you notice when your mind starts to wander.
That usually happens at the hard parts, like math or decimals. Catch it, drag yourself back and push through those steps.
What if you find something big near the end? Reset the timer and keep going. Three sessions in a row is fine when you are making progress.
If you are not in the middle of something, stop. Take five to fifteen minutes, eat or go for a walk, and come back clear.
3. Set goals and keep a notepad
Goals keep you oriented. In an 8,000-line codebase, it is easy to meander and glaze over the code without achieving anything.
A simple notepad layout:
- Goals at the top.
- Notes in the middle.
- Findings at the bottom, each with its location (for example, a router contract at line 102), a description and a recommendation.
Guardian tracks findings in team threads. Rather than keep that chat open, write findings in the notepad, then copy them over at the end of the session.
G: Set Goals From Features
The first phase of an audit is getting over the context barrier. Break it into goals, starting with what the system does.
Split the system into features. A CDP protocol, for example, should have five:
- Borrow
- Repay
- Add collateral
- Remove collateral
- Liquidate
The list changes with the type of protocol. Sometimes heavy terminology makes it hard to tell whether you are looking at lending or liquid staking.
One shortcut: check the project's Twitter and user-facing documentation. Whatever users are told they can do, such as lending, is a feature to write down.
Next, briefly read the docs for more on each goal, without spending long there. Then scan every file in scope to see which functions each contract holds.
You will not understand them yet. The point is to see where the logic lives and what is on your plate, like ripping off a bandage.
Walk the code path for each feature
Now take each feature and walk its code path. Adding collateral is a good place to start in a CDP system.
Find the entry point in the documentation if you can. If the docs are poor, look for a router, or list every external function and look for names like deposit or addCollateral.
Once you have the starting point, follow it like a thread. Note which contracts are called and what logic runs.
Reading functions in isolation is much harder, because you do not know what they are trying to achieve. Framed as "this is how collateral gets added", the same code becomes far easier to understand.
That framing depends on knowing the DeFi concept first, so you know what to expect.
T: Tag Questions for Later
As you walk the code paths, you will have questions and spot edge cases. You will get ideas for attacks.
None of that matters yet. Resist the rabbit hole, because you do not know how the system works and you will waste a lot of time.
Leave an @audit tag in the code instead, so you can grep for it later. When you first open a codebase, do not even look for vulnerabilities: lock in on understanding.
When you come back with full context, most tags take a minute or two. Either it is not an issue, or it is, and you know which knob to turn to combine two edge cases.
D: Diagram the Call Paths
For complex codebases especially, diagram the paths as you walk them. A tool such as Whimsical keeps diagrams clean.
GMX V1 is a good example: a central vault with many ways to interact with it. One goal was understanding how to mint GLP, which provides liquidity to the vault.
That diagram traced the call from the entry contract into addLiquidityForAccount, then buyUSDG on the vault. Other diagrams covered creating and executing orders.
Dolomite was the most confusing codebase of all, with inheritance taken to the nth degree.
Its deposit diagram ran from the vault entry point to a generic trader proxy in the margin repository, then to operate on the Dolomite margin contract.
Withdrawals were even worse. Without mapping it out, there is no way to hold all of that in your head.
A diagram looks complex at the end, but you build it brick by brick. That is how you make a complex codebase digestible.
A diagram is also an artifact you can share.
Teammates can poke at it, tell you where you are wrong and learn the system from it, so it pays off most when you audit as a team.
A: Attack With Your Toolbox
Goals, tags and diagrams are the painful part. They are 75% of GTDA, all spent getting past the context phase.
Attack is the fun part, and where the findings are. Only now does your toolbox of reentrancy, denial of service and other attacks matter: it is the last 25%, the icing on top.
Once you have finished your goals and walked every path, you should understand the system well. Start by going back to your audit tags.
Many will be plainly invalid now that you understand the code, so delete them. Explore the interesting ones further: if you had an idea for a reentrancy, now is the time to think it through.
The second pass
When the tags run out, make a second pass with an attacker's mindset. Follow the execution paths again, this time line by line.
Question everything. If a parameter is a uint256, what happens with zero, or with the maximum value?
This is where the toolbox comes in:
- Reentrancy
- Denial of service
- Gas griefing
- Zero amounts
- Single-wei or very small amounts
- Stepwise jumps in token values
For a wider set of patterns, see the smart contract vulnerabilities list.
This is also the time to write tests. Fill in areas with poor test coverage, and identify invariants to fuzz.
Fuzzing is undervalued. It is as powerful as manual analysis, if not more.
What is a stepwise jump?
Many DeFi systems give stakers a share of a pool's rewards or value. When rewards are added, the pool's value jumps in one step.
So stake right before the jump, by front-running it, and unstake right after. You collect the gain risk-free, staked for a single block instead of the whole period.
In effect, you sandwich the reward compound. It is a common finding, because it is a fundamental problem in distributing rewards. See sandwich attacks.
Does it happen in Uniswap pools? In Uniswap it is just-in-time liquidity, which is less malicious, since it captures fees rather than distributed rewards.
With Uniswap V3, providing liquidity in a very tight range around a big swap gets the most fees from it.
Uniswap treats that as a feature, but it is a downside for LPs, and in an audit it could be a Medium.
Applying GTDA to a Practice Audit
To try the method on a practice audit:
- Set aside time with no distractions. Use a timer, or at least commit to the next two hours.
- Set up a notepad with goals, notes and findings.
- Get initial context. Do a once-over of the contracts and read the README's overview.
- List the high-level goals, such as mint and redeem.
- Walk those code paths. Do not go line by line yet: learn what each function does and which lines matter.
- Tag what you notice and stay out of rabbit holes until you understand the contracts.
- Diagram the flows. A small system fits in your head, but the practice is worth it.
- Attack. Work through the interesting tags and look for real developer bugs.
The most interesting findings are rarely an obvious reentrancy or denial of service. The vast majority are logic errors the developers made, specific to the code they wrote.
When you find a critical, write a PoC for it; see how to write a smart contract proof of concept. For a practice audit meant to be solved manually, skip fuzzing.
The GTDA Checklist
- Goals: list the features, scan every file, then walk each feature's code path from its entry point.
- Tags: mark questions, edge cases and attack ideas with
@audit, and keep moving. - Diagram: map the call paths of complex flows, and share the diagrams with your team.
- Attack: clear your tags, make a line-by-line second pass with your toolbox, then write tests and fuzz invariants.
For the full audit workflow around this method, see how to audit a smart contract.
How Guardian Helps
GTDA is the system Guardian developed for its own audits.
If you want to use it full time, Guardian hires full-time security researchers who focus on auditing, working as one collaborative team. See open roles on our careers page.
Frequently Asked Questions
What does GTDA stand for?
Goals, Tags, Diagram and Attack. Set goals from the system's features, tag questions instead of chasing them, diagram complex call paths, then attack the code with your toolbox once you understand it.
Why not start an audit by looking for vulnerabilities?
Without context, you cannot tell whether something is a bug. You lose time in rabbit holes. Understand the codebase first, tag what you notice, and most of those tags take a minute or two once you return with context.
How long should an audit session be?
Sixty minutes with a timer works well. Remove every distraction, and use the timer to notice when your mind wanders. If you are making progress when it ends, reset it; otherwise take a short break.
What are most critical vulnerabilities in smart contracts?
Most are logic errors and edge cases unique to the codebase, not textbook attacks. Finding them depends on understanding what the protocol is trying to do, and why, at a deep level.
Sources
Put the GTDA method on your codebase
Guardian researchers bring this method to every audit, from first goals to final attack.