Skip to main content
ClaudeWave
Skill55 repo starsupdated 4mo ago

upgradeability

>

Install in Claude Code
Copy
git clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/upgradeability && cp -r /tmp/upgradeability/packages/opencode/skills/upgradeability ~/.claude/skills/upgradeability
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Upgradeability & Proxy Pattern Vulnerabilities

**2025-2026 Statistics**: Proxy-related vulnerabilities caused $340M+ in losses, with initialization bugs and storage collision being the leading causes.

---

## Why Upgradeability Fails (Root Causes)

### Root Cause 1: Initialization vs Constructor

Proxies can't use constructors; initializers can be called multiple times.

```solidity
// VULNERABLE: No initialization protection
contract ImplementationV1 {
    address public owner;
    
    function initialize(address _owner) external {
        owner = _owner;  // @audit Can be called by anyone, multiple times!
    }
}
```

**Attacker's view**: "They forgot the initializer guard. I'll take ownership."

### Root Cause 2: Storage Layout Mismatch

Upgrades must maintain exact storage slot ordering.

```solidity
// V1 Storage
contract V1 {
    address public owner;     // slot 0
    uint256 public balance;   // slot 1
}

// V2 Storage - WRONG!
contract V2 {
    uint256 public newVar;    // slot 0 - COLLISION with owner!
    address public owner;     // slot 1
    uint256 public balance;   // slot 2
}
```

### Root Cause 3: delegatecall Context

Implementation code runs with proxy's storage and context.

```solidity
// VULNERABLE: selfdestruct in implementation
contract Implementation {
    function destroy() external onlyOwner {
        selfdestruct(payable(owner));  // @audit Destroys PROXY, not implementation!
    }
}
```

### Root Cause 4: Unprotected Upgrade Function

Anyone can upgrade to malicious implementation.

```solidity
// VULNERABLE: Missing access control on upgrade
contract UUPSVulnerable is UUPSUpgradeable {
    function _authorizeUpgrade(address) internal override {
        // @audit No onlyOwner check!
    }
}
```

---

## Proxy Patterns Overview

### Transparent Proxy Pattern

```
User → Proxy (storage + delegatecall) → Implementation (logic)
         ↓
      Admin → ProxyAdmin (upgrade logic)
```

**Key Features**:
- Admin calls go to ProxyAdmin, not implementation
- Users can't accidentally call admin functions
- Requires separate ProxyAdmin contract

### UUPS Pattern (EIP-1822)

```
User → Proxy (storage + delegatecall) → Implementation (logic + upgrade)
```

**Key Features**:
- Upgrade logic in implementation
- Smaller proxy bytecode
- Risk: forgetting upgrade function in new implementation

### Beacon Pattern

```
User → Proxy → Beacon → Implementation
                 ↑
            Multiple proxies share beacon
```

**Key Features**:
- Single upgrade updates all proxies
- Good for factory patterns
- More complex architecture

---

## The Proxy Architecture Map (Core Artifact)

For each proxy system, document:

| Component | Address | Type | Storage | Risk |
|-----------|---------|------|---------|------|
| Proxy | 0x1234... | TransparentProxy | User data | Storage collision |
| Implementation | 0x5678... | Logic V1 | None | selfdestruct |
| ProxyAdmin | 0x9abc... | Admin | None | Access control |
| Beacon | N/A | N/A | N/A | N/A |

---

## Detection Patterns

### Pattern 1: Unprotected Initializer

**Root Cause**: Initialization vs Constructor

```solidity
// VULNERABLE: Can be re-initialized
contract VaultV1 {
    address public owner;
    bool private _initialized;  // @audit Wrong pattern
    
    function initialize(address _owner) external {
        require(!_initialized);  // @audit Can be front-run!
        owner = _owner;
        _initialized = true;
    }
}
```

**Attack Flow**:
1. Protocol deploys implementation
2. Protocol deploys proxy
3. Attacker front-runs initialize() call
4. Attacker becomes owner
5. Attacker drains protocol

**Search Queries**:
```
Grep("function initialize|initializer", glob="**/*.sol")
Grep("Initializable|_initialized|initializing", glob="**/*.sol")
```

**Secure Pattern**:
```solidity
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract VaultV1 is Initializable {
    address public owner;
    
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();  // Prevent implementation initialization
    }
    
    function initialize(address _owner) external initializer {
        owner = _owner;
    }
}
```

### Pattern 2: Storage Collision

**Root Cause**: Storage Layout Mismatch

```solidity
// V1
contract VaultV1 {
    address public owner;     // slot 0
    uint256 public balance;   // slot 1
}

// V2 - VULNERABLE
contract VaultV2 {
    address public newAdmin;  // slot 0 - OVERWRITES owner!
    address public owner;     // slot 1 - READS balance as address!
    uint256 public balance;   // slot 2
}
```

**Attack Flow**:
1. V1 deployed with owner = 0x1234...
2. Upgrade to V2
3. newAdmin (slot 0) now contains old owner value
4. owner (slot 1) now reads balance (corrupted)
5. Access control broken

**Search Queries**:
```
Grep("pragma solidity|contract.*is.*Upgradeable", glob="**/*.sol")
```

**Detection**:
- Use `forge inspect Contract storage-layout`
- Compare layouts between versions
- Check inheritance order

**Secure Pattern**:
```solidity
// V2 - CORRECT: Append only
contract VaultV2 {
    address public owner;     // slot 0 - SAME
    uint256 public balance;   // slot 1 - SAME
    address public newAdmin;  // slot 2 - NEW at end
}
```

### Pattern 3: UUPS Missing Upgrade Authorization

**Root Cause**: Unprotected Upgrade Function

```solidity
// VULNERABLE: No access control
contract MyUUPS is UUPSUpgradeable {
    function _authorizeUpgrade(address newImplementation) internal override {
        // @audit Empty = anyone can upgrade!
    }
}

// VULNERABLE: Wrong check
contract MyUUPS is UUPSUpgradeable {
    function _authorizeUpgrade(address newImplementation) internal override {
        require(msg.sender == owner);  // @audit What if owner is uninitialized?
    }
}
```

**Attack Flow**:
1. Attacker deploys malicious implementation
2. Calls upgradeTo(maliciousImpl)
3. No authorization check passes
4. Proxy now points to malicious code
5. Attacker