今回は、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の共用フォルダになっています。
そこで、下記のように分けたいと思います。
hardhat | foundry | |
test | test | foundry-test |
cache | cache | foundry-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用に書き換えてみてくださいね!