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


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.