Advanced

Chapter 12 / 15

UUPS Upgradeable

upgradeable_by, version, migrate — safe contract upgrades.


The Universal Upgradeable Proxy Standard (EIP-1822) separates logic from storage. Covenant's upgradeable_by and version keywords generate a compliant UUPS implementation. The migrate from N to M block handles storage layout migrations with a re-initialization guard so migration logic can only run once.

counter_v1.cov
record CounterV1 {
    value:   u256;
    owner:   address;
    version: 1;    // compile-time version constant

    upgradeable_by: self.owner;  // who can authorize an upgrade

    action set(v: u256) only(self.owner) {
        self.value = v;
    }

    action increment() only(self.owner) {
        self.value += 1;
    }

    // Migration from version 0 (unversioned) to version 1
    // Runs once at upgrade time, never again
    migrate from 0 to 1 {
        // Initialize new fields added in V1
        // (owner was always present; nothing new here)
    }
}
counter_v2.cov — adding a step field
record CounterV2 {
    value:   u256;
    owner:   address;
    step:    u256;    // new in V2
    version: 2;

    upgradeable_by: self.owner;

    action set(v: u256) only(self.owner) {
        self.value = v;
    }

    action set_step(s: u256) only(self.owner) {
        self.step = s;
    }

    action increment() only(self.owner) {
        self.value += self.step;
    }

    // Storage migration: initialize step when upgrading from V1
    migrate from 1 to 2 {
        self.step = 1;   // sensible default
    }

    // Guard against re-initialization: compiler enforces this runs once
}
Deploying and upgrading
# Deploy V1 through a UUPS proxy
covenant deploy counter_v1.cov --proxy uups --network sepolia

# After logic change, deploy V2 and upgrade the proxy
covenant deploy counter_v2.cov --network sepolia
covenant upgrade   --network sepolia

Annotations

version: Nis a compile-time constant embedded in the bytecode. The upgrade mechanism verifies version monotonicity.
upgradeable_by: exprgenerates an _authorizeUpgrade override that requires expr to be true.
migrate from N to M { ... }generates an initializer guarded by a storage flag — it cannot be called twice (re-initialization guard).
Storage layoutThe compiler validates that V2 does not change the slot positions of V1 fields — only appending new fields at the end is allowed.

Key takeaways