💹

JPYCv2のレスキュー機能の紹介

2022/10/03に公開

こんにちは。retocroomanです。

今回は、JPYCv2の新機能紹介シリーズで最後となるレスキュー機能の紹介です!コントラクトに間違ってトークンを送ってしまって取り出せない〜という事態を回避するための機能です。

この機能はまだ規格化されていないようでOpenZeppelinで類似のライブラリはなさそうですが、USDCに倣って簡略化したものをJPYCv2に実装しています。それではどうやってコントラクトに間違えて送られてきたトークンを取り出せるのか見ていきましょう!

前提知識

Solidity(前提知識集)

Rescuable.solの目的と概要

コントラクトに間違えて送られてきたERC20トークンを取り出すことが目的です。そもそも、トークンの送信機能がないコントラクトにトークンを送れてしまうことが根本的な問題です。

なぜ、コントラクトに間違えて送られてきたERC20トークンが取り出せないかを説明します。ERC20トークンを送る際に本人確認となるのがsolidityのmsg.senderです。EOAからの場合は、msg.senderを起点にどんなデータも送れます。しかし、コントラクトからの場合は、msg.senderを起点に、コントラクトに予め用意されているデータしか送れません。

この問題はERC20トークンが出来てからずっと指摘されているため、ERC20より後のERC721やERC1155などのNFTでは、トークンの送り先がコントラクトだった場合はちゃんと送信機能が付いているか試してからトークンを送っています。同様の機能を入れたERC20の拡張版としてERC777が提案されていますが、これに対応しているコントラクトはそもそも少なく、ERC20の採用が圧倒的に多数なのであまり導入されていません。結局のところ、コントラクトにトークンを取り出す機能をつけておくのが無難な解決方法です。他の手段として、ブロックリスト機能があるERC20トークンなら、送信機能のないコントラクトをブロックするのもありです。JPYCv2も自身のコントラクトアドレスはブロックリストに入れています。

レスキュー機能の具体的な仕組みは、Rescuerの権限を持つアカウントが、依頼を受けると間違って送ってしまったトークンを元の持ち主に送り返す、という単純なものです。トークンが間違えて送られてきてもコントラクト自体は検知できないため、そこをオンチェーンで自動化することは今のところ難しそうです。

Rescuable.solのコード解説

JPYCv2の全体像

解説するコードのリンク
Rescuable.sol(JPYCv2)
https://github.com/jcam1/JPYCv2/blob/main/contracts/v1/Rescuable.sol
継承しているコントラクトのコードのリンク
Ownable.sol(JPYCv2)
https://github.com/jcam1/JPYCv2/blob/main/contracts/v1/Ownable.sol
IERC20.sol(JPYCv2)
https://github.com/jcam1/JPYCv2/blob/main/contracts/util/IERC20.sol

Rescuableが継承しているコントラクトの解説

contract Resuable is Ownable {

JPYCv2のOwnableコントラクトはOpenZeppelinのOwanbleとほぼ同じコードですが少しだけ変更しています。変更点はバージョンが0.8.11であること、オーナー権を放棄する関数を削っていること、OpenZeppelinのContextライブラリを継承していないことです。このOwnableコントラクトで定義されているownerがRescuerの変更をすることができます。

Rescuableの状態変数と修飾子の解説

    address public rescuer;

    event RescuerChanged(address indexed newRescuer); // Rescuerが変更された時

    // msg.senderがRescuer以外ならエラーになる修飾子
    modifier onlyRescuer() {
        require(msg.sender == rescuer, "Rescuable: caller is not the rescuer");
        _;
    }

そしてコードの最後にはサイズ50のuint256型の配列が宣言されていますが、アップグレード時に状態変数を追加できるようにするためです。

    uint256[50] private __gap;

関数の解説

    // 自身のコントラクトを起点にトークンを指定のアドレスに送る
    // Rescuerのみが呼べる
    function rescueERC20(
        IERC20 tokenContract, // トークンアドレス
        address to, // 送信先
        uint256 amount // 送信量
    ) external onlyRescuer {
        tokenContract.transfer(to, amount); // トークンコントラクトを呼び出し
    }

    // Ownerのみが呼べてRescuerを変更する
    function updateRescuer(address newRescuer) external onlyOwner {
        require(
            newRescuer != address(0),
            "Rescuable: new rescuer is the zero address"
        );
        rescuer = newRescuer;
        emit RescuerChanged(newRescuer);
    }

ちなみに、ネイティブトークンに関してはpayable関数がそもそもないので受け取ることはできません。よってRescuableで気にする必要もありません。

まとめ

JPYCv2のレスキュー機能をみてきました。単純な機能ではあるものの、なしにした場合下手をすると何千万という単位で資産をロストしてしまうかもしれず付けておいて損はないと思います。実際、レスキュー機能のないコントラクトに送ってしまって困ったという例は本当によく聞きます。ただ、取り出すにもガス代やオペレーション費用はかかるので小額のケースでは対応しないのが一般的だと思います。

これでJPYCv2で実装された新機能はあらかた紹介できたと思います。ERC20はトークンの規格として最も人気でよく使われていますが、実際に有名なERC20トークンの中身を見てみるとそれぞれ違っていたりします。あくまで最低限のインターフェースと処理内容を定義しているだけなので、関数などは自由に追加してみても大丈夫なのです。ERC20トークンを発行する際はJPYCv2のコードを参考に色々カスタマイズして発行してみてはどうでしょうか?

ここまでお読みいただきありがとうございます。今後もweb3周りの技術を紹介していくのでお楽しみください!

日本初のブロックチェーン技術(ERC20)を活用した日本円ステーブルコインJPYCはこちらから購入できます!
JPYC社はブロックチェーンエンジニアを募集中です!こちらからご応募お願いします!(タイミングにより募集を行なっていない場合があります)
また、ラボ形式でブロックチェーンに関する講義をしているJPYC開発コミュニティにも是非ご参加ください!

Discussion