Attack a Contract

Find an attackable contract on BattleChain, exploit it, collect your bounty, and walk away clean under Safe Harbor.

⚔️
You are the Whitehat
Find a vulnerable contract. Exploit it. Get paid.
BattleChain contracts in attack mode are fair game. Safe Harbor agreements define the rules — bounty percentage, recovery address, and scope. You attack legally, return the majority of funds, and keep your bounty.
⚠️

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 3UNDER_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.


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.