Open1

Proxy Pattern

ぽけなぽけな

Proxy Pattern

概要

  • コントラクトにバグがあった時、修正できるようになる
  • ユーザごとにコントラクトを用意するとき、デプロイのガス代を節約できる(あんま分かってない)

構成

User→Proxy→Impl1, Impl2...

  • Proxyをいじれるのはアドミン権限がある人のみ。
    誰でもいじれるようにすると、勝手に呼び出し先のImplを変更できてしまう。

問題点

  • セレクタの衝突により、攻撃に利用される可能性があるらしい(全然分かってない)
    →Proxyに設定されているオーナーアドレスを変更されるとまずい?

解決策

  • アドミン以外からProxyが呼び出された場合は、必ずImplにデリゲートするようにする
  • アドミンからProxyが呼び出された場合は、デリゲートしない

更に問題点

  • アドミンからProxyを通過してImplを呼び出すことができない

TransParent Proxy

https://blog.shinonome.io/transparent-proxy-pattern-contracts/

概要

  • Proxy Patternの問題点をある程度解決できる?

構成

User→ProxyAdmin→Proxy→Impl1, Impl2...

  • Userではなく、ProxyAdminがProxyのデリゲート先を変更できるようにする
    当然、ProxyAdminはアドミンしか呼び出せない
  • ProxyAdminは1個デプロイすればOK

実装方法

  • openzeppelinで機能が提供されている

インストール

npm install --save-dev @openzeppelin/hardhat-upgrades @openzeppelin/contracts-upgradeable 

準備

  • hardhat.config.js
require("@openzeppelin/hardhat-upgrades");
  • Solidity
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
  • deploy.js, test.jsなど
const { upgrades } = require("hardhat");

実践

実装コントラクトImpl

setValue()の挙動が異なる。

  • Impl1
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract Impl1 is Initializable {
    uint256 public value;

    function initialize(uint256 _val) public initializer {
        value = _val;
    }

    function setValue(uint256 _val) public {
        value = _val;
    }
}
  • Impl2
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract Impl2 is Initializable {
    uint256 public value;

    function initialize(uint256 _val) public initializer {
        value = _val;
    }

    function setValue(uint256 _val) public {
        value = _val * 2;
    }
}

Proxy, ProxyAdmin

openzeppelinが作ってくれるので不要

テスト

テストコード

const { ethers, upgrades } = require("hardhat");

describe("Proxy", function () {
  it("Should return the new greeting once it's changed", async function () {
    const Impl = await ethers.getContractFactory("Impl1");
    const impl = await upgrades.deployProxy(Impl, [100], {
      initializer: "initialize",
    });
    console.log("Deploying...: ", impl.address);
    await impl.deployed();
    console.log("Impl1 deployed to:", impl.address);

    let result = await impl.value();
    console.log(result);

    await impl.setValue(200);
    result = await impl.value();
    console.log(result);

    const Impl2 = await ethers.getContractFactory("Impl2");

    const upgraded = await upgrades.upgradeProxy(impl.address, Impl2);
    console.log("Upgraded deployed to: ", upgraded.address);

    result = await upgraded.value();
    console.log(result);

    await upgraded.setValue(300);
    result = await upgraded.value();
    console.log(result);
  });
});

出力

Deploying...:  0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Impl1 deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
BigNumber { value: "100" }
BigNumber { value: "200" }
Upgraded deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
BigNumber { value: "200" }
BigNumber { value: "600" }
    ✓ Should return the new g

まとめ

  • Upgrade後も同じコントラクトアドレスでアクセスできる
  • Upgrade後からUpgrade前の変数を参照できる