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.
Using battlechain-lib (Recommended)
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
| Helper | Address Determinism | Notes |
|---|---|---|
bcDeployCreate(bytecode) | Nonce-based | Simplest option |
bcDeployCreate2(salt, bytecode) | Salt + bytecode | Same address cross-chain |
bcDeployCreate3(salt, bytecode) | Salt only | Address 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
| Method | Address Determinism | Notes |
|---|---|---|
deployCreate() | Nonce-based | Simplest option |
deployCreateAndInit() | Nonce-based | Deploys + calls init |
deployCreateClone() | Nonce-based | EIP-1167 proxy |
deployCreate2() | Salt + bytecode | Same address cross-chain |
deployCreate2AndInit() | Salt + bytecode | Deploy + init |
deployCreate2Clone() | Salt + impl | Deterministic proxy |
deployCreate3() | Salt only | Address independent of code |
deployCreate3AndInit() | Salt only | Deploy + init |
What Happens After Deployment
Every deployment through BattleChainDeployer (or bcDeployCreate* on BattleChain) automatically calls AttackRegistry.registerDeployment(), which:
- Records the contract address
- Sets its state to
NEW_DEPLOYMENT - Records you as the deployer
- 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:
- Create your Safe Harbor agreement as normal
- Use
requestUnderAttackByNonAuthorized()instead ofrequestUnderAttack()
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.