Beacon Proxy
Beacon Proxy is an advanced proxy pattern that allows multiple proxy contracts to share a single beacon contract that determines their implementation. This pattern is particularly useful for scenarios where you want to upgrade multiple proxy contracts simultaneously by updating a single beacon.
The OpenZeppelin Stylus Contracts provides a complete implementation of the Beacon Proxy pattern, including the BeaconProxy contract and UpgradeableBeacon contract.
Understanding Beacon Proxy
The Beacon Proxy pattern consists of three main components:
- Beacon Contract: A contract that stores the current implementation address.
 - Beacon Proxy: Multiple proxy contracts that delegate to the beacon for their implementation.
 - Implementation Contract: The actual logic contract that gets executed.
 
How It Works
- Multiple 
BeaconProxycontracts are deployed, each pointing to the sameUpgradeableBeacon. - The 
UpgradeableBeaconstores the current implementation address. - When a call is made to any 
BeaconProxy, it queries the beacon for the current implementation. - The proxy then delegates the call to that implementation.
 - To upgrade all proxies, you only need to update the beacon’s implementation.
 
Benefits
- Mass Upgrades: Upgrade multiple proxies with a single transaction.
 - Gas Efficiency: Shared beacon reduces storage costs.
 - Consistency: All proxies always use the same implementation.
 - Centralized Control: Single point of control for upgrades.
 - Trust Minimization: Proxies can verify the beacon’s implementation.
 
Basic Beacon Proxy Implementation
Here’s how to implement a basic beacon proxy:
use openzeppelin_stylus::proxy::{
    beacon::{proxy::BeaconProxy, IBeacon},
    erc1967,
    IProxy,
};
use stylus_sdk::{
    abi::Bytes,
    alloy_primitives::Address,
    prelude::*,
    ArbResult,
};
#[entrypoint]
#[storage]
struct MyBeaconProxy {
    beacon_proxy: BeaconProxy,
}
#[public]
impl MyBeaconProxy {
    #[constructor]
    fn constructor(
        &mut self,
        beacon: Address,
        data: Bytes,
    ) -> Result<(), erc1967::utils::Error> {
        self.beacon_proxy.constructor(beacon, &data)
    }
    /// Get the beacon address
    fn get_beacon(&self) -> Address {
        self.beacon_proxy.get_beacon()
    }
    /// Get the current implementation address from the beacon
    fn implementation(&self) -> Result<Address, Vec<u8>> {
        self.beacon_proxy.implementation()
    }
    /// Fallback function that delegates all calls to the implementation
    #[fallback]
    fn fallback(&mut self, calldata: &[u8]) -> ArbResult {
        unsafe { self.do_fallback(calldata) }
    }
}
unsafe impl IProxy for MyBeaconProxy {
    fn implementation(&self) -> Result<Address, Vec<u8>> {
        self.beacon_proxy.implementation()
    }
}Upgradeable Beacon Implementation
The UpgradeableBeacon contract manages the implementation address and provides upgrade functionality:
use openzeppelin_stylus::{
    access::ownable::{IOwnable, Ownable},
    proxy::beacon::{IBeacon, IUpgradeableBeacon, UpgradeableBeacon},
};
use stylus_sdk::{
    alloy_primitives::Address,
    prelude::*,
};
#[entrypoint]
#[storage]
struct MyUpgradeableBeacon {
    beacon: UpgradeableBeacon,
}
#[public]
impl MyUpgradeableBeacon {
    #[constructor]
    fn constructor(
        &mut self,
        implementation: Address,
        initial_owner: Address,
    ) -> Result<(), beacon::Error> {
        self.beacon.constructor(implementation, initial_owner)
    }
    /// Upgrade to a new implementation (only owner)
    fn upgrade_to(
        &mut self,
        new_implementation: Address,
    ) -> Result<(), beacon::Error> {
        self.beacon.upgrade_to(new_implementation)
    }
}
#[public]
impl IBeacon for MyUpgradeableBeacon {
    fn implementation(&self) -> Result<Address, Vec<u8>> {
        self.beacon.implementation()
    }
}
#[public]
impl IOwnable for MyUpgradeableBeacon {
    fn owner(&self) -> Address {
        self.beacon.owner()
    }
    fn transfer_ownership(&mut self, new_owner: Address) -> Result<(), Vec<u8>> {
        self.beacon.transfer_ownership(new_owner)
    }
    fn renounce_ownership(&mut self) -> Result<(), Vec<u8>> {
        self.beacon.renounce_ownership()
    }
}
#[public]
impl IUpgradeableBeacon for MyUpgradeableBeacon {
    fn upgrade_to(&mut self, new_implementation: Address) -> Result<(), Vec<u8>> {
        Ok(self.beacon.upgrade_to(new_implementation)?)
    }
}Custom Beacon Implementation
You can also implement your own beacon contract by implementing the IBeacon trait:
use openzeppelin_stylus::proxy::beacon::IBeacon;
use stylus_sdk::{
    alloy_primitives::Address,
    prelude::*,
    storage::StorageAddress,
};
#[entrypoint]
#[storage]
struct MyCustomBeacon {
    implementation: StorageAddress,
    admin: StorageAddress,
}
#[public]
impl MyCustomBeacon {
    #[constructor]
    fn constructor(&mut self, implementation: Address, admin: Address) {
        self.implementation.set(implementation);
        self.admin.set(admin);
    }
    /// Upgrade implementation (only admin)
    fn upgrade_implementation(&mut self, new_implementation: Address) -> Result<(), Vec<u8>> {
        if self.admin.get() != msg::sender() {
            return Err("Only admin can upgrade".abi_encode());
        }
        if !new_implementation.has_code() {
            return Err("Invalid implementation".abi_encode());
        }
        self.implementation.set(new_implementation);
        Ok(())
    }
}
#[public]
impl IBeacon for MyCustomBeacon {
    fn implementation(&self) -> Result<Address, Vec<u8>> {
        Ok(self.implementation.get())
    }
}Constructor Data
Like ERC-1967 proxies, beacon proxies support initialization data:
impl MyBeaconProxy {
    #[constructor]
    fn constructor(
        &mut self,
        beacon: Address,
        data: Bytes,
    ) -> Result<(), erc1967::utils::Error> {
        // If data is provided, it will be passed to the implementation
        // returned by the beacon during construction via delegatecall
        self.beacon_proxy.constructor(beacon, &data)
    }
}The data parameter can be used to:
- Initialize storage: Pass encoded function calls to set up initial state.
 - Mint initial tokens: Call mint functions on token contracts.
 - Set up permissions: Configure initial access control settings.
 - Empty data: Pass empty bytes if no initialization is needed.
 
