Deploying Contracts to BattleChain

Learn how to deploy smart contracts using BattleChainDeployer and understand deployment options

This tutorial teaches you how to deploy contracts to BattleChain and why the deployment method matters for the attack mode workflow.

ℹ️

Audience: Developers deploying contracts to BattleChain.

Prerequisites: Familiarity with Solidity and smart contract deployment.

The easiest way to deploy is with battlechain-lib, which handles address resolution, CreateX routing, and AttackRegistry registration automatically:

forge install cyfrin/battlechain-lib

Add the remapping to foundry.toml:

remappings = [
    "battlechain-lib/=lib/battlechain-lib/src/",
]
⚠️

Contract size limit: 24,576 bytes

BattleChain enforces the standard EVM contract size limit (EIP-170). If your compiled bytecode exceeds 24,576 bytes, deployment will fail. Enable the Solidity optimizer in foundry.toml to reduce bytecode size:

[profile.default]
optimizer = true
optimizer_runs = 200

Deploy with helpers

Inherit BCDeploy (deploy only) or BCScript (deploy + agreement + attack mode):

import { BCDeploy } from "battlechain-lib/BCDeploy.sol";

contract Deploy is BCDeploy {
    function run() external {
        vm.startBroadcast();

        // Simple deployment
        address token = bcDeployCreate(type(MyToken).creationCode);

        // Deterministic address (same across chains)
        bytes32 salt = keccak256("vault-v1");
        address vault = bcDeployCreate2(
            salt,
            abi.encodePacked(type(MyVault).creationCode, abi.encode(token))
        );

        vm.stopBroadcast();
    }
}

On BattleChain, bcDeployCreate* calls go through BattleChainDeployer, which wraps CreateX and auto-registers your contracts with the AttackRegistry. On any other supported chain (190+ EVM chains), the same functions route directly through CreateX (0xba5Ed...), giving you deterministic addresses without any code changes.

💡

Use bcDeployCreate2 when you need the same contract address across BattleChain and mainnet. The salt + bytecode determines the address, so it's identical on every chain where CreateX is deployed.

Available deploy helpers

HelperAddress DeterminismNotes
bcDeployCreate(bytecode)Nonce-basedSimplest option
bcDeployCreate2(salt, bytecode)Salt + bytecodeSame address cross-chain
bcDeployCreate3(salt, bytecode)Salt onlyAddress independent of code

All deployed addresses are tracked automatically. Call getDeployedContracts() to retrieve them for use with Safe Harbor agreement scoping.


Why BattleChainDeployer?

BattleChain tracks which contracts are eligible for attack mode through the AttackRegistry. When you deploy via BattleChainDeployer (which bcDeployCreate* uses automatically on BattleChain), your contracts are automatically registered, which means:

  • You're recorded as the deployer on-chain
  • You can request attack mode without extra authorization steps
  • The DAO has on-chain proof of deployment origin

Without BattleChainDeployer, you can still enter attack mode, but the process requires more DAO scrutiny.

Direct BattleChainDeployer Usage

If you prefer not to use battlechain-lib, you can call BattleChainDeployer directly. It wraps CreateX, giving you access to all standard deployment patterns:

CREATE (Simple Deployment)

BattleChainDeployer deployer = BattleChainDeployer(BATTLECHAIN_DEPLOYER_ADDRESS);

// Simple deployment
bytes memory bytecode = type(MyContract).creationCode;
address deployed = deployer.deployCreate(bytecode);

CREATE2 (Deterministic Address)

// Deterministic address based on salt + bytecode
bytes32 salt = keccak256("my-contract-v1");
bytes memory bytecode = type(MyContract).creationCode;

address deployed = deployer.deployCreate2(salt, bytecode);

CREATE3 (Address Independent of Bytecode)

// Address depends only on salt, not bytecode
bytes32 salt = keccak256("my-contract-v1");
bytes memory bytecode = type(MyContract).creationCode;

address deployed = deployer.deployCreate3(salt, bytecode);

All Available Methods

MethodAddress DeterminismNotes
deployCreate()Nonce-basedSimplest option
deployCreateAndInit()Nonce-basedDeploys + calls init
deployCreateClone()Nonce-basedEIP-1167 proxy
deployCreate2()Salt + bytecodeSame address cross-chain
deployCreate2AndInit()Salt + bytecodeDeploy + init
deployCreate2Clone()Salt + implDeterministic proxy
deployCreate3()Salt onlyAddress independent of code
deployCreate3AndInit()Salt onlyDeploy + init

What Happens After Deployment

Every deployment through BattleChainDeployer (or bcDeployCreate* on BattleChain) automatically calls AttackRegistry.registerDeployment(), which:

  1. Records the contract address
  2. Sets its state to NEW_DEPLOYMENT
  3. Records you as the deployer
  4. Authorizes you as the agreement owner for that contract
// You can verify registration
address deployer = attackRegistry.getContractDeployer(myContract);
// deployer == your address

IAttackRegistry.ContractState state = attackRegistry.getAgreementState(myContract);
// state == NEW_DEPLOYMENT (1)

Deploying Without BattleChainDeployer

If your contracts are already deployed (e.g., through a custom factory or script), you can still enter the attack flow:

  1. Create your Safe Harbor agreement as normal
  2. Use requestUnderAttackByNonAuthorized() instead of requestUnderAttack()
attackRegistry.requestUnderAttackByNonAuthorized(agreementAddress);
⚠️

Non-BattleChainDeployer contracts face extra DAO scrutiny because there's no on-chain proof of deployment origin. The DAO may take longer to approve or may request additional information.

Next Steps