HomeBlogQA Blockchain Testing: Smart Contract & Network Performance with Hardhat | by...

QA Blockchain Testing: Smart Contract & Network Performance with Hardhat | by Ploy Thanasornsawan | Coinmonks | Feb, 2025

-

🛠️ “Testing Node & Network Behavior in Smart Contracts Using Hardhat with TypeScript & Mocha”A blockchain network consists of multiple nodes that communicate with each other. Each node participates in mining blocks to form a chain, which requires gas fees. Each block contains:Transaction data (e.g., deposit or transfer records)Timestamp (when the block was created)A link to the previous blockTechnical information about the network stateFor example, if you deposit 10 ETH, the block would record:Your wallet address (who sent it)The contract address (who received it)The amount (10 ETH)The transaction timestampOther technical details about the transactionIn this blog, we will test a smart contract for token lending, where you can deposit ETH to borrow WETH tokens, as shown in the Solidity code below:Network Simulation Capabilities// In Hardhat, we can control block miningawait network.provider.send(“evm_mine”, []); // Mine a new block// We can control block timestampsawait network.provider.send(“evm_increaseTime”, [3600]); // Move time forward 1 hour// We can even control gas pricesawait network.provider.send(“hardhat_setNextBlockBaseFeePerGas”, [ethers.utils.hexValue(ethers.utils.parseUnits(“100”, “gwei”))]);You can save and restore the entire blockchain state for test error recovery:describe(“Error Recovery Testing”, () => {it(“should recover from failed operations”, async function() {// Take snapshot before risky operationconst snapshotId = await network.provider.send(“evm_snapshot”, []);try {// Attempt potentially failing operationawait lendingProtocol.withdraw(ethers.utils.parseEther(“100”)); // More than deposited} catch (error) {// Revert to clean stateawait network.provider.send(“evm_revert”, [snapshotId]);// Verify state is cleanconst balance = await lendingProtocol.getUserDeposit(signer.address);expect(balance).to.equal(0);}});});Hardhat allows us to create a copy of the real Ethereum mainnet at any point in time for testing.// We can fork mainnet at a specific blockawait network.provider.request({method: “hardhat_reset”,params: [{forking: {jsonRpcUrl: MAINNET_RPC_URL,blockNumber: 15000000 // Specific block number}}]});// Now we can interact with real mainnet contractsconst USDC = await ethers.getContractAt(“IERC20”, “0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48”);We can get stack traces for failed transactionsit(“should maintain consistent performance under sustained load”, async function() {const testUser = await setupTestUser(0, “1000”);const userContract = lendingProtocol.connect(testUser);const depositAmount = ethers.utils.parseEther(“0.1”);const iterations = 3;const results = [];try {// Get initial balanceconst initialBalance = await userContract.getUserDeposit(testUser.address);console.log(“Initial balance:”, ethers.utils.formatEther(initialBalance));} catch (error) {console.error(“Load test failed:”, error);}You can also use Harthat to debug Smart Contract in the console:npx hardhat consoleWith Hardhat, we have extensive control over the test environment.// We can manipulate account balances directlyawait network.provider.send(“hardhat_setBalance”, [userAddress,ethers.utils.hexValue(ethers.utils.parseEther(“100”))]);// We can impersonate any Ethereum addressawait network.provider.request({method: “hardhat_impersonateAccount”,params: [whaleAddress],});Advance Hardhat feature:We can test how our protocol handles pending transactions and mempool behavior like the code below.describe(“Transaction Pool Behavior”, () => {it(“should handle multiple pending transactions correctly”, async function() {// Enable auto-miningawait network.provider.send(“evm_setAutomine”, [false]);// Submit multiple transactions that will sit in the mempoolconst tx1 = lendingProtocol.deposit({ value: ethers.utils.parseEther(“1”) });const tx2 = lendingProtocol.deposit({ value: ethers.utils.parseEther(“2”) });const tx3 = lendingProtocol.deposit({ value: ethers.utils.parseEther(“3”) });// Get pending transactionsconst pendingTxs = await network.provider.send(“eth_pendingTransactions”);console.log(“Pending transactions:”, pendingTxs.length);// Mine them all at onceawait network.provider.send(“evm_mine”);// Verify final stateconst totalDeposits = await lendingProtocol.totalDeposits();expect(totalDeposits).to.equal(ethers.utils.parseEther(“6”));});});To run a test script with Hardhat on local:npx hardhat testMethod 1: Have fully access to the dev code repository// Need access to contract code and typechainimport { TestLendingProtocol } from “../../typechain/contracts/TestLendingProtocol”;// Full testing capabilities with type safetydescribe(“Full Testing Suite”, () => {let lendingProtocol: TestLendingProtocol;beforeEach(async () => {lendingProtocol = await ethers.getContractAt(“TestLendingProtocol”,addresses.lendingProtocol,signer);});});Before importing from the typechain folder, you might need to run this command to compile the Solidity file to generate ABI (Application Binary Interface) and TypeChain types.npx hardhat compileThis creates:artifacts/ folder containing ABIstypechain/ folder containing TypeScript typesMethod 2: Testing Deployed Contracts (No Source Code Access)// QA only has contract address and ABIconst contractAddress = “0x123…”; // Deployed contract addressconst abi = [“function deposit() external payable”,”function withdraw(uint256 amount) external”];describe(“Limited Testing Suite”, () => {it(“can test basic functionality”, async () => {const contract = new ethers.Contract(contractAddress, abi, signer);// Can test basic functionsawait contract.deposit({ value: ethers.utils.parseEther(“1”) });// Can’t simulate network conditions as effectively// Limited to actual network behavior});});About testing deployed contracts, you can still use Hardhat to fork the Ethereum Mainnet or a Testnet, depending on which network the developer has deployed to.If you fork Ethereum Mainnet:// CORRECT: Using Mainnet contract addressconst MAINNET_USDC = “0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48”;const MAINNET_WETH = “0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2”;await network.provider.request({method: “hardhat_reset”,params: [{forking: {jsonRpcUrl: MAINNET_RPC_URL, // Mainnet RPC URLblockNumber: 15000000}}]});const usdc = new ethers.Contract(MAINNET_USDC, USDC_ABI, signer);const weth = new ethers.Contract(MAINNET_WETH, WETH_ABI, signer);If you fork Testnets (e.g., Sepolia):// CORRECT: Using Sepolia contract addressconst SEPOLIA_USDC = “0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238”; // Example addressawait network.provider.request({method: “hardhat_reset”,params: [{forking: {jsonRpcUrl: SEPOLIA_RPC_URL, // Sepolia RPC URLblockNumber: 3000000}}]});const usdc = new ethers.Contract(SEPOLIA_USDC, USDC_ABI, signer);

LATEST POSTS

XRP price may crash 65%, Wyckoff Theory shows

The XRP price has suffered a harsh reversal this month, moving into...

EUR/USD rebound to stall near 1.0450: ING

EUR/USD rebound to stall near 1.0450: ING Source link

US, UK, and Australia Sanction Zservers for Hosting LockBit Ransom…

The United States, the United Kingdom, and Australia have imposed sanctions on Zservers, a Russia-based web hosting provider accused of supporting the LockBit ransomware gang....

Abu Dhabi’s crypto mining firm Phoenix Group posts 236% revenue surge amid global expansion

Phoenix Group reported $107 million in Bitcoin mining revenue for 2024, more...

Most Popular

Blockonomics is a decentralized and permissionless bitcoin payment solution