見出し画像

DN404(ERC404)のテスト

概要

前回の記事でDN404のローカル環境の構築と簡単な動作テストをしたので今回はテストコードを実装してDN404の動作確認を行う。
テストは独自クラスではなくインストールされた際に含まれている「MockDN404.sol」を使用して確認を行う。

手順

(1) SoladyTestをベースにテストクラスを作成する

前回DN404のインストールを行った際「SoladyTest」も含めてダウンロードできているはずなので、継承してテストクラスを作成する。

登場パラメータ:
dn: ERC20トークンクラス(以下、「トークン」)
mirror: ERC721クラス(以下、「NFT」)
alice: ユーザー1
bob: ユーザー2

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./utils/SoladyTest.sol";
import {DN404, MockDN404} from "./utils/mocks/MockDN404.sol";
import {DN404Mirror} from "../src/DN404Mirror.sol";


contract MNFTDN404Test is SoladyTest {
    MockDN404 dn;
    DN404Mirror mirror;

    uint256 private constant _WAD = 1000000000000000000;
    uint256 initialSupply = 1000;
    address alice = address(111);
    address bob = address(222);

    function setUp() public {
        dn = new MockDN404();
        mirror = new DN404Mirror(address(this));
    }
:
:
}

(2) nameとsymbolのテスト
試しにnameとsymbolのテストコードを作成。

    function testNameAndSymbol(string memory name, string memory symbol) public {
        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.setNameAndSymbol(name, symbol);

        assertEq(mirror.name(), name);
        assertEq(mirror.symbol(), symbol);
    }

実行結果↓↓

% forge test --match-path test/MNFTDN404.t.sol -vvv
[] Compiling...
[] Compiling 1 files with 0.8.24
[] Solc 0.8.24 finished in 463.95ms
Compiler run successful!

Ran 2 tests for test/MNFTDN404.t.sol:MNFTDN404Test
[PASS] testNameAndSymbol(string,string) (runs: 256, μ: 207165, ~: 207775)
[PASS] test__codesize() (gas: 21503)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 29.07ms (28.51ms CPU time)

