The most common design pattern for upgradable contracts is the Proxy Pattern. This involves separating the contract's data (storage) and logic (code).

Normal contract (Storage)

Screenshot 2025-01-03 at 4.47.19 PM.png

Proxy contract

Screenshot 2025-01-03 at 6.57.36 PM.png

Basic example

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract StorageProxy {
    uint public num;
    address implementation;

    constructor(address _implementation) {
        num = 0;
        implementation = _implementation;
    }

    function setNum(uint _num) public {
        (bool success, ) = implementation.delegatecall(
            abi.encodeWithSignature("setNum(uint256)", _num)
        );
        require(success, "Error while delegating call");
    }

    function setImplementation(address _implementation) public {
        implementation = _implementation;
    }

}

contract Implementationv1 {
    uint public num;

    function setNum(uint _num) public {
        num = _num;
    }
}

contract Implementationv2 {
    uint public num;

    function setNum(uint _num) public {
        num = _num * 2;
    }
}

contract Implementationv3 {
    uint public num;

    function setNum(uint _num) public {
        num = _num * 3;
    }
}