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. You can also query the explorer API programmatically — use GET /battlechain/agreement/by-contract/:address to check if a specific contract is in scope, or GET /battlechain/agreement/:address to see all covered contracts for an agreement. For on-chain querying via the AttackRegistry, see How to Find Attackable Contracts.
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.
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));
uint256 bounty = (total * BOUNTY_BPS) / 10_000; // 10%
TOKEN.transfer(RECOVERY_ADDRESS, total - bounty); // return 90% to protocol
TOKEN.transfer(OWNER, bounty); // keep 10%
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 attacker...
--- Vault drained ---
Vault before: 1000 tokens
Vault after: 0 tokens
Bounty kept: 100 tokens
Returned to protocol: 900 tokens
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). The vault had 1,000 seeded tokens plus your 100 seed tokens = 1,100 total.
- Bounty:
1,100 × 10% = 110 tokens - Returned:
1,100 - 110 = 990 tokens
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.