# Foundry Forge बनाम Hardhat Testing — एक व्यावहारिक तुलना
Smart contract testing frameworks का choice आपकी development velocity और code quality को significantly impact करता है। Hardhat लंबे समय से industry standard रहा है, लेकिन Foundry Forge एक game-changing alternative है। इस guide में हम practical examples के साथ दोनों को compare करेंगे।
Quick Overview
| Feature | Hardhat | Foundry Forge |
|---------|---------|---------------|
| Language | JavaScript/TypeScript | Solidity |
| Speed | Moderate | Fast (~10-100x) |
| Fuzzing | Limited (via plugins) | Built-in |
| Gas Reports | ✅ Good | ✅ Excellent |
| Fork Testing | ✅ Good | ✅ Excellent |
| Debugging | ✅ console.log | ✅ vm.* cheats |
| Learning Curve | Easy | Moderate |
Test Syntax Comparison
Simple Balance Test
Hardhat (JavaScript/Chai):
// test/Token.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Token", function () {
let token;
let owner;
let addr1;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
token = await Token.deploy(1000);
await token.deployed();
});
it("Should assign total supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(ownerBalance).to.equal(1000);
});
it("Should transfer tokens", async function () {
await token.transfer(addr1.address, 50);
expect(await token.balanceOf(addr1.address)).to.equal(50);
expect(await token.balanceOf(owner.address)).to.equal(950);
});
});
Foundry Forge (Solidity):
// test/Token.t.sol
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token token;
address owner = address(this);
address addr1 = address(0x1);
function setUp() public {
token = new Token(1000);
}
function testTotalSupplyToOwner() public {
assertEq(token.balanceOf(owner), 1000);
}
function testTransfer() public {
token.transfer(addr1, 50);
assertEq(token.balanceOf(addr1), 50);
assertEq(token.balanceOf(owner), 950);
}
}
Observations:
- Foundry में same language (Solidity) use होती है
- Hardhat में async/await boilerplate
- Foundry syntax cleaner और concise
Speed Comparison
Benchmark: Uniswap V2 Tests
# Hardhat
npx hardhat test
# Time: ~45 seconds
# Tests: 120
# Foundry
forge test
# Time: ~2 seconds
# Tests: 120
# Result: Forge ~22x faster
Why Foundry is Faster?
# Foundry parallel execution
forge test --jobs 4 # 4 cores
# Hardhat sequential (mostly)
npx hardhat test
Fuzzing Capabilities
Hardhat: Manual or Plugin-Based
// Hardhat + Echidna plugin (complex setup)
// या manual random testing
it("Fuzz transfer", async function () {
for (let i = 0; i < 100; i++) {
const amount = Math.floor(Math.random() * 1000);
// test logic
}
});
Foundry: Built-in Fuzzing
contract TokenTest is Test {
Token token;
function setUp() public {
token = new Token(1000000);
}
// ✅ Automatic fuzzing - runs 256 random inputs by default
function testTransferFuzz(address to, uint256 amount) public {
vm.assume(to != address(0));
vm.assume(amount <= token.balanceOf(address(this)));
uint256 balanceBefore = token.balanceOf(address(this));
token.transfer(to, amount);
assertEq(token.balanceOf(address(this)), balanceBefore - amount);
}
// Configure fuzz runs
/// forge-config: default.fuzz.runs = 10000
function testTransferNeverOverflows(uint256 amount) public {
vm.assume(amount <= type(uint256).max / 2);
uint256 balance = token.balanceOf(address(this));
// Test logic
}
}
Foundry fuzzing config:
# foundry.toml
[fuzz]
runs = 1000 # Number of fuzz runs
max_test_rejects = 65536
seed = '0x123' # Reproducible fuzzing
Advanced Testing Patterns
1. Time Manipulation
Hardhat:
const { time } = require("@nomicfoundation/hardhat-network-helpers");
it("Should allow withdrawal after timelock", async function () {
await vault.deposit({ value: ethers.utils.parseEther("1") });
// Fast forward 7 days
await time.increase(7 * 24 * 60 * 60);
await vault.withdraw();
});
Foundry:
function testWithdrawAfterTimelock() public {
vault.deposit{value: 1 ether}();
// Warp time 7 days forward
vm.warp(block.timestamp + 7 days);
vault.withdraw();
}
2. Pranking (Impersonation)
Hardhat:
const { impersonateAccount } = require("@nomicfoundation/hardhat-network-helpers");
it("Should allow admin to pause", async function () {
await impersonateAccount(adminAddress);
const admin = await ethers.getSigner(adminAddress);
await contract.connect(admin).pause();
});
Foundry:
function testAdminCanPause() public {
address admin = address(0xADMIN);
vm.prank(admin); // Next call from admin
contract.pause();
assertTrue(contract.paused());
}
// Multiple calls
function testMultiplePranks() public {
vm.startPrank(admin);
contract.action1();
contract.action2();
vm.stopPrank();
}
3. Expectation Testing
Hardhat:
it("Should revert on insufficient balance", async function () {
await expect(
token.transfer(addr1.address, 10000)
).to.be.revertedWith("Insufficient balance");
});
Foundry:
function testRevertInsufficientBalance() public {
vm.expectRevert("Insufficient balance");
token.transfer(addr1, 10000);
}
// More specific: expect exact error
function testRevertCustomError() public {
vm.expectRevert(
abi.encodeWithSelector(
Token.InsufficientBalance.selector,
100,
50
)
);
token.transfer(addr1, 100);
}
Fork Testing
Hardhat Fork Testing
// hardhat.config.js
module.exports = {
networks: {
hardhat: {
forking: {
url: "https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY",
blockNumber: 14390000
}
}
}
};
// test
it("Should swap on Uniswap", async function () {
const uniswap = await ethers.getContractAt(
"IUniswapV2Router",
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
);
// Interact with mainnet state
await uniswap.swapExactETHForTokens(/* ... */);
});
Foundry Fork Testing
contract ForkTest is Test {
uint256 mainnetFork;
function setUp() public {
mainnetFork = vm.createFork(
"https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY",
14390000
);
vm.selectFork(mainnetFork);
}
function testUniswapSwap() public {
IUniswapV2Router router = IUniswapV2Router(
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
);
// Fork से interact करें
router.swapExactETHForTokens{value: 1 ether}(/* ... */);
}
// Multiple forks
function testCrossFork() public {
uint256 optimismFork = vm.createFork("OPTIMISM_RPC");
// Mainnet पर action
vm.selectFork(mainnetFork);
contract.doSomething();
// Optimism पर action
vm.selectFork(optimismFork);
contract.doSomethingElse();
}
}
Performance:
- Hardhat fork: ~30s startup
- Foundry fork: ~5s startup
Debugging
Hardhat Console.log
// contracts/Token.sol
import "hardhat/console.sol";
function transfer(address to, uint256 amount) public {
console.log("Transferring", amount, "to", to);
console.log("Sender balance:", balances[msg.sender]);
balances[msg.sender] -= amount;
balances[to] += amount;
}
npx hardhat test
# Output:
# Transferring 50 to 0x1234...
# Sender balance: 1000
Foundry vm.* Cheats
import "forge-std/console.sol";
function testDebug() public {
console.log("Address:", address(this));
console.log("Balance:", address(this).balance);
// Advanced debugging
vm.expectEmit(true, true, false, true);
emit Transfer(address(this), addr1, 100);
token.transfer(addr1, 100);
// Trace specific call
vm.trace(address(token));
token.complexFunction();
}
Foundry traces:
forge test -vvvv # Verbosity levels:
# -v: Test results
# -vv: + console.log
# -vvv: + stack traces
# -vvvv: + full execution traces
Gas Reporting
Hardhat Gas Reporter
// hardhat.config.js
require("hardhat-gas-reporter");
module.exports = {
gasReporter: {
enabled: true,
currency: 'USD',
coinmarketcap: 'API_KEY'
}
};
Output:
·-----------------------|----------------------------|-------------|----------------------------·
| Contract · Method · Gas · USD (@ $2000/ETH) │
·······················|····························|·············|····························│
| Token · transfer · 51234 · 0.10 │
·-----------------------|----------------------------|-------------|----------------------------·
Foundry Gas Snapshots
forge snapshot
# Creates .gas-snapshot file
forge snapshot --diff
# Compare with previous snapshot
Output:
testTransfer() (gas: 51234)
testApprove() (gas: 44567)
Overall gas change: -1234 (-2.3%)
// Inline gas assertions
function testGasOptimization() public {
uint256 gasBefore = gasleft();
token.transfer(addr1, 100);
uint256 gasUsed = gasBefore - gasleft();
assertLt(gasUsed, 52000, "Gas usage too high");
}
Integration और Ecosystem
Hardhat Advantages
- hardhat-deploy
- hardhat-etherscan
- hardhat-typechain
- hardhat-gas-reporter
Foundry Advantages
- forge (testing)
- cast (CLI interactions)
- anvil (local node)
- chisel (REPL)
# .github/workflows/test.yml
- name: Run tests
run: forge test
# Completes in seconds
Migration Path
Hardhat → Foundry
# 1. Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 2. Initialize
forge init --force
# 3. Install dependencies
forge install openzeppelin/openzeppelin-contracts
# 4. Convert tests (manual)
# Rewrite JS tests in Solidity
# 5. Run
forge test
Hybrid Approach
// package.json
{
"scripts": {
"test:hardhat": "hardhat test",
"test:forge": "forge test",
"test": "npm run test:hardhat && npm run test:forge"
}
}
दोनों frameworks साथ चला सकते हैं:
- Hardhat: Integration tests, deployment scripts
- Foundry: Unit tests, fuzzing
Real-World Recommendation
Use Hardhat If:
- Team JavaScript में comfortable है
- Complex deployment scripts चाहिए
- Rich plugin ecosystem important है
- TypeScript typing चाहिए
Use Foundry If:
- Speed critical है (CI/CD में especially)
- Fuzzing important है
- Solidity-only development prefer करते हैं
- Minimal dependencies चाहिए
Best of Both Worlds:
├── test/
│ ├── foundry/ # Unit tests, fuzzing
│ │ ├── Token.t.sol
│ │ └── Vault.t.sol
│ └── hardhat/ # Integration tests
│ ├── deploy.test.js
│ └── integration.test.js
├── foundry.toml
└── hardhat.config.js
Conclusion
Hardhat और Foundry दोनों powerful हैं, लेकिन different use cases के लिए।
Key takeaways:
- Foundry: Faster, built-in fuzzing, Solidity-native
- Hardhat: Mature ecosystem, easier onboarding, TypeScript
- Speed difference: Foundry ~10-100x faster
- Best approach: Consider hybrid setup
नए projects में Foundry try करें — speed difference alone worth it है। Existing Hardhat projects में gradual migration consider करें।
Testing framework आपकी development velocity define करती है। Choose wisely!