Advanced

Chapter 13 / 15

Beacon Proxy Pattern

@proxy_compatible, deploy_proxy — upgrading many instances atomically.


The beacon proxy pattern (EIP-1967) lets one upgrade transaction update the logic for hundreds of proxy instances simultaneously. Covenant's @proxy_compatible decorator ensures the implementation contract has no constructor-side effects (safe for delegate calls), and deploy_proxy deploys a new proxy pointing at the beacon.

implementation.cov — proxy-safe logic
// @proxy_compatible disables the constructor and adds
// an initializer pattern instead
@proxy_compatible
record Vault {
    owner:   address;
    balance: amount;
    initialized: bool;

    error AlreadyInitialized();

    // Called once by the factory after proxy deployment
    action initialize(owner: address) when(!self.initialized) {
        self.owner       = owner;
        self.initialized = true;
    }

    action deposit() {
        self.balance += msg.value;
    }

    action withdraw(to: address, value: amount) only(self.owner) {
        self.balance -= value;
        transfer(to, value);
    }
}
beacon.cov — single upgrade point
record VaultBeacon {
    implementation: address;
    owner:          address;

    event Upgraded(new_impl: address);

    action upgrade(new_impl: address) only(self.owner) {
        self.implementation = new_impl;
        emit Upgraded(new_impl);
    }

    view get_implementation() -> address {
        return self.implementation;
    }
}
factory.cov — deploy many proxies
record VaultFactory {
    beacon:  address;
    vaults:  map(address => address);   // user => vault proxy
    count:   u64;

    event VaultCreated(user: address, vault: address);

    action create_vault() {
        // deploy_proxy creates a minimal proxy pointing at beacon
        let vault_addr = deploy_proxy(self.beacon);

        // Initialize the vault for the caller
        Vault(vault_addr).initialize(msg.sender);

        self.vaults[msg.sender] = vault_addr;
        self.count += 1;
        emit VaultCreated(msg.sender, vault_addr);
    }

    view vault_of(user: address) -> address {
        return self.vaults[user];
    }
}

Annotations

@proxy_compatibleadds a compile-time check that the implementation has no immutable constructor arguments and uses an initializer pattern.
deploy_proxy(beacon)deploys a minimal EIP-1967 proxy whose implementation slot points at the beacon's get_implementation() result.
Atomic upgradeCalling VaultBeacon.upgrade(newImpl) immediately affects all proxies — they delegate to the new implementation on the next call.
Storage slotsAll proxies share the same storage layout as the implementation. Never change field order in an implementation upgrade.

Key takeaways