Attack a Contract
Find an attackable contract on BattleChain, exploit it, collect your bounty, and walk away clean under Safe Harbor.
Review what your AI runs. AI coding tools execute real commands on your machine. Always read the commands before approving them, and never allow anything you don't understand. If in doubt, use the Manual tab and run each command yourself.
Choose your path
What You'll Need
- An AI coding tool with terminal access — Claude Code, Cursor, Windsurf, or any agent that can run terminal commands
- A test wallet with BattleChain Testnet ETH
Windows users: Use WSL2 and run your AI tool inside the WSL terminal.
Step 1 — Find an Attackable Contract
On BattleChain, any contract in attack mode (state 3 — UNDER_ATTACK) is a legitimate target under Safe Harbor.
For this quickstart, you'll attack a pre-built vulnerable vault from the starter repo. In the real world, you'd find targets through the explorer or on-chain queries.
Finding real targets: Browse all currently attackable agreements on the BattleChain explorer. To check a specific contract programmatically, use isAttackable(address) from battlechain-lib — it handles the full scope hierarchy including child contracts. See How to Find Attackable Contracts for details and usage examples.
If you just completed Deploy and Battle-Test Your Contract, your vault is already set up — skip to Step 2.
Otherwise, you need to deploy a vault first. The quickest way is to follow the deploy quickstart and come back here when your vault is in attack mode (state 3).
Step 2 — Check the Terms
Before attacking any contract, read the agreement terms. For the starter vault the terms are straightforward — but on real targets, this step is critical.
Key things to check:
- Bounty percentage — how much you keep (starter vault: 10%)
- Recovery address — where you send the remaining funds
- Retainability — whether you keep the bounty from drained funds directly, or return everything and get paid separately
- Identity requirements — whether the protocol requires you to identify yourself
- Scope — which contracts are covered by the agreement
You can view all agreement details on the BattleChain explorer — click any agreement to see its terms, covered contracts, and recovery address. The explorer API also exposes these details via GET /battlechain/agreement/:agreementAddress. For a full reference, see Bounty Terms.
Step 3 — Understand the Exploit
Open src/Attacker.sol in the starter repo. The attack exploits two things working together: the CEI violation in VulnerableVault and the hook system in MockToken.
just attack deploys a thin Exploit wrapper (src/Exploit.sol) whose constructor deploys this Attacker, approves attack mode via the testnet moderator, and runs the exploit — all in one transaction.
MockToken's hook system: Any address can register a hook contract via setTransferHook(address hook). When tokens are transferred to that address, the token calls hook.onTokenTransfer() after the transfer completes.
The vault's CEI violation: withdrawAll() calls TOKEN.transfer() before zeroing balances[msg.sender].
Put them together and the reentrancy chain looks like this:
attack()
└── TOKEN.setTransferHook(address(this)) ← register as our own hook
└── vault.deposit(seedAmount) ← establish a balance
└── vault.withdrawAll()
└── TOKEN.transfer(attacker, amount)
└── onTokenTransfer() ← hook fires, balance not yet zeroed
└── vault.withdrawAll()
└── TOKEN.transfer(attacker, amount)
└── onTokenTransfer() ← still not zeroed
└── ... ← repeats until vault is empty
Once the vault is drained, the Safe Harbor settlement runs automatically:
uint256 total = TOKEN.balanceOf(address(this)); // 1,100 = vault's 1,000 + your 100 seed
uint256 recovered = total - SEED_AMOUNT; // 1,000 — the protocol's funds only
uint256 bounty = (recovered * BOUNTY_BPS) / 10_000; // 10% of recovered = 100
TOKEN.transfer(RECOVERY_ADDRESS, recovered - bounty); // return 900 to the protocol
TOKEN.transfer(BENEFICIARY, bounty + SEED_AMOUNT); // keep 100 bounty + your 100 seed
The bounty is taken on the protocol's recovered funds only — you don't earn a bounty on the seed you deposited, and you get that seed back. You're not stealing. The protocol gets the majority of funds back minus the agreed bounty. Everyone knew the rules when the agreement was signed.
Step 4 — Execute the Attack
Run this yourself in your terminal (you'll be prompted for your keystore password):
just attack
Expected output:
Vault balance before: 1000 tokens
Deploying Exploit (approves attack mode + drains in one tx)...
--- Vault drained ---
Vault before: 1000 tokens
Vault after: 0 tokens
Returned to protocol: 900 tokens
Bounty kept (yours): 100 tokens (plus your reclaimed seed)
Step 5 — Verify and Settle
Search your VAULT_ADDRESS on the BattleChain explorer — you'll see the attack transaction, the vault balance drop to zero, and the bounty transfer to your wallet.
Verify the math
The agreement set a 10% bounty (BOUNTY_BPS = 1_000). To drive the reentrancy, the exploit first deposits 100 tokens of its own as seed, so 1,100 gets drained (the vault's 1,000 + your 100 seed). The bounty is 10% of the recovered protocol funds — you don't earn a bounty on your own seed:
- Recovered (protocol funds):
1,100 − 100 seed = 1,000 tokens - Bounty (yours):
1,000 × 10% = 100 tokens - Returned to protocol:
1,000 − 100 = 900 tokens - Your 100-token seed is returned to you on top, so your wallet nets +100.
If the numbers don't look right, check that RECOVERY_ADDRESS in your .env matches the address in the agreement — that's where the returned funds go.
What Just Happened
You found a vulnerable contract on BattleChain, verified it was in attack mode, exploited it, and collected a bounty — all under Safe Harbor protection.
- Found a contract open for attack
- Checked the agreement terms and bounty structure
- Exploited a reentrancy vulnerability
- Settled automatically — bounty kept, remaining funds returned to the protocol
The same workflow applies to real contracts. Whitehats on BattleChain earn bounties by finding vulnerabilities before they reach mainnet, and protocols get battle-tested code they can deploy with confidence.
Find Attackable Contracts
Discover real targets beyond the starter vault using on-chain queries
Claim Bounties
Detailed bounty calculations, caps, retainability, and settlement mechanics
Troubleshooting
Forge scripts failing
Add --skip-simulation to any failing forge script command. Forge's local gas estimation doesn't work reliably on BattleChain.
Tell your AI: "All forge script commands need the --skip-simulation flag."
Stuck pending transaction
Tell your AI: "I have a stuck transaction at nonce [N]. Send a replacement with a higher gas price using cast send with --value 0 to clear it."
Out-of-gas failures
If a forge script command fails with a vague error even with --skip-simulation, try adding -g 300 to use 3x the estimated gas.