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
Set up your first project and deploy a simple smart contract in under 10 minutes.
Beginner 10 min
๐Ÿช™ ERC-20 Token
Create, test, and deploy a full-featured ERC-20 token with advanced functionality.
Beginner 30 min
๐ŸŽจ NFT Collection
Build and deploy an NFT collection with minting, metadata, and marketplace integration.
Intermediate 45 min
๐Ÿฆ DeFi Protocol
Develop a complete DeFi lending protocol with yield farming and governance.
Advanced 2 hours
๐Ÿ› Advanced Debugging
Master EVM debugging techniques, transaction tracing, and state inspection.
Intermediate 40 min
๐Ÿ›ก๏ธ Security Best Practices
Learn security auditing techniques and how to protect your smart contracts.
Advanced 1 hour

๐Ÿš€ 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.

1

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 .
Note: When you open the project in VS Code, Blockchain Toolkit will automatically detect the Hardhat configuration and activate.
2

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;
    }
}
3

Compile the Contract

Use the extension to compile your contract:

  1. Open Command Palette (Ctrl+Shift+P)
  2. Type "Blockchain: Compile" and select it
  3. Or right-click on the contract file and select "Compile Contract"
Success! You should see compilation artifacts generated in the artifacts/ directory.
4

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);
  });
});
5

Run Tests

Execute your tests using the extension:

  1. Open Command Palette (Ctrl+Shift+P)
  2. Type "Blockchain: Test" and select it
  3. Watch the test results in the output panel
Tip: Use Ctrl+Shift+T as a keyboard shortcut for running tests.
6

Deploy to Local Network

Deploy your contract to a local Hardhat network:

  1. Start local node: Command Palette โ†’ "Blockchain: Start Local Node"
  2. Deploy contract: Command Palette โ†’ "Blockchain: Deploy"
  3. Select your contract and deployment network
Congratulations! You've successfully created, tested, and deployed your first smart contract using Blockchain Toolkit.

๐Ÿช™ ERC-20 Token Tutorial

Learn how to create a full-featured ERC-20 token with advanced features like minting, burning, and access control.

1

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 .
2

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);
    }
}
3

Compile and Test

Use Blockchain Toolkit to compile and test your token:

  1. Compile: Ctrl+Shift+P โ†’ "Blockchain: Compile"
  2. Create comprehensive tests in test/MyToken.js
  3. Run tests: Ctrl+Shift+P โ†’ "Blockchain: Test"
1

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 .
2

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.

1

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 .
2

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);
    }
}
3

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"
    }
  ]
}
4

Deploy and Verify

Use Blockchain Toolkit to deploy and verify your NFT contract:

  1. Compile: Ctrl+Shift+P โ†’ "Blockchain: Compile"
  2. Deploy: Ctrl+Shift+P โ†’ "Blockchain: Deploy"
  3. Verify: Ctrl+Shift+P โ†’ "Blockchain: Verify Contract"
Tip: Make sure to upload your metadata to IPFS before setting the base URI.

๐Ÿฆ DeFi Protocol Tutorial

Build a complete DeFi lending protocol with yield farming and governance features.

1

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;
    }
}
2

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.

1

Automated Security Scanning

Use Blockchain Toolkit's integrated security tools:

  1. Open Command Palette (Ctrl+Shift+P)
  2. Run "Blockchain: Security Scan" for comprehensive analysis
  3. Run "Blockchain: Slither Analysis" for static analysis
  4. Run "Blockchain: MythX Scan" for professional audit
Note: MythX requires an API key for full analysis. Get one from mythx.io
2

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
    }
}
3

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.

1

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 .
Note: Blockchain Toolkit will automatically detect the Anchor project and provide Solana-specific commands.
2

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,
}
3

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);
  });
});
4

Build and Deploy

Use Blockchain Toolkit commands:

  1. Build: Ctrl+Shift+P โ†’ "Blockchain: Build Solana Program"
  2. Test: Ctrl+Shift+P โ†’ "Blockchain: Test Solana Program"
  3. Deploy: Ctrl+Shift+P โ†’ "Blockchain: Deploy Solana Program"
Tip: Make sure to configure your Solana cluster (devnet/testnet/mainnet) before deploying.

๐Ÿงช Testing Strategies Tutorial

Master comprehensive testing strategies using Blockchain Toolkit's advanced testing features.

1

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);
    });
  });
});
2

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"));
  });
});
3

Gas Optimization Testing

Use Blockchain Toolkit's gas analysis features:

  1. Run tests: Ctrl+Shift+P โ†’ "Blockchain: Test with Gas Report"
  2. Analyze gas usage: Ctrl+Shift+P โ†’ "Blockchain: Gas Analysis"
  3. 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);
  });
});
4

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.

1

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;
    }
}
2

Debug Transaction

Use the EVM debugger to trace problematic transactions:

  1. Deploy the contract and cause a failing transaction
  2. Copy the transaction hash
  3. Command Palette โ†’ "Blockchain: Debug Transaction"
  4. Paste the transaction hash
  5. Step through the execution to find the issue
Pro Tip: Use the state inspector to view storage changes and identify where the bug occurs.
3

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
Keyboard Shortcuts: Use F10 (step over), F11 (step into), and F5 (continue) for debugging navigation.
Ready to Learn More? These examples are just the beginning. Each tutorial includes complete source code, step-by-step instructions, and best practices. Visit our GitHub repository for the complete examples and more advanced tutorials.