$npx -y skills add OpenZeppelin/openzeppelin-skills --skill upgrade-stylus-contractsUpgrade Stylus smart contracts using OpenZeppelin proxy patterns on Arbitrum. Use when users need to: (1) make Stylus Rust contracts upgradeable with UUPS or Beacon proxies, (2) understand Stylus-specific proxy mechanics (logic_flag, WASM reactivation), (3) integrate UUPSUpgradea
| 1 | # Stylus Upgrades |
| 2 | |
| 3 | ## Contents |
| 4 | |
| 5 | - [Stylus Upgrade Model](#stylus-upgrade-model) |
| 6 | - [Proxy Patterns](#proxy-patterns) |
| 7 | - [Access Control](#access-control) |
| 8 | - [Upgrade Safety](#upgrade-safety) |
| 9 | |
| 10 | ## Stylus Upgrade Model |
| 11 | |
| 12 | Stylus contracts run on Arbitrum as WebAssembly (WASM) programs alongside the EVM. They share the same state trie, storage model, and account system as Solidity contracts. Because of this, **EVM proxy patterns work identically** for Stylus — a Solidity proxy can delegate to a Stylus implementation and vice versa. |
| 13 | |
| 14 | | | Stylus | Solidity | |
| 15 | |---|---|---| |
| 16 | | **Proxy mechanism** | Same — `delegatecall` to implementation contract | `delegatecall` to implementation contract | |
| 17 | | **Storage layout** | `#[storage]` fields map to the same EVM slots as equivalent Solidity structs | Sequential slot allocation per Solidity rules | |
| 18 | | **EIP standards** | ERC-1967 storage slots, ERC-1822 proxiable UUID | Same | |
| 19 | | **Context detection** | `logic_flag` boolean in a unique storage slot (no `immutable` support) | `address(this)` stored as `immutable` | |
| 20 | | **Initialization** | Two-step: constructor sets `logic_flag`, then `set_version()` via proxy | Constructor + initializer via proxy | |
| 21 | | **Reactivation** | WASM contracts must be reactivated every 365 days or after a Stylus protocol upgrade | Not applicable | |
| 22 | |
| 23 | Existing Solidity contracts can upgrade to a Stylus (Rust) implementation via proxy patterns. The `#[storage]` macro lays out fields in the EVM state trie identically to Solidity, so storage slots line up when type definitions match. |
| 24 | |
| 25 | ## Proxy Patterns |
| 26 | |
| 27 | OpenZeppelin Contracts for Stylus provides three proxy patterns: |
| 28 | |
| 29 | | Pattern | Key types | Best for | |
| 30 | |---------|----------|----------| |
| 31 | | **UUPS** | `UUPSUpgradeable`, `IErc1822Proxiable`, `Erc1967Proxy` | Most projects — upgrade logic in the implementation, lighter proxy | |
| 32 | | **Beacon** | `BeaconProxy`, `UpgradeableBeacon` | Multiple proxies sharing one implementation — updating the beacon upgrades all proxies atomically | |
| 33 | | **Basic Proxy** | `Erc1967Proxy`, `Erc1967Utils` | Low-level building block for custom proxy patterns | |
| 34 | |
| 35 | ### UUPS |
| 36 | |
| 37 | The implementation contract composes `UUPSUpgradeable` in its `#[storage]` struct alongside access control (e.g., `Ownable`). Integration requires: |
| 38 | |
| 39 | 1. Add `UUPSUpgradeable` (and access control) as fields in the `#[storage]` struct |
| 40 | 2. Call `self.uups.constructor()` and initialize access control in the constructor |
| 41 | 3. Expose `initialize` calling `self.uups.set_version()` — invoked via proxy after deployment |
| 42 | 4. Implement `IUUPSUpgradeable` — `upgrade_to_and_call` guarded by access control, `upgrade_interface_version` delegating to `self.uups` |
| 43 | 5. Implement `IErc1822Proxiable` — `proxiable_uuid` delegating to `self.uups` |
| 44 | |
| 45 | The proxy contract is a thin `Erc1967Proxy` with a constructor that takes the implementation address and initialization data, and a `#[fallback]` handler that delegates all calls. |
| 46 | |
| 47 | Deploy the proxy with `set_version` as the initialization call data. Use `cargo stylus deploy` or a deployer contract. The initialization data is the ABI-encoded `setVersion` call: |
| 48 | |
| 49 | ```rust |
| 50 | let data = MyContractAbi::setVersionCall {}.abi_encode(); |
| 51 | // Pass `data` as the proxy constructor's second argument at deployment time. |
| 52 | ``` |
| 53 | |
| 54 | ### Beacon |
| 55 | |
| 56 | Multiple `BeaconProxy` contracts point to a single `UpgradeableBeacon` that stores the current implementation address. Updating the beacon upgrades all proxies in one transaction. |
| 57 | |
| 58 | ### Context detection (Stylus-specific) |
| 59 | |
| 60 | Stylus does not support the `immutable` keyword. Instead of storing `__self = address(this)`, `UUPSUpgradeable` uses a `logic_flag` boolean in a unique storage slot: |
| 61 | |
| 62 | - The implementation's **constructor** sets `logic_flag = true` in its own storage. |
| 63 | - When code runs via a proxy (`delegatecall`), the proxy's storage does not contain this flag, so it reads as `false`. |
| 64 | - `only_proxy()` checks this flag to ensure upgrade functions can only be called through the proxy, not directly on the implementation. |
| 65 | |
| 66 | `only_proxy()` also verifies that the ERC-1967 implementation slot is non-zero and that the proxy-stored version matches the implementation's `VERSION_NUMBER`. |
| 67 | |
| 68 | > **Examples:** See the `examples/` directory of the [rust-contracts-stylus repository](https://github.com/OpenZeppelin/rust-contracts-stylus) for full working integration examples of UUPS, Beacon, and related patterns. |
| 69 | |
| 70 | ## Access Control |
| 71 | |
| 72 | Upgrade functions must be guarded with access control. OpenZeppelin's Stylus contracts do **no |