Example: Initializing with Data
use alloy_sol_macro::sol;
use alloy_sol_types::SolCall;
sol! {
    interface IERC20 {
        function mint(address to, uint256 amount) external;
    }
}
// In your deployment script or test
let beacon = deploy_beacon();
let implementation = deploy_implementation();
let initial_owner = alice;
let initial_supply = U256::from(1000000);
// Encode the mint call
let mint_data = IERC20::mintCall {
    to: initial_owner,
    amount: initial_supply,
}.abi_encode();
// Deploy beacon proxy with initialization data
let proxy = MyBeaconProxy::deploy(
    beacon.address(),
    mint_data.into(),
).expect("Failed to deploy beacon proxy");Storage Layout Safety
Beacon proxies use ERC-1967 storage slots for safety:
Benefits
- No Storage Collisions: Implementation storage cannot conflict with proxy storage.
 - Predictable Layout: Storage slots are standardized and well-documented.
 - Upgrade Safety: New implementations can safely use any storage layout.
 - Gas Efficiency: No need for complex storage gap patterns.
 
Implementation Storage
Your implementation contract can use any storage layout without worrying about conflicts:
#[entrypoint]
#[storage]
struct MyToken {
    // These fields are safe to use - they won't conflict with beacon proxy storage
    balances: StorageMapping<Address, U256>,
    allowances: StorageMapping<(Address, Address), U256>,
    total_supply: StorageU256,
    name: StorageString,
    symbol: StorageString,
    decimals: StorageU8,
    // ... any other storage fields
}Best Practices
- Trust the beacon: Ensure you control or trust the beacon contract, as it determines all proxy implementations.
 - Use proper access control: Implement admin controls for beacon upgrade functions.
 - Test mass upgrades: Ensure all proxies work correctly after beacon upgrades.
 - Monitor beacon events: Track beacon upgrades for transparency.
 - Handle initialization data carefully: Only send value when providing initialization data.
 - Document beacon ownership: Clearly document who controls the beacon.
 - Use standardized slots: Don’t override the ERC-1967 storage slots in your implementation.
 - Consider beacon immutability: Beacon proxies cannot change their beacon address after deployment.
 
Common Pitfalls
- Untrusted beacon: Using a beacon you don’t control can lead to malicious upgrades.
 - Beacon immutability: Beacon proxies cannot change their beacon address after deployment.
 - Missing access control: Protect beacon upgrade functions with proper access control.
 - Storage layout changes: Be careful when changing storage layout in new implementations.
 - Incorrect initialization data: Ensure initialization data is properly encoded.
 - Sending value without data: Beacon proxies prevent sending value without initialization data.
 
Use Cases
Beacon proxies are particularly useful for:
- Token Contracts: Multiple token instances sharing the same implementation.
 - NFT Collections: Multiple NFT contracts with identical logic.
 - DeFi Protocols: Multiple vault or pool contracts.
 - DAO Governance: Multiple governance contracts.
 - Cross-chain Bridges: Multiple bridge contracts on different chains.
 
Related Patterns
- Basic proxy: Basic proxy pattern using 
delegate_callfor upgradeable contracts. - Beacon Proxy: Multiple proxies pointing to a single beacon contract for mass upgrades of the implementation contract address.
 - UUPS Proxy: The Universal Upgradeable Proxy Standard (UUPS) that is a minimal and gas-efficient pattern for upgradeable contracts.