Examples & Tutorials
Learn Blockchain Toolkit through practical examples and step-by-step tutorials. Each example includes complete code, explanations, and best practices.
๐ Quick Start Tutorial
This tutorial will get you up and running with Blockchain Toolkit in under 10 minutes. You'll create a simple smart contract, compile it, test it, and deploy it to a local network.
Create a New Project
First, let's create a new Hardhat project using the extension:
# Create a new directory
mkdir my-first-dapp
cd my-first-dapp
# Initialize Hardhat project
npx hardhat init
# Choose "Create a JavaScript project"
# Accept default settings
# Open in VS Code
code .
Write Your First Contract
Create a simple storage contract in contracts/SimpleStorage.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract SimpleStorage {
uint256 private storedData;
event DataStored(uint256 indexed value, address indexed sender);
function set(uint256 x) public {
storedData = x;
emit DataStored(x, msg.sender);
}
function get() public view returns (uint256) {
return storedData;
}
}
Compile the Contract
Use the extension to compile your contract:
- Open Command Palette (
Ctrl+Shift+P) - Type "Blockchain: Compile" and select it
- Or right-click on the contract file and select "Compile Contract"
artifacts/ directory.
Write Tests
Create a test file test/SimpleStorage.js:
const { expect } = require("chai");
describe("SimpleStorage", function () {
let simpleStorage;
let owner;
beforeEach(async function () {
[owner] = await ethers.getSigners();
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
simpleStorage = await SimpleStorage.deploy();
});
it("Should store and retrieve a value", async function () {
const value = 42;
// Set the value
await simpleStorage.set(value);
// Get the value
expect(await simpleStorage.get()).to.equal(value);
});
it("Should emit DataStored event", async function () {
const value = 123;
await expect(simpleStorage.set(value))
.to.emit(simpleStorage, "DataStored")
.withArgs(value, owner.address);
});
});
Run Tests
Execute your tests using the extension:
- Open Command Palette (
Ctrl+Shift+P) - Type "Blockchain: Test" and select it
- Watch the test results in the output panel
Ctrl+Shift+T as a keyboard shortcut for running tests.
Deploy to Local Network
Deploy your contract to a local Hardhat network:
- Start local node: Command Palette โ "Blockchain: Start Local Node"
- Deploy contract: Command Palette โ "Blockchain: Deploy"
- Select your contract and deployment network
๐ช ERC-20 Token Tutorial
Learn how to create a full-featured ERC-20 token with advanced features like minting, burning, and access control.
Setup and Dependencies
Create a new Hardhat project and install OpenZeppelin:
# Create project
mkdir my-token-project
cd my-token-project
npx hardhat init
# Install OpenZeppelin
npm install @openzeppelin/contracts
# Open in VS Code
code .
Create the Token Contract
Create contracts/MyToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable {
uint256 public constant MAX_SUPPLY = 1000000 * 10**18; // 1 million tokens
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 100000 * 10**18); // Initial mint: 100k tokens
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal
whenNotPaused
override
{
super._beforeTokenTransfer(from, to, amount);
}
}
Compile and Test
Use Blockchain Toolkit to compile and test your token:
- Compile:
Ctrl+Shift+Pโ "Blockchain: Compile" - Create comprehensive tests in
test/MyToken.js - Run tests:
Ctrl+Shift+Pโ "Blockchain: Test"
Setup Foundry Project
# Create project
forge init my-token-foundry
cd my-token-foundry
# Install OpenZeppelin
forge install OpenZeppelin/openzeppelin-contracts
# Open in VS Code
code .
Configure Dependencies
Update foundry.toml:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
]
๐จ NFT Collection Tutorial
Build and deploy a complete NFT collection with minting, metadata management, and marketplace integration.
Project Setup
Create a new project for your NFT collection:
# Create new project
mkdir my-nft-collection
cd my-nft-collection
npx hardhat init
# Install dependencies
npm install @openzeppelin/contracts
npm install @nomiclabs/hardhat-etherscan
# Open in VS Code
code .
Create NFT Contract
Create contracts/MyNFTCollection.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFTCollection is ERC721, ERC721Enumerable, Pausable, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.001 ether;
uint256 public constant MAX_MINT_PER_TX = 10;
string private _baseTokenURI;
bool public mintingActive = false;
constructor(string memory baseURI) ERC721("MyNFTCollection", "MNC") {
_baseTokenURI = baseURI;
}
function mint(uint256 quantity) external payable whenNotPaused {
require(mintingActive, "Minting is not active");
require(quantity > 0 && quantity <= MAX_MINT_PER_TX, "Invalid quantity");
require(totalSupply() + quantity <= MAX_SUPPLY, "Exceeds max supply");
require(msg.value >= MINT_PRICE * quantity, "Insufficient payment");
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
}
}
function ownerMint(address to, uint256 quantity) external onlyOwner {
require(totalSupply() + quantity <= MAX_SUPPLY, "Exceeds max supply");
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
}
function setMintingActive(bool active) external onlyOwner {
mintingActive = active;
}
function setBaseURI(string memory baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function withdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
function _baseURI() internal view override returns (string memory) {
return _baseTokenURI;
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal
whenNotPaused
override(ERC721, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
Create Metadata Structure
Set up metadata for your NFTs. Create a metadata/ directory:
{
"name": "My NFT Collection #1",
"description": "A unique digital collectible",
"image": "https://your-domain.com/images/1.png",
"attributes": [
{
"trait_type": "Background",
"value": "Blue"
},
{
"trait_type": "Character",
"value": "Robot"
},
{
"trait_type": "Rarity",
"value": "Common"
}
]
}
Deploy and Verify
Use Blockchain Toolkit to deploy and verify your NFT contract:
- Compile:
Ctrl+Shift+Pโ "Blockchain: Compile" - Deploy:
Ctrl+Shift+Pโ "Blockchain: Deploy" - Verify:
Ctrl+Shift+Pโ "Blockchain: Verify Contract"
๐ฆ DeFi Protocol Tutorial
Build a complete DeFi lending protocol with yield farming and governance features.
Protocol Architecture
Create the main lending pool contract contracts/LendingPool.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract LendingPool is ReentrancyGuard, Ownable {
using SafeERC20 for IERC20;
struct UserInfo {
uint256 amount;
uint256 rewardDebt;
uint256 lastDepositTime;
}
struct PoolInfo {
IERC20 token;
uint256 totalSupply;
uint256 accRewardPerShare;
uint256 lastRewardBlock;
uint256 rewardPerBlock;
uint256 lockupPeriod;
}
PoolInfo[] public poolInfo;
mapping(uint256 => mapping(address => UserInfo)) public userInfo;
IERC20 public rewardToken;
uint256 public startBlock;
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
constructor(IERC20 _rewardToken, uint256 _startBlock) {
rewardToken = _rewardToken;
startBlock = _startBlock;
}
function addPool(
IERC20 _token,
uint256 _rewardPerBlock,
uint256 _lockupPeriod
) external onlyOwner {
poolInfo.push(PoolInfo({
token: _token,
totalSupply: 0,
accRewardPerShare: 0,
lastRewardBlock: block.number > startBlock ? block.number : startBlock,
rewardPerBlock: _rewardPerBlock,
lockupPeriod: _lockupPeriod
}));
}
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
if (pool.totalSupply == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = block.number - pool.lastRewardBlock;
uint256 reward = multiplier * pool.rewardPerBlock;
pool.accRewardPerShare += (reward * 1e12) / pool.totalSupply;
pool.lastRewardBlock = block.number;
}
function deposit(uint256 _pid, uint256 _amount) external nonReentrant {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
if (user.amount > 0) {
uint256 pending = (user.amount * pool.accRewardPerShare) / 1e12 - user.rewardDebt;
if (pending > 0) {
rewardToken.safeTransfer(msg.sender, pending);
}
}
if (_amount > 0) {
pool.token.safeTransferFrom(msg.sender, address(this), _amount);
user.amount += _amount;
pool.totalSupply += _amount;
user.lastDepositTime = block.timestamp;
}
user.rewardDebt = (user.amount * pool.accRewardPerShare) / 1e12;
emit Deposit(msg.sender, _pid, _amount);
}
function withdraw(uint256 _pid, uint256 _amount) external nonReentrant {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, "Insufficient balance");
require(
block.timestamp >= user.lastDepositTime + pool.lockupPeriod,
"Lockup period not met"
);
updatePool(_pid);
uint256 pending = (user.amount * pool.accRewardPerShare) / 1e12 - user.rewardDebt;
if (pending > 0) {
rewardToken.safeTransfer(msg.sender, pending);
}
if (_amount > 0) {
user.amount -= _amount;
pool.totalSupply -= _amount;
pool.token.safeTransfer(msg.sender, _amount);
}
user.rewardDebt = (user.amount * pool.accRewardPerShare) / 1e12;
emit Withdraw(msg.sender, _pid, _amount);
}
function pendingReward(uint256 _pid, address _user) external view returns (uint256) {
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accRewardPerShare = pool.accRewardPerShare;
if (block.number > pool.lastRewardBlock && pool.totalSupply != 0) {
uint256 multiplier = block.number - pool.lastRewardBlock;
uint256 reward = multiplier * pool.rewardPerBlock;
accRewardPerShare += (reward * 1e12) / pool.totalSupply;
}
return (user.amount * accRewardPerShare) / 1e12 - user.rewardDebt;
}
}
Add Governance Features
Create a governance token contract contracts/GovernanceToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes, Ownable {
constructor() ERC20("GovernanceToken", "GOV") ERC20Permit("GovernanceToken") {
_mint(msg.sender, 1000000 * 10**decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
function _burn(address account, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._burn(account, amount);
}
}
๐ก๏ธ Security Audit Tutorial
Learn comprehensive security analysis techniques using Blockchain Toolkit's built-in security tools.
Automated Security Scanning
Use Blockchain Toolkit's integrated security tools:
- Open Command Palette (
Ctrl+Shift+P) - Run "Blockchain: Security Scan" for comprehensive analysis
- Run "Blockchain: Slither Analysis" for static analysis
- Run "Blockchain: MythX Scan" for professional audit
Common Vulnerability Patterns
Example of vulnerable contract patterns to avoid:
// VULNERABLE PATTERNS - DON'T USE IN PRODUCTION
contract VulnerableContract {
mapping(address => uint256) public balances;
// โ Reentrancy vulnerability
function withdraw() external {
uint256 amount = balances[msg.sender];
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] = 0; // Too late!
}
// โ Integer overflow (pre-0.8.0)
function unsafeAdd(uint256 a, uint256 b) external pure returns (uint256) {
return a + b; // Can overflow
}
// โ Unchecked external call
function unsafeCall(address target, bytes calldata data) external {
target.call(data); // Ignores return value
}
}
Secure Implementation
Secure version using best practices:
// SECURE IMPLEMENTATION
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureContract is ReentrancyGuard, Ownable {
mapping(address => uint256) public balances;
event Withdrawal(address indexed user, uint256 amount);
event Deposit(address indexed user, uint256 amount);
// โ
Protected against reentrancy
function withdraw() external nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance to withdraw");
balances[msg.sender] = 0; // Update state first
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawal(msg.sender, amount);
}
// โ
Safe math (automatic in 0.8.0+)
function safeAdd(uint256 a, uint256 b) external pure returns (uint256) {
return a + b; // Automatically reverts on overflow
}
// โ
Checked external call
function safeCall(address target, bytes calldata data) external onlyOwner returns (bool) {
(bool success,) = target.call(data);
return success;
}
// โ
Deposit with proper checks
function deposit() external payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
}
โก Solana Program Tutorial
Build and deploy Solana programs using Anchor framework with Blockchain Toolkit.
Setup Solana Development
Install Solana tools and create an Anchor project:
# Install Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/v1.16.0/install)"
# Install Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install latest
avm use latest
# Create new Anchor project
anchor init my-solana-program
cd my-solana-program
# Open in VS Code
code .
Create Solana Program
Edit programs/my-solana-program/src/lib.rs:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod my_solana_program {
use super::*;
pub fn initialize(ctx: Context, data: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
my_account.authority = ctx.accounts.authority.key();
Ok(())
}
pub fn update(ctx: Context, data: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + 32 + 8
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(
mut,
has_one = authority
)]
pub my_account: Account<'info, MyAccount>,
pub authority: Signer<'info>,
}
#[account]
pub struct MyAccount {
pub authority: Pubkey,
pub data: u64,
}
Write Tests
Create tests in tests/my-solana-program.ts:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MySolanaProgram } from "../target/types/my_solana_program";
import { expect } from "chai";
describe("my-solana-program", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MySolanaProgram as Program;
const myAccount = anchor.web3.Keypair.generate();
it("Initializes the account", async () => {
const tx = await program.methods
.initialize(new anchor.BN(1234))
.accounts({
myAccount: myAccount.publicKey,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([myAccount])
.rpc();
console.log("Transaction signature:", tx);
const account = await program.account.myAccount.fetch(myAccount.publicKey);
expect(account.data.toNumber()).to.equal(1234);
expect(account.authority.toString()).to.equal(provider.wallet.publicKey.toString());
});
it("Updates the account", async () => {
await program.methods
.update(new anchor.BN(5678))
.accounts({
myAccount: myAccount.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
const account = await program.account.myAccount.fetch(myAccount.publicKey);
expect(account.data.toNumber()).to.equal(5678);
});
});
Build and Deploy
Use Blockchain Toolkit commands:
- Build:
Ctrl+Shift+Pโ "Blockchain: Build Solana Program" - Test:
Ctrl+Shift+Pโ "Blockchain: Test Solana Program" - Deploy:
Ctrl+Shift+Pโ "Blockchain: Deploy Solana Program"
๐งช Testing Strategies Tutorial
Master comprehensive testing strategies using Blockchain Toolkit's advanced testing features.
Unit Testing Best Practices
Create comprehensive unit tests with proper setup:
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
describe("Token Contract", function () {
// Fixture for consistent test setup
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy("TestToken", "TT", 1000000);
return { token, owner, addr1, addr2 };
}
describe("Deployment", function () {
it("Should set the right owner", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign the total supply to the owner", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
const { token, owner, addr1, addr2 } = await loadFixture(deployTokenFixture);
// Transfer 50 tokens from owner to addr1
await expect(token.transfer(addr1.address, 50))
.to.changeTokenBalances(token, [owner, addr1], [-50, 50]);
// Transfer 50 tokens from addr1 to addr2
await expect(token.connect(addr1).transfer(addr2.address, 50))
.to.changeTokenBalances(token, [addr1, addr2], [-50, 50]);
});
it("Should fail if sender doesn't have enough tokens", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
const initialOwnerBalance = await token.balanceOf(owner.address);
await expect(
token.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("ERC20: transfer amount exceeds balance");
expect(await token.balanceOf(owner.address)).to.equal(
initialOwnerBalance
);
});
it("Should emit Transfer events", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
await expect(token.transfer(addr1.address, 50))
.to.emit(token, "Transfer")
.withArgs(owner.address, addr1.address, 50);
});
});
});
Integration Testing
Test complex interactions between multiple contracts:
describe("DeFi Protocol Integration", function () {
let token, lendingPool, governance;
let owner, user1, user2;
beforeEach(async function () {
[owner, user1, user2] = await ethers.getSigners();
// Deploy all contracts
const Token = await ethers.getContractFactory("Token");
token = await Token.deploy("TestToken", "TT", ethers.utils.parseEther("1000000"));
const GovernanceToken = await ethers.getContractFactory("GovernanceToken");
governance = await GovernanceToken.deploy();
const LendingPool = await ethers.getContractFactory("LendingPool");
lendingPool = await LendingPool.deploy(governance.address, 0);
// Setup initial state
await token.transfer(user1.address, ethers.utils.parseEther("1000"));
await token.transfer(user2.address, ethers.utils.parseEther("1000"));
await lendingPool.addPool(token.address, ethers.utils.parseEther("1"), 86400); // 1 day lockup
});
it("Should handle complete deposit-withdraw cycle", async function () {
const depositAmount = ethers.utils.parseEther("100");
// User1 approves and deposits
await token.connect(user1).approve(lendingPool.address, depositAmount);
await lendingPool.connect(user1).deposit(0, depositAmount);
// Check pool state
const poolInfo = await lendingPool.poolInfo(0);
expect(poolInfo.totalSupply).to.equal(depositAmount);
// Fast forward time to after lockup period
await ethers.provider.send("evm_increaseTime", [86401]);
await ethers.provider.send("evm_mine");
// Withdraw
await lendingPool.connect(user1).withdraw(0, depositAmount);
// Verify final state
const finalBalance = await token.balanceOf(user1.address);
expect(finalBalance).to.be.gte(ethers.utils.parseEther("1000"));
});
});
Gas Optimization Testing
Use Blockchain Toolkit's gas analysis features:
- Run tests:
Ctrl+Shift+Pโ "Blockchain: Test with Gas Report" - Analyze gas usage:
Ctrl+Shift+Pโ "Blockchain: Gas Analysis" - Compare optimizations with gas snapshots
// Gas optimization test example
describe("Gas Optimization", function () {
it("Should use minimal gas for batch operations", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
// Test single transfers vs batch transfer
const recipients = [addr1.address, addr2.address, addr3.address];
const amounts = [100, 200, 300];
// Measure gas for individual transfers
const individualGas = [];
for (let i = 0; i < recipients.length; i++) {
const tx = await token.transfer(recipients[i], amounts[i]);
const receipt = await tx.wait();
individualGas.push(receipt.gasUsed);
}
// Reset state and test batch transfer
const batchTx = await token.batchTransfer(recipients, amounts);
const batchReceipt = await batchTx.wait();
// Batch should be more efficient
const totalIndividualGas = individualGas.reduce((a, b) => a.add(b));
expect(batchReceipt.gasUsed).to.be.lt(totalIndividualGas);
});
});
Fuzz Testing
Use Foundry for property-based testing:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../contracts/Token.sol";
contract TokenFuzzTest is Test {
Token token;
function setUp() public {
token = new Token("TestToken", "TT", 1000000 * 10**18);
}
function testFuzzTransfer(address to, uint256 amount) public {
// Assume valid conditions
vm.assume(to != address(0));
vm.assume(to != address(this));
vm.assume(amount <= token.balanceOf(address(this)));
uint256 balanceBefore = token.balanceOf(address(this));
uint256 toBalanceBefore = token.balanceOf(to);
token.transfer(to, amount);
// Invariant checks
assertEq(token.balanceOf(address(this)), balanceBefore - amount);
assertEq(token.balanceOf(to), toBalanceBefore + amount);
}
function testFuzzMint(address to, uint256 amount) public {
vm.assume(to != address(0));
vm.assume(amount <= type(uint256).max - token.totalSupply());
uint256 totalSupplyBefore = token.totalSupply();
uint256 balanceBefore = token.balanceOf(to);
token.mint(to, amount);
assertEq(token.totalSupply(), totalSupplyBefore + amount);
assertEq(token.balanceOf(to), balanceBefore + amount);
}
}
๐ Advanced Debugging Tutorial
Master the advanced debugging features of Blockchain Toolkit, including transaction tracing, state inspection, and EVM debugging.
Setup Debug Environment
First, let's set up a contract with a bug to debug:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract BuggyContract {
mapping(address => uint256) private balances;
uint256 public totalSupply;
function mint(address to, uint256 amount) external {
// Bug: potential overflow
balances[to] += amount;
totalSupply += amount;
}
function transfer(address to, uint256 amount) external {
// Bug: no balance check
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
Debug Transaction
Use the EVM debugger to trace problematic transactions:
- Deploy the contract and cause a failing transaction
- Copy the transaction hash
- Command Palette โ "Blockchain: Debug Transaction"
- Paste the transaction hash
- Step through the execution to find the issue
Advanced Debugging Features
Blockchain Toolkit provides several debugging commands:
- EVM State Inspector: View contract storage, memory, and stack
- Transaction Tracer: Step through execution with gas tracking
- Memory Analyzer: Inspect memory layout and heap usage
- Call Stack Viewer: Navigate through function calls