Ran 1 test suite in 136.02ms (29.07ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

(2)DN404のテストコード

ファンクション:testMint
DN404が思った通りに動作しているかどうか、鋳造や移動を行ってERC20とERC721の数を確認する。

最大1000トークンのDN404を初期化。
4トークン分aliceに対して生成。
NFTのTokenID1、2がaliceにも生成されていることを確認(実際は4つNFTを持っている)。
bobのトークン数は0。
↓↓

        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.mint(alice, 4 * _WAD);
        //nftは4つ
        assertEq(mirror.balanceOf(alice), 4);
        assertEq(mirror.ownerOf(1), alice);
        assertEq(mirror.ownerOf(2), alice);

        //bobのerc20は0
        assertEq(dn.balanceOf(bob), 0);

aliceがbobにTokenID:2のNFTを移動。
bobがTokenID:2のNFTを所持していることを確認。
aliceのトークン数が3になってるはず。
bobのトークン数が1になっているはず。
↓↓

        //id:2をbobに移動
        vm.prank(alice);
        mirror.transferFrom(alice, bob, 2);
        assertEq(mirror.ownerOf(2), bob);

        //aliceのerc20が3になってるはず
        assertEq(dn.balanceOf(alice), 3 * _WAD);
        //bobのerc20が1になってるはず
        assertEq(dn.balanceOf(bob), 1 * _WAD);

bobからaliceに0.5トークン移動。
bobのトークンは0.5になっているはず。
bobのNFTは0になっているはず。(TokenID:2のNFTがバーンされた)
aliceのトークン数は3.5になっているはず。
aliceのNFTは3のまま。
↓↓

        vm.prank(bob);
        //bobから資金移動
        dn.transfer(alice, _WAD/2);

        //bobのerc20半分
        assertEq(dn.balanceOf(bob), _WAD/2);
        //bobのnft0
        assertEq(mirror.balanceOf(bob), 0);

        //aliceのerc20 = 3.5 * _WAD
        assertEq(dn.balanceOf(alice), (3 * _WAD)+(_WAD/2));
        //aliceのnft 3
        assertEq(mirror.balanceOf(alice), 3);

aliceからbobに0.5トークンを再度移動。
bobのNFTが1になる。
bobが所持するNFTのTokenIDは5。
↓↓

        //aliceからbobに0.5おくる。
        vm.prank(alice);
        dn.transfer(bob, _WAD/2);
        //bobのnft = 1になる
        assertEq(mirror.balanceOf(bob), 1);
        //idは4まで。id:2がburnされ、新しいidが生成される。id:5になる。
        assertEq(mirror.ownerOf(5), bob);

TokenID:2のNFTは存在しないことを確認。
↓↓

        //id2はbobでは無くなってるはず
        vm.expectRevert(DN404.TokenDoesNotExist.selector);
        mirror.ownerOf(2);

ファンクション:testBurn
トークンの焼却に関してテスト。
トークン焼却できるが、NFTに焼却機能はついていなかった。
自分で実装する必要がある。

aliceの初期保有トークン数を4として初期化。
↓↓

        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.mint(alice, 4 * _WAD);

        //nftは4つ
        assertEq(mirror.balanceOf(alice), 4);

aliceのトークンを1つ焼却。
aliceの所持トークン数が3であること。
aliceの所持NFT数が3であること。
↓↓

        //erc20 1バーン
        dn.burn(alice, 1 * _WAD);

        //ERC20: 3*_WAD, NFT: 3
        assertEq(dn.balanceOf(alice), 3 * _WAD);
        assertEq(mirror.balanceOf(alice), 3);

テストコード全体
下記にテスト全体を記載。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./utils/SoladyTest.sol";
import {DN404, MockDN404} from "./utils/mocks/MockDN404.sol";
import {DN404Mirror} from "../src/DN404Mirror.sol";


contract MNFTDN404Test is SoladyTest {
    MockDN404 dn;
    DN404Mirror mirror;

    uint256 private constant _WAD = 1000000000000000000;
    uint256 initialSupply = 1000;
    address alice = address(111);
    address bob = address(222);

    function setUp() public {
        dn = new MockDN404();
        mirror = new DN404Mirror(address(this));
    }

    function testNameAndSymbol(string memory name, string memory symbol) public {
        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.setNameAndSymbol(name, symbol);
        assertEq(mirror.name(), name);
        assertEq(mirror.symbol(), symbol);
    }

    function testTokenURI(string memory baseURI, uint256 id) public {
        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.setBaseURI(baseURI);
        assertEq(mirror.tokenURI(id), string(abi.encodePacked(baseURI, id)));
    }

    function testMint() public {
        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.mint(alice, 4 * _WAD);
        //nftは4つ
        assertEq(mirror.balanceOf(alice), 4);
        assertEq(mirror.ownerOf(1), alice);
        assertEq(mirror.ownerOf(2), alice);
        
        //bobのerc20は0
        assertEq(dn.balanceOf(bob), 0);

        //id:2をbobに移動
        vm.prank(alice);
        mirror.transferFrom(alice, bob, 2);
        assertEq(mirror.ownerOf(2), bob);

        //aliceのerc20が3になってるはず
        assertEq(dn.balanceOf(alice), 3 * _WAD);
        //bobのerc20が1になってるはず
        assertEq(dn.balanceOf(bob), 1 * _WAD);

        vm.prank(bob);
        //bobから資金移動
        dn.transfer(alice, _WAD/2);

        //bobのerc20半分
        assertEq(dn.balanceOf(bob), _WAD/2);
        //bobのnft0
        assertEq(mirror.balanceOf(bob), 0);

        //aliceのerc20 = 3.5 * _WAD
        assertEq(dn.balanceOf(alice), (3 * _WAD)+(_WAD/2));
        //aliceのnft 3
        assertEq(mirror.balanceOf(alice), 3);

        //aliceからbobに0.5おくる。
        vm.prank(alice);
        dn.transfer(bob, _WAD/2);
        //bobのnft = 1になる
        assertEq(mirror.balanceOf(bob), 1);
        //idは4まで。id:2がburnされ、新しいidが生成される。id:5になる。
        assertEq(mirror.ownerOf(5), bob);

        //id2はbobでは無くなってるはず
        vm.expectRevert(DN404.TokenDoesNotExist.selector);
        mirror.ownerOf(2);
    }

    function testBurn() public {
        dn.initializeDN404(1000 * _WAD, address(this), address(mirror));
        dn.mint(alice, 4 * _WAD);

        //nftは4つ
        assertEq(mirror.balanceOf(alice), 4);

        //erc20 1バーン
        dn.burn(alice, 1 * _WAD);

        //ERC20: 3*_WAD, NFT: 3
        assertEq(dn.balanceOf(alice), 3 * _WAD);
        assertEq(mirror.balanceOf(alice), 3);
    }
}

結論

トークンやNFTの移動に関しては想定通り動作している。
想定外だったのがNFTの焼却ができないこと。
トークンの焼却で対処するしかないが、結論としては同じ事なので特段問題はない。

  1. NFTが生成できる分トークン移動すればNFTも移動される(今回の場合1:1)。

  2. NFTが生成できない程度のトークン移動ではNFTは焼却される(今回の場合0.5のみ移動してみた)。

  3. (2)の後生成されるNFTのTokenIDは新しくなる。

  4. トークン焼却はできるがNFT焼却はできない(自分で実装する)。

残念なことに、複数のERC721が登録できない
例えば一定数NFTミントしたら次から新しいNFTをミントする、と言うことができない。
複雑さの増加やガス代が高くなるため回避していると思われる。
その為、キャラクター毎のバージョン販売等といったNFTプロジェクトにDN404を使用することは不可能。

ただし、キャラクターNFTとの交換チケットとして作成することは可能

  1. ユーザーがDN404NFTを発行元に送信

  2. DN404NFTを受領発行元はユーザーが指定したキャラクターNFTを送信

  3. 発行元はNFTと同価値のトークンを受領

もしくは

  1. ユーザーが一定量のDN404トークンを発行元に送金

  2. DN404トークンを受領した発行元はユーザーが指定したキャラクターNFTを送信

  3. 発行元は同価値のNFTを受領

推奨としては前者。
トークン数をチェックする必要がない。
次回はチケット交換コントラクトの実装を行う。

この記事が気に入ったらサポートをしてみませんか?