Skip to content

Commit 9d757b7

Browse files
committed
store as a draft
1 parent 5129fa3 commit 9d757b7

File tree

1 file changed

+103
-4
lines changed

1 file changed

+103
-4
lines changed

why_upgrade_possible.md

+103-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,120 @@
1-
# はじめに
1+
<!-- # はじめに
22
33
Upgradeableなコントラクト、スマートコントラクトを書き始めた人なら一度は聞いたことのある用語だと思います。Upgradeableなコントラクトについては、OpenZeppelinのLibraryも充実しています。そのため、すでに多くの人がUpgradeableなコントラクトを書いたことがあるかもしれません。
44
5-
今回のブログでは、なぜコントラクトがUpgradeable可能になるかについて書いていきます。 DELEGATECALL、Proxy patternを理解している人には退屈な内容だと思います。それでは見ていきます
5+
今回のブログでは、なぜコントラクトがUpgradeable可能になるかについて書いていきます。 delegatecall, proxy patternを理解している人には退屈な内容になると思います
66
7-
# なぜUpgradeableなコントラクトが必要とされるのか?
7+
8+
# Upgradeableなコントラクトの必要性
89
910
スマートコントラクトはアプリケーション開発において、バックエンドを担うとされます。しかし、本来のバックエンド開発と大きな違いがあります。それは、一度デプロイされたスマートコントラクトは変更ができない点です。このことはアプリケーション開発を難しくします。デプロイ後、変更のできないスマートコントラクトでは、バグの修正、新規機能追加などができません。その解決策とされるのが、Upgradeableなコントラクトです。
1011
12+
1113
# なぜUpgradeableなコントラクトが可能になるのか?
1214
13-
スマートコントラクトは、デプロイ後変更できない。それではなぜUpgradeableなコントラクトは可能なのでしょうか?Upgradeableなコントラクトが可能になる背景には、DelegatecallとProxy patternがあります。
15+
スマートコントラクトは、デプロイ後変更できない。それではなぜUpgradeableなコントラクトが可能になるのでしょうか?Upgradeableなコントラクトが可能になる背景には、DelegatecallとProxy patternがあります。
1416
1517
## Delegatecall
1618
19+
delegatecallはLow-level functionで、あるコントラクトから別のコントラクトを呼ぶ際に使われます。delegatecallには、二つの特徴があります。コントラクトAからコントラクトBを呼び出すというケースを想定します。一つ目の特徴は、コントラクトBのコードはロジックとして利用されます。そして、コントラクトAのstate variablesを更新します。二つ目の特徴は、msg.senderがコントラクトAを呼び出したアドレスになるということです。(delegatecallを使わずコントラクトAからコントラクトBを呼ぶ場合。この時、コントラクトBで実行されるコードは、コントラクトBのState variablesを参照し更新します。msg.senderはコントラクトAなります。)
20+
21+
文章だけでは理解するのが大変だと思います。具体例をみていきます。以下のコードは、[Solidity by Example](https://solidity-by-example.org/delegatecall)からの引用です。
22+
23+
```
24+
// SPDX-License-Identifier: MIT
25+
pragma solidity ^0.8.13;
26+
27+
// NOTE: Deploy this contract first
28+
contract B {
29+
// NOTE: storage layout must be the same as contract A
30+
uint public num;
31+
address public sender;
32+
uint public value;
33+
34+
function setVars(uint _num) public payable {
35+
num = _num;
36+
sender = msg.sender;
37+
value = msg.value;
38+
}
39+
}
40+
41+
contract A {
42+
uint public num;
43+
address public sender;
44+
uint public value;
45+
46+
function setVars(address _contract, uint _num) public payable {
47+
// A's storage is set, B is not modified.
48+
(bool success, bytes memory data) = _contract.delegatecall(
49+
abi.encodeWithSignature("setVars(uint256)", _num)
50+
);
51+
}
52+
}
53+
```
54+
55+
コントラクトAのsetVarsという関数から、コントラクトBのsetVarsという関数を呼び出しています。コントラクトAのsetVarsがパラメーターとして、コントラクトBのアドレスを取ります。そして、_contract.delegatecall()という部分でdelegatecallが使われて、コントラクトBのsetVars関数が呼ばれています。コントラクトBのsetVars関数の中で、num、sender、valueというstate variablesが更新されます。実際に更新されるstate variablesは、コントラクトAのstate variablesになります。senderにはコントラクトAのsetVars関数を呼び出したアドレスが代入され、valueはコントラクトAが呼び出された際に、送金されたEtherになります。このことは、以下のテストコードでも確認することができます。興味がある方は、テストコードをhardhatで実行してみてください。
56+
57+
```
58+
import { ethers } from 'hardhat'
59+
import { expect } from 'chai'
60+
import { Contract, constants } from 'ethers'
61+
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
62+
63+
describe("Delegatecall", function () {
64+
let constractA: Contract
65+
let contractB: Contract
66+
let user1: SignerWithAddress
67+
68+
before(async () => {
69+
[user1] = await ethers.getSigners()
70+
contractB = await (await ethers.getContractFactory("B")).deploy()
71+
constractA = await (await ethers.getContractFactory("A")).deploy()
72+
})
73+
74+
it("prove of the delegatecall", async () => {
75+
expect(await constractA.num()).to.equal(0)
76+
expect(await constractA.sender()).to.equal(constants.AddressZero)
77+
expect(await constractA.value()).to.equal(0)
78+
79+
const options = {value: ethers.utils.parseEther("1.0")}
80+
81+
await constractA.connect(user1).setVars(contractB.address, 7, options)
1782
83+
expect(await constractA.num()).to.equal(7)
84+
expect(await constractA.sender()).to.equal(user1.address)
85+
expect(await constractA.value()).to.equal(options.value)
86+
})
87+
});
88+
```
89+
90+
以上がdelegatecallの説明になります。まとめると、
91+
92+
①コントラクトBはロジックを実行するコードとなり、コントラクトA(呼び出し元)のstate variablesが更新されること
93+
②コントラクトBのmsg.senderはコントラクトAではなく、コントラクトAを呼び出したアドレスになること
94+
95+
の二点が重要です。
1896
1997
## Proxy pattern
2098
99+
Proxy patternでは、ロジックとなるコントラクトのアドレスをstate variableとして保存します。さらに呼び出し元(コントラクトA)では、fallback functionを使用します。このfallback functionの中でdelegatecallが使われ、保存したアドレスをロジックコントラクトとして利用することを可能にします。
100+
101+
このロジックコントラクト自体は、他のコントラクトと同様、変更できないコントラクトです。コントラクトをUpgradeableにするのは、先ほど保存したロジックコントラクトのアドレスを更新します(例えば、コントラクトBアドレスから、コントラクトCのアドレスに変更する)これにより、delegatecallの対象はコントラクトCになります。delegatecallの特徴から、今までコントラクトBのロジックを利用して更新されてきたコントラクトAのstate variablesは維持されることになります。
102+
103+
# 最後に
104+
105+
今回のブログでは、なぜUpgradeableなコントラクトが可能になるのかについてまとめました。デプロイ後、変更できないはずのスマートコントラクトが、なぜUpgradeableになるのか理解できたと思います。次回のブログでは、Upgradeableなコントラクトを作成する際の注意点について書きたいと思います。最後まで読んでいただき、ありがとうございました。
106+
107+
# 参考資料
108+
109+
Solidity<br />
110+
https://docs.soliditylang.org/en/v0.8.15/index.html
111+
112+
The State of Smart Contract Upgrades <br />
113+
https://blog.openzeppelin.com/the-state-of-smart-contract-upgrades/#upgrade-patterns
114+
115+
Proxy Upgrade Pattern <br />
116+
https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies -->
117+
118+
119+
21120

0 commit comments

Comments
 (0)