Open1
コントラクトの再デプロイ
コントラクトの再デプロイ
リソース
- Upgrading smart contracts - OpenZeppelin Docs
- OpenZeppelin/openzeppelin-contracts-upgradeable: Upgradeable variant of OpenZeppelin Contracts, meant for use in upgradeable contracts.
やること
- コントラクトの状態を維持したまま再デプロイする方法について
メモ
- 通常はコントラクトはimmutableなのでデプロイ後に変更ができない
- しかしながらバグの修正など変更できた方が当たり前ながら便利
- やることとしては
- 新たなコントラクトのデプロイ
- 全ての状態を古いコントラクトから新しいコントラクトにマイグレートする
- 古いコントラクトとやりとりしているコントラクトのアドレスを新しいコントラクトのものに変更
- 既存の利用者に新しいコントラクトを利用するよう調整する
- これらの作業は煩雑で大変だがOpenZeppelinのプラグインでこれらを一括で行えるようになる
- 必要なもの
yarn add @openzeppelin/contracts-upgradeable
yarn add -D @openzeppelin/hardhat-upgrades
- 仕組み
- 実装したコントラクト、ProxyAdminコントラクト、Proxyコントラクトの3つをデプロイする
- Proxyコントラクトを表に出すインターフェースにする。状態を保持する。Proxyコントラクトは実装したコントラクトの機能を全て継承する。
- コードのアップデート時はProxyAdminコントラクトがProxyコントラクトのコード部分のみ新しく実装したコントラクト(のアドレス)に差し替える。
- こうすることで状態は維持されたまま実装だけ差し替え可能になる。
- 制限
- constructorが使えない。なのでOpenZeppelinが提供しているInitializableを使う。
-
initialize
はただの関数なので通常のconstructが自動でやってくれてる分を自前で面倒を見ないといけない。- 例えば下記
-
initialize
はただ一回のみ呼ばれるようにする - 継承元に
initialize
があればそれを子で呼ばないといけない - fieldメンバで直接値の定義をせず
initialize
内で行う -
ERC20
moduleは使えないのでERC20Upgradable
というmoduleを使う - 攻撃を避けるためにconstruct関数自体は実装しておく
-
@custom:oz-upgrades-unsafe-allow constructor
を使うにはSolidityは0.8.3
以上でないと使えない
-
- コントラクトの状態の順番の変更はできない。ここ制限が特に多くてバギーなのでDocsをよく読んだ方がいい。
-
- 詳しくはこのDocsを読むと良い。
- 例えば下記
-
- constructorが使えない。なのでOpenZeppelinが提供しているInitializableを使う。
成果物
実際のコード例
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract MyNFT is
Initializable,
ContextUpgradeable,
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
OwnableUpgradeable
{
using SafeMath for uint256;
string private _baseTokenURI;
uint256 public constant MAX_ELEMENTS = 3;
uint256 public constant price = 1000000000000000000; //1 ETH / 1 MATIC
function initialize(
string memory name,
string memory symbol,
string memory baseTokenURI
) public initializer {
__Context_init();
__Ownable_init();
__ERC721_init(name, symbol);
__ERC721Enumerable_init();
_baseTokenURI = baseTokenURI;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
)
internal
virtual
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
{
super._beforeTokenTransfer(from, to, tokenId);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function buy() public payable {
require(
totalSupply() < MAX_ELEMENTS,
"Purchase would exceed max supply of NFTs"
);
require(price <= msg.value, "Ether value sent is not correct");
uint256 mintIndex = totalSupply();
_safeMint(msg.sender, mintIndex);
}
function withdraw() public onlyOwner {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
}
function ownedTokens() public view returns (uint256[] memory) {
uint256 balance = balanceOf(msg.sender);
if (balance == 0) {
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](balance);
uint256 mintedCount = totalSupply();
uint256 resultIndex = 0;
uint256 tokenId = 0;
while (tokenId < mintedCount) {
if (ownerOf(tokenId) == msg.sender) {
result[resultIndex] = tokenId;
resultIndex = resultIndex.add(1);
}
tokenId = tokenId.add(1);
}
return result;
}
}
function nyan() public view virtual returns (string memory) {
return "nyao nyao nyan";
}
}