Skip to content

A workshop on how to implement the Transparent proxy pattern using Foundry and OZ Foundry Upgrade library

Notifications You must be signed in to change notification settings

chrisco512/proxy-patterns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Proxy Pattern

Uses Foundry and OpenZeppelin's Foundry Upgrades library to create a demonstration of the Transparent Proxy pattern.

This example was created with version 1.3.2-stable of Foundry and v5 of OpenZeppelin Contracts.

Setup

Step 1: Install Foundry if needed (https://getfoundry.sh):

From a terminal session:

curl -L https://foundry.paradigm.xyz | bash
foundryup

Step 2: Initialize the project:

forge init my-proxy-project
cd my-proxy-project

Step 3: Install OpenZeppelin as a dependency:

forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/openzeppelin-contracts-upgradeable

Step 4: Define remappings

Create a remappings.txt file in your project and add the following lines:

@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/

Step 5: Customize foundry settings for OpenZeppelin library

Open foundry.toml and change to the following:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]

Step 6: Compile to verify

forge build

Step 7: Run a local node

Open a new terminal tab or window and start a local node:

anvil

This runs a local blockchain at http://127.0.0.0.1:8545 with test accounts (use private key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 by default)

Customize Counter

Open the generated src/Counter.sol file. Change it to the following.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract Counter is Initializable {
    uint256 public number;

    function initialize(uint256 initialValue) public initializer {
        number = initialValue;
    }

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

This extends the Counter contract as Initializable and creates a one-time use initialize method that allows the Counter to be initialized with an arbitrary number when it's being deployed. Constructors cannot be used in implementation contracts when they sit behind a proxy.

Run forge build to ensure it compiles correctly.

Create Deployment Script

Create script/Deploy.s.sol.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {Counter} from "src/Counter.sol";

contract Deploy is Script {
    function run() external {
        vm.startBroadcast();

        address proxy = Upgrades.deployTransparentProxy(
            "Counter.sol",
            msg.sender,
            abi.encodeCall(Counter.initialize, (0))
        );

        console.log("Proxy address:", proxy);

        Counter counter = Counter(address(proxy));
        counter.increment();
        console.log("Count after: increment:", counter.number());

        vm.stopBroadcast();
    }
}

This script utilizes the OpenZeppelin Foundry Upgrades library to deploy the Counter.sol implementation contract, sets the admin as msg.sender, then calls the Counter.initialize method to set its initial number. It then logs the Proxy address and the modified count to the terminal (along with other transaction metadata).

Compile your project by running forge clean && forge build.

To run the script:

forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast

It will log out a lot of transaction metadata. Scroll up to the == Logs == section and observe the values as:

== Logs ==
  npm warn exec The following package was not found and will be installed: @openzeppelin/upgrades-core@1.44.1

  Proxy address: {the address your proxy was deployed to} 
  Count after: increment: 1

Upgrade the Counter Contract

Create src/CounterV2.sol and append a new function. Here we are adding multBy to multiply the count by some value.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract CounterV2 is Initializable {
    uint256 public number;

    function initialize(uint256 initialValue) public initializer {
        number = initialValue;
    }

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }

    function multBy(uint256 value) public {
        number *= value;
    }
}

Create script/Upgrade.s.sol.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.29;

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {Options} from "openzeppelin-foundry-upgrades/Options.sol";

import {CounterV2} from "src/CounterV2.sol";

contract Upgrade is Script {
    // Change to the Proxy address logged from Deploy.s.sol
    address constant PROXY_ADDRESS = 0xYourProxyAddressHere;

    function run() external {
        vm.startBroadcast();

        Options memory opts;
        opts.referenceContract = "Counter.sol";
        Upgrades.upgradeProxy(PROXY_ADDRESS, "CounterV2.sol", "", opts);

        // Test
        CounterV2 counter = CounterV2(PROXY_ADDRESS);
        console.log("Current count:", counter.number());

        counter.multBy(3);
        counter.multBy(3);

        console.log("Counter after multiplying by 3 twice:", counter.number());

        vm.stopBroadcast();
    }
}

Run forge clean && forge build, then:

forge script script/Upgrade.s.sol --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast

This upgrades the implementation, keeping the proxy address and state.

About

A workshop on how to implement the Transparent proxy pattern using Foundry and OZ Foundry Upgrade library

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published