Open1
Proxy Pattern

Proxy Pattern
概要
- コントラクトにバグがあった時、修正できるようになる
- ユーザごとにコントラクトを用意するとき、デプロイのガス代を節約できる(あんま分かってない)
構成
User→Proxy→Impl1, Impl2...
- Proxyをいじれるのはアドミン権限がある人のみ。
誰でもいじれるようにすると、勝手に呼び出し先のImplを変更できてしまう。
問題点
- セレクタの衝突により、攻撃に利用される可能性があるらしい(全然分かってない)
→Proxyに設定されているオーナーアドレスを変更されるとまずい?
解決策
- アドミン以外からProxyが呼び出された場合は、必ずImplにデリゲートするようにする
- アドミンからProxyが呼び出された場合は、デリゲートしない
更に問題点
- アドミンからProxyを通過してImplを呼び出すことができない
TransParent Proxy
概要
- 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前の変数を参照できる