Skip to content

coinspect/learn-evm-attacks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

417 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Learn EVM Attacks

A collection of Foundry tests reproducing exploits, bug bounty reports, and theoretical vulnerabilities on EVM chains. Diagrams and context links accompany each attack reproduction to make it more helpful as a reference or study material for anyone trying to learn more about vulnerabilities in smart contract systems.

Important

Some tests need access to archive data as they require state from old blocks. If a test is failing, make sure to set up an archive node as the JSON-RPC URL in foundry.toml. Infura provides free access to archive data.

Want to take a quick look? Just go to a vulnerability folder (say, MBCToken). Read the README or jump ahead to running the reproduction in your computer! You only need Foundry installed.

$ git clone https://github.com/coinspect/learn-evm-attacks
$ forge install
$ forge test --match-contract Exploit_MBCToken -vvv

Index

We now have 40 reproduced exploits. Of those 40, we have chosen a few in case you want to start studying up with some of the most interesting ones.

  • Tornado Cash Governance Takeover is an excellent way to show the dangers of DELEGATECALL and the perils of governance systems.
  • Furucombo another excellent way to show the dangers of DELEGATECALL.
  • MBC Token is a primer on how sandwich attacks can be made with an interesting backstory on suspicious tokenomics.
  • Uranium is a great excuse to study up on the actual code that guards the famouse AMM constant product x*y=k.

To run an specific exploit, you can just use:

forge test --match-contract Exploit_MBCToken -vvv

Vary the amount of verbosity (-v, -vv...) according to the data you want. -vvvv includes traces!

The full list is below:

Access Control

Bad Data Validation

Business Logic

Reentrancy

Bridges

Contributing

To contribute, create a new file inside the most appropriate category. Use the template.txt file in the test folder including the information related to the attack.

Utils that perform flashloans and swaps are provided in test/utils to ease the job of reproducing future attacks. Also, modules that provide enhanced features to Foundry are included in the test/modules folder.

The tests should pass if the attacker succeeded, for examples: your requires should show that the attacker has more balance after the attack than before.

Warming the RPC cache for a new attack

Each attack uses createSelectFork to replay on-chain state from a specific block. To allow attacks to run offline (no RPC needed at runtime), we pre-cache all required RPC responses. When you add a new attack you must warm the cache so that Codespaces/devcontainers can run it without a live RPC endpoint.

1. Warm the cache for a single attack

cache_warm.sh takes a real RPC URL, the fork block number, and the test contract name. It spins up a temporary Anvil fork, runs the test to populate Foundry's RPC cache, and captures block metadata into rpc_cache/:

bash scripts/cache_warm.sh <rpc_url> <block_number> <test_contract>

# Example:
bash scripts/cache_warm.sh "$ETH_RPC_URL" 14684300 Exploit_FeiProtocol

This populates the unified rpc_cache/ directory:

  • rpc_cache/blocks/<chainId>/<block_number>/ β€” block JSON and chain metadata (eth_chainId, eth_gasPrice, net_version) used by the mock RPC proxy
  • rpc_cache/foundry/ β€” a copy of Foundry's internal RPC cache (bind to localhost:8546)

The rpc_cache/ directory must be committed to the repo.

2. Warm the cache for all attacks at once

If you need to rebuild the entire cache (or are setting up for the first time), use warm_all.sh. It iterates over every .devcontainer/*/devcontainer.json, extracts the contract name, block number, and chain, then calls cache_warm.sh for each:

export ETH_RPC_URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
export BSC_RPC_URL="https://bsc-dataseed.binance.org"
# ... set POLYGON_RPC_URL, ARBITRUM_RPC_URL, FANTOM_RPC_URL, GNOSIS_RPC_URL as needed

bash scripts/warm_all.sh

3. Verify the cache works offline

test_all_cached.sh wipes Foundry's local cache, restores from the committed rpc_cache/foundry/, starts the mock RPC proxy (mock_rpc_proxy.js) for each attack, and runs every test without any external RPC:

bash scripts/test_all_cached.sh

If any attacks fail, you can re-warm just those with warm_failed.sh (edit the script to list the failing attacks) and re-run the verification.

How it works at runtime (devcontainers)

When a user opens an attack in a Codespace, attach-run.sh starts the lightweight Node.js mock RPC proxy on port 8546. This proxy serves cached block metadata from rpc_cache/blocks/ while Foundry's own RPC cache handles all eth_call/eth_getStorageAt requests. No external RPC is needed β€” the attack replays entirely from the committed cache.

Past work and further study

  • DefiHackLabs has a similar repository with more exploits and more focus on the test reproductions alone, with no context or further explanations. It is nevertheless great if you only care about the attack reproductions! Go check it out.

Troubleshooting

The main reason why tests fail is due to failures on the RPC providers we have set up as defaults. Please either:

  • Try again
  • Change the corresponding provider in the foundry.toml

If a reproduction is still failing (ie: it reverts), try to:

  • Clean Forge's cache: forge cache clean
  • Update Foundry: foundryup