Open1

コントラクトの再デプロイ

YuheiNakasakaYuheiNakasaka

コントラクトの再デプロイ

リソース

やること

  • コントラクトの状態を維持したまま再デプロイする方法について

メモ

  • 通常はコントラクトはimmutableなのでデプロイ後に変更ができない
  • しかしながらバグの修正など変更できた方が当たり前ながら便利
  • やることとしては
    • 新たなコントラクトのデプロイ
    • 全ての状態を古いコントラクトから新しいコントラクトにマイグレートする
    • 古いコントラクトとやりとりしているコントラクトのアドレスを新しいコントラクトのものに変更
    • 既存の利用者に新しいコントラクトを利用するよう調整する
  • これらの作業は煩雑で大変だがOpenZeppelinのプラグインでこれらを一括で行えるようになる
  • 必要なもの
    • yarn add @openzeppelin/contracts-upgradeable
    • yarn add -D @openzeppelin/hardhat-upgrades
  • 仕組み
    • 実装したコントラクト、ProxyAdminコントラクト、Proxyコントラクトの3つをデプロイする
    • Proxyコントラクトを表に出すインターフェースにする。状態を保持する。Proxyコントラクトは実装したコントラクトの機能を全て継承する。
    • コードのアップデート時はProxyAdminコントラクトがProxyコントラクトのコード部分のみ新しく実装したコントラクト(のアドレス)に差し替える。
    • こうすることで状態は維持されたまま実装だけ差し替え可能になる。
  • 制限

成果物

実際のコード例
//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";
    }
}