foundry

hardhatからfoundryの移行について

今回は、hardhatからfoundryの移行について、完全移行ではなくhardhatもfoundryもどちらも共存できる方法を実践していきたいと思います。

Githubで共有してます https://github.com/eggdragons/hardhatToFoundry

ダミープロジェクトの準備

随分前に移行してしまったので、適当なhardhatのプロジェクトがなかったので、hardhatのインストールから始めてます。

ここは読み飛ばしてもらえればと思います。

npm init
npm install --save-dev hardhat

npx hardhat
npx hardhat test

foundryへ移行

早速foundryの導入をしていきます。

インストール関係については、各自済ませておいてくださいね

まず、foundryの初期化をします。移行したいHardhatプロジェクトのフォルダで書きコマンドを実行してください。

forge init --force

するとエラーが出ると思うので、次のコマンドを実行してください。

forge install foundry-rs/forge-std --no-commit

次に、リマッピングの設定を行なっていきます。

ルートフォルダにremappings.txtを作る必要があるのですが、このときforgeコマンドで作ると後々エラーが出るので、forgeコマンド使わずに作成ください!

その後、下記コマンドを実行すると、

forge remappings

リマッピング情報がコンソールに表示されると思います。

ds-test/=lib/forge-std/lib/ds-test/src/
eth-gas-reporter/=node_modules/eth-gas-reporter/
forge-std/=lib/forge-std/src/
hardhat/=node_modules/hardhat/

//下は好みで追記してください!
contracts/=contracts/

これをそのままコピーして、remappings.txtに記入してください。

forge remappings->remappings.txtするとエラーが出て動きません!!

ここで、一度テストを実行してみましょう!

forge test
npx hardhat test

どちらもテストが実行されると思います!

hardhatとfoundryをちゃんと分ける

以降は、好みの問題もありますが、後々のエラーを避けるためにも実施することをお勧めします!

なお、foundryに完全移行したい人は、hardhat側の設定をいじる方がお勧めです。

今回は、foundryをお試しに使ってみたい人向けに、foundry側をhardhatに寄せてます

現状、testフォルダとcacheフォルダがhardhatとfoundryの共用フォルダになっています。

そこで、下記のように分けたいと思います。

hardhatfoundry
testtestfoundry-test
cachecachefoundry-cache

ルートディレクトリにfoundry-testフォルダとfoundry-cacheを作成し、foundry-testフォルダにfoundryのテストを保存します。

次にfoundry.tomlを書き替えましょう!

[profile.default]
src = 'src'
out = 'artifacts'
libs = ["node_modules", "lib"]
test = 'foundry-test'
cache_path  = 'foundry-cache'

# See more config options <https://github.com/foundry-rs/foundry/tree/master/config>

実際にテストを実行

forge test
npx hardhat test

これで、hardhatとfoundryの共存が簡単にできました!

おまけ

せっかくなので、hardhatのテストLock.tsを雑にfoundry用に書き替えてテストしてみましょう!

foundry-test/Lock.t.solに下記コードを追記して実行してみてください。

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "forge-std/Test.sol";
import "contracts/Lock.sol";

contract LockTest is Test {
    Lock public lock;
    Lock public lock2;

    uint256 public constant ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
    uint256 public ONE_GWEI = 1_000_000_000;

    uint256 public lockedAmount = ONE_GWEI;
    uint256 public unlockTime = block.timestamp + ONE_YEAR_IN_SECS;

    address owner = vm.addr(1);
    address another = vm.addr(2);

    event Withdrawal(uint256 amount, uint256 when);

    //Deployment
    function setUp() public {
        hoax(owner, 1 ether);
        lock = new Lock{ value: lockedAmount }(unlockTime);
        // console.log("Called setUp");
    }

    //Should set the right unlockTime
    function testUnlockTime() public {
        assertEq(lock.unlockTime(), unlockTime);
    }

    //Should set the right owner
    function testRightOwner() public {
        assertEq(lock.owner(), owner);
    }

    //Should receive and store the funds to lock
    function testStoreFunds() public {
        assertEq(address(lock).balance, lockedAmount);
    }

    //Should fail if the unlockTime is not in the future
    function testFailDeployNotFutureUnlockTime() public {
        lock = new Lock{ value: lockedAmount }(1);
    }

    function testDeployNotFutureUnlockTime() public {
        vm.expectRevert();
        lock = new Lock{ value: lockedAmount }(1);
    }

    //Should revert with the right error if called too soon
    function testNotWithdraws() public {
        vm.expectRevert(bytes("You can't withdraw yet"));
        lock.withdraw();
    }

    //Should revert with the right error if called from another account
    function testNotHandleAnotherAccount() public {
        vm.startPrank(another);
        vm.expectRevert(bytes("You aren't the owner"));

        //increase the time
        skip(unlockTime);
        lock.withdraw();

        vm.stopPrank();
    }

    //Shouldn't fail if the unlockTime has arrived and the owner calls it
    function testFailWithdrawsAtUnlockTime() public {
        vm.expectRevert();
        vm.prank(owner);
        skip(unlockTime);
        lock.withdraw();
    }

    //Should emit an event on withdrawals
    function testEmitWithdrawal() public {
        vm.startPrank(another);
        vm.deal(another, 1 ether);
        lock2 = new Lock{ value: lockedAmount }(unlockTime);

        skip(unlockTime);

        //Lock.sol event Withdrawal(uint amount, uint when);
        vm.expectEmit(false, false, false, true);
        emit Withdrawal(address(lock2).balance, block.timestamp);
        lock2.withdraw();

        vm.stopPrank();
    }

    //Should transfer the funds to the owner
    function testOwnerWithdraws() public {
        skip(unlockTime);

        vm.startPrank(owner);

        lock.withdraw();
        assertEq(address(lock).balance, 0);
        assertEq(address(owner).balance, 1 ether);

        vm.stopPrank();
    }
}

下記コマンドを実行することで、上記テストのみ実行できます。

forge test --match-contract Lock

問題なく動けば、テスト移行完了です!

お疲れ様でした!

基本的な使い方が学べるので、ぜひ一度自分で、hardhatのLockテストをfoundry用に書き換えてみてくださいね!

-foundry