0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Foundry チュートリアルをやってみる (EIP-2612のテスト)

Last updated at Posted at 2023-11-02

こちらのfoundry チュートリアル(EIP-2612のテスト)をやってみた結果と気づきのメモです。
https://book.getfoundry.sh/tutorials/testing-eip712

// はじめの理解は不足していたので追記しました。
ERC-20ではapprove(spender, aomunt) で defiなどのcontractに対してtransferfrom(sender,recipient,amount)を直接ユーザが許可するのだけど、ERC2612では以下をユーザに渡して署名してもらいます。

  "message": {
    "owner": owner,
    "spender": spender,
    "value": value,
    "nonce": nonce,
    "deadline": deadline
  }

これの何がうれしいのかというと、この署名(v,r.s)があると、permitをcontract側ができるので、ユーザはapproveにかかるガス代がかからないそうです。

ERC-20のコイン作成 (Orecoin)

ERC-20も作った事なかったのですが、チュートリアルに無いのでERC20.solをみながら作成。
ERC20を定義しているライブラリはいくつかあるらしく、チュートリアルのとおりsolmateのERC20を使ってます。(ERC-2612用の定義があります)

./src/orecoin.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
import "lib/solmate/src/tokens/ERC20.sol";
contract Orecoin is ERC20 {

    constructor() ERC20("Orecoin","ORC", 100) {
    }
    function mint(address _to, uint256 _amount) public {
        _mint(_to, _amount);
    } 
}
  • ERC20constructorの第三引数は基準単位と最小単位の桁数。ether と weiの関係。wethの場合は18
  • ERC20abstractになっていて継承して使うようになってる。
  • ミントは_mint()internalになっているので、これだけpublic から呼べるように実装。他のERC20関数はERC20.solで実装済み。

ERC-20テストの作成

チュートリアルだとないけど、ERC-20の正常性テストのミントと転送だけやってみた。

./test/orecoin.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {Test, console2} from "forge-std/Test.sol";
import "../src/orecoin.sol";
import "../src/sigutil.sol";

contract OreCoinTest is Test {
    Orecoin public token;

    function setUp() public {
        token = new Orecoin();
    }

    // basic ERC20 test
    function test_mint_Increment() public {
        token.mint(address(1), 100);
        assertEq(token.balanceOf(address(1)), 100);
    }

    function test_transfer() public {
        token.mint(address(1), 100);
        vm.prank(address(1));
        token.transfer(address(2), 10);
        assertEq(token.balanceOf(address(2)), 10);
        assertEq(token.balanceOf(address(1)), 90);
   }

ERC-2612のテスト

まずオフチェーンサインをするデータ部作成とハッシュ作成のcontractを src/sigutil.sol にコピペ。これはfoundryのテストでユーザが署名する部分の関数です。foundryはすべてsolidiryで記述するのでこのようになります。

./src/sigutil.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract SigUtils {
    bytes32 internal DOMAIN_SEPARATOR;

    constructor(bytes32 _DOMAIN_SEPARATOR) {
        DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR;
    }

    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH =
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    struct Permit {
        address owner;
        address spender;
        uint256 value;
        uint256 nonce;
        uint256 deadline;
    }

    // computes the hash of a permit
    function getStructHash(Permit memory _permit)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    PERMIT_TYPEHASH,
                    _permit.owner,
                    _permit.spender,
                    _permit.value,
                    _permit.nonce,
                    _permit.deadline
                )
            );
    }

    // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer
    function getTypedDataHash(Permit memory _permit)
        public
        view
        returns (bytes32)
    {
        return
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR,
                    getStructHash(_permit)
                )
            );
    }
}

次に./test/orecoin.t.solを書き換え。 setUp()やglobal変数をチュートリアルの通りに変えてみた。

./test/orecoin.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {Test, console2} from "forge-std/Test.sol";
import "../src/orecoin.sol";
import "../src/sigutil.sol";


contract OreCoinTest is Test {
    Orecoin public token;
    SigUtils public sigUtils;

    uint256 internal ownerPrivateKey;
    uint256 internal spenderPrivateKey;

    address internal owner;
    address internal spender;


    function setUp() public {
        token = new Orecoin();
        sigUtils = new SigUtils(token.DOMAIN_SEPARATOR());

        ownerPrivateKey = 0xA11CE;
        spenderPrivateKey = 0xB0B;

        owner = vm.addr(ownerPrivateKey);
        spender = vm.addr(spenderPrivateKey);

        token.mint(owner, 1e18);
    }

次に正常性のテスト。permitTransferFromをまとめてみた。

// ERC2612 test 
    function test_Permit_and_transfer() public {

    //  permitのデータ構造をつくる。
        SigUtils.Permit memory permitData = SigUtils.Permit({
            owner: owner,
            spender: spender,
            value: 1e18,
            nonce: 0,
            deadline: 1 days
        });
 // ここでサービスプロバイダはユーザに上記のpermit情報を渡す
  
        bytes32 digest = sigUtils.getTypedDataHash(permitData);
  //  ユーザは自分の秘密鍵でpermit情報にサイン。これはオフチェーンでできる。   
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);

 // サービスプロバイダはpermitをownerの代わりに実行
        vm.prank(spender);
        token.permit(
            permitData.owner,
            permitData.spender,
            permitData.value,
            permitData.deadline,
            v,
            r,
            s
        );

        assertEq(token.allowance(owner, spender), 1e18);
        assertEq(token.nonces(owner), 1);

// サービスプロバイダがtransferFromを実行
        vm.prank(spender);
        token.transferFrom(owner, spender, 1e18);

        assertEq(token.balanceOf(owner), 0);
        assertEq(token.balanceOf(spender), 1e18);
        assertEq(token.allowance(owner, spender), 0);
    // トランザクションはtransferFromしても増えない
        assertEq(token.nonces(owner), 1);
    }
}
  • 自分の理解したところを日本語コメントいれてみた。勘違いがあるかもしれない。

  • チュートリアルではtoken.permitSigUtilpermitが紛らわしかったのでSigUtilで作る方をpermitDataとしました。

  • nonceってなにかわからなかったけど、そのユーザのトランザクションの累計なんですね。テストでははじめは0から始まるので、permitには +1された1 がはいる。トランザクションにこの値を入れることによってリプレイ攻撃を防ぐ。

あとはネガティブテストをチュートリアルのとおりにやってみた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?