Skip to main content

Overview

Testing applications on Paxeer Network follows the same principles as Ethereum development. This guide covers best practices specific to building reliable applications on Paxeer Network.
For most tests, you don’t need Paxeer-specific features. Use your development stack’s built-in testing tools for faster iteration.

Testing Strategy

1

Unit Tests (Local)

Test individual functions with framework’s local networkTools: Hardhat Network, Anvil (Foundry), GanacheSpeed: ⚡ FastestWhen: 90% of your tests
2

Integration Tests (Local Fork)

Test interactions with deployed contractsTools: Hardhat forking, Foundry forkingSpeed: ⚡ FastWhen: Testing with existing protocols
3

Testnet Tests

Test on live network with real conditionsNetwork: Paxeer Testnet (if available)Speed: 🐌 SlowerWhen: Final validation before mainnet
4

Mainnet

Deploy to productionNetwork: Paxeer Network (Chain ID: 229)When: After thorough testing

Unit Testing

Hardhat Example

test/MyContract.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("MyContract", function () {
  let contract;
  let owner;
  let addr1;
  let addr2;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();
    
    const MyContract = await ethers.getContractFactory("MyContract");
    contract = await MyContract.deploy();
    await contract.waitForDeployment();
  });

  describe("Deployment", function () {
    it("Should set the right owner", async function () {
      expect(await contract.owner()).to.equal(owner.address);
    });

    it("Should start with zero value", async function () {
      expect(await contract.getValue()).to.equal(0);
    });
  });

  describe("Transactions", function () {
    it("Should update value", async function () {
      await contract.setValue(42);
      expect(await contract.getValue()).to.equal(42);
    });

    it("Should emit ValueChanged event", async function () {
      await expect(contract.setValue(42))
        .to.emit(contract, "ValueChanged")
        .withArgs(42);
    });

    it("Should revert when unauthorized", async function () {
      await expect(
        contract.connect(addr1).adminFunction()
      ).to.be.revertedWith("Not authorized");
    });
  });

  describe("Edge Cases", function () {
    it("Should handle zero value", async function () {
      await contract.setValue(0);
      expect(await contract.getValue()).to.equal(0);
    });

    it("Should handle max uint256", async function () {
      const maxUint = ethers.MaxUint256;
      await contract.setValue(maxUint);
      expect(await contract.getValue()).to.equal(maxUint);
    });
  });
});

Foundry Example

test/MyContract.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/MyContract.sol";

contract MyContractTest is Test {
    MyContract public myContract;
    address owner = address(1);
    address user = address(2);

    function setUp() public {
        vm.prank(owner);
        myContract = new MyContract();
    }

    function testSetValue() public {
        myContract.setValue(42);
        assertEq(myContract.getValue(), 42);
    }

    function testEventEmission() public {
        vm.expectEmit(true, true, true, true);
        emit ValueChanged(42);
        myContract.setValue(42);
    }

    function testUnauthorized() public {
        vm.prank(user);
        vm.expectRevert("Not authorized");
        myContract.adminFunction();
    }

    function testFuzz_setValue(uint256 value) public {
        myContract.setValue(value);
        assertEq(myContract.getValue(), value);
    }
}

Integration Testing

Testing with Mainnet Fork

Fork Paxeer Network mainnet to test against real deployed contracts:
hardhat.config.js
module.exports = {
  networks: {
    hardhat: {
      forking: {
        url: "https://public-rpc.paxeer.app/rpc",
        blockNumber: 1000000, // Optional: pin to block
      },
    },
  },
};
test/Integration.test.js
describe("PaxDex Integration", function () {
  it("Should swap tokens", async function () {
    const vault = await ethers.getContractAt(
      "PaxDexVault",
      "0x49B0f9a0554da1A7243A9C8ac5B45245A66D90ff"
    );

    // Test with real contract
    const price = await vault.getPrice(wbtcAddress);
    expect(price).to.be.gt(0);
  });
});

Gas Testing

Measure and Optimize Gas Usage

test/Gas.test.js
describe("Gas Optimization", function () {
  it("Should track gas usage", async function () {
    const tx = await contract.setValue(42);
    const receipt = await tx.wait();
    
    console.log("Gas used:", receipt.gasUsed.toString());
    
    // Assert gas usage is within expected range
    expect(receipt.gasUsed).to.be.lt(50000);
  });

  it("Should compare optimized vs unoptimized", async function () {
    // Test optimized function
    const tx1 = await contract.optimizedFunction();
    const receipt1 = await tx1.wait();
    
    // Test unoptimized function
    const tx2 = await contract.unoptimizedFunction();
    const receipt2 = await tx2.wait();
    
    console.log("Optimized gas:", receipt1.gasUsed.toString());
    console.log("Unoptimized gas:", receipt2.gasUsed.toString());
    
    expect(receipt1.gasUsed).to.be.lt(receipt2.gasUsed);
  });
});

Foundry Gas Snapshots

# Create gas snapshot
forge snapshot

# Compare with previous
forge snapshot --diff
contract GasTest is Test {
    function testGas_transfer() public {
        token.transfer(user, 100 ether);
    }
    
    function testGas_batchTransfer() public {
        address[] memory recipients = new address[](10);
        // ... test batch operation
    }
}

Testing Best Practices

Aim for high test coverage:
# Hardhat coverage
npx hardhat coverage

# Foundry coverage
forge coverage
Target:
  • Statements: > 90%
  • Branches: > 80%
  • Functions: > 90%
  • Lines: > 90%
Test boundary conditions:
describe("Edge Cases", function () {
  it("Should handle zero", async function () {
    await contract.setValue(0);
  });

  it("Should handle max uint256", async function () {
    await contract.setValue(ethers.MaxUint256);
  });

  it("Should handle empty arrays", async function () {
    await contract.processBatch([]);
  });

  it("Should handle duplicate values", async function () {
    await contract.addItem(1);
    await expect(contract.addItem(1)).to.be.reverted;
  });
});
Foundry’s fuzzing finds edge cases automatically:
// Foundry will test with random values
function testFuzz_transfer(uint256 amount) public {
    vm.assume(amount <= token.balanceOf(address(this)));
    token.transfer(user, amount);
    assertEq(token.balanceOf(user), amount);
}

function testFuzz_division(uint256 a, uint256 b) public {
    vm.assume(b != 0);
    uint256 result = a / b;
    assertLe(result, a);
}
Verify events are emitted correctly:
it("Should emit Transfer event", async function () {
  await expect(token.transfer(addr1.address, 100))
    .to.emit(token, "Transfer")
    .withArgs(owner.address, addr1.address, 100);
});
Test time-dependent functionality:
it("Should unlock after time period", async function () {
  await contract.lock();
  
  // Fast forward time
  await ethers.provider.send("evm_increaseTime", [86400]); // 1 day
  await ethers.provider.send("evm_mine");
  
  await contract.unlock();
});
Foundry:
function testTimeLock() public {
    contract.lock();
    
    // Warp time forward
    vm.warp(block.timestamp + 1 days);
    
    contract.unlock();
}

Testing Multi-Contract Interactions

test/MultiContract.test.js
describe("Token Vault Integration", function () {
  let token, vault;
  let owner, user;

  beforeEach(async function () {
    [owner, user] = await ethers.getSigners();

    // Deploy token
    const Token = await ethers.getContractFactory("MyToken");
    token = await Token.deploy();

    // Deploy vault
    const Vault = await ethers.getContractFactory("Vault");
    vault = await Vault.deploy(await token.getAddress());

    // Setup: give user some tokens
    await token.transfer(user.address, ethers.parseEther("1000"));
  });

  it("Should deposit and withdraw", async function () {
    const amount = ethers.parseEther("100");

    // User approves vault
    await token.connect(user).approve(await vault.getAddress(), amount);

    // User deposits
    await vault.connect(user).deposit(amount);
    expect(await vault.balances(user.address)).to.equal(amount);
    expect(await token.balanceOf(user.address)).to.equal(
      ethers.parseEther("900")
    );

    // User withdraws
    await vault.connect(user).withdraw(amount);
    expect(await vault.balances(user.address)).to.equal(0);
    expect(await token.balanceOf(user.address)).to.equal(
      ethers.parseEther("1000")
    );
  });
});

Continuous Integration

GitHub Actions Example

.github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
      
      - name: Run tests
        run: forge test -vvv
      
      - name: Check coverage
        run: forge coverage

Mock Contracts for Testing

test/mocks/MockERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor() ERC20("Mock Token", "MOCK") {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
    
    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

Testing Checklist

Before deploying to Paxeer Network mainnet:
  • All unit tests passing
  • Integration tests passing
  • Gas costs optimized
  • Test coverage > 90%
  • Edge cases tested
  • Events tested
  • Access control tested
  • Reentrancy protection verified
  • Integer overflow scenarios tested
  • Failed transaction scenarios handled
  • Tested with mainnet fork
  • Security review completed
  • Documentation updated

Testing Tools & Resources

Advanced Testing Patterns

Snapshot Testing

describe("State Snapshots", function () {
  let snapshotId;

  beforeEach(async function () {
    snapshotId = await ethers.provider.send("evm_snapshot");
  });

  afterEach(async function () {
    await ethers.provider.send("evm_revert", [snapshotId]);
  });

  it("Should test with clean state", async function () {
    // Each test starts with fresh state
    await contract.setValue(42);
    expect(await contract.getValue()).to.equal(42);
  });
});

Testing Reverts

describe("Revert Scenarios", function () {
  it("Should revert with message", async function () {
    await expect(
      contract.connect(user).adminOnly()
    ).to.be.revertedWith("Only admin");
  });

  it("Should revert with custom error", async function () {
    await expect(
      contract.invalidOperation()
    ).to.be.revertedWithCustomError(contract, "InvalidOperation");
  });

  it("Should revert with panic code", async function () {
    await expect(
      contract.divideByZero()
    ).to.be.revertedWithPanic(0x12); // Division by zero
  });
});

Testing Gas Usage

describe("Gas Optimization", function () {
  it("Should use less gas than limit", async function () {
    const tx = await contract.optimizedFunction();
    const receipt = await tx.wait();
    
    console.log("Gas used:", receipt.gasUsed.toString());
    expect(receipt.gasUsed).to.be.lt(100000);
  });

  it("Should compare gas between implementations", async function () {
    const tx1 = await contract.methodA();
    const receipt1 = await tx1.wait();
    
    const tx2 = await contract.methodB();
    const receipt2 = await tx2.wait();
    
    console.log("Method A gas:", receipt1.gasUsed.toString());
    console.log("Method B gas:", receipt2.gasUsed.toString());
  });
});

Load Testing

Test your contract under load:
test/Load.test.js
describe("Load Testing", function () {
  it("Should handle batch operations", async function () {
    const numOperations = 100;
    const promises = [];

    for (let i = 0; i < numOperations; i++) {
      promises.push(contract.setValue(i));
    }

    await Promise.all(promises);
    
    // Verify all operations succeeded
    expect(await contract.getValue()).to.equal(numOperations - 1);
  });

  it("Should handle large arrays", async function () {
    const largeArray = Array(1000).fill(0).map((_, i) => i);
    await contract.processBatch(largeArray);
  });
});

Security Testing

test/Reentrancy.t.sol
contract ReentrancyTest is Test {
    Vulnerable vulnerable;
    Attacker attacker;

    function setUp() public {
        vulnerable = new Vulnerable();
        attacker = new Attacker(address(vulnerable));
    }

    function testReentrancyAttack() public {
        vm.deal(address(vulnerable), 10 ether);
        vm.deal(address(attacker), 1 ether);
        
        vm.expectRevert("ReentrancyGuard: reentrant call");
        attacker.attack();
    }
}
describe("Access Control", function () {
  it("Owner can perform admin actions", async function () {
    await expect(contract.connect(owner).adminAction())
      .to.not.be.reverted;
  });

  it("Non-owner cannot perform admin actions", async function () {
    await expect(contract.connect(user).adminAction())
      .to.be.revertedWith("Ownable: caller is not the owner");
  });

  it("Should transfer ownership", async function () {
    await contract.transferOwnership(user.address);
    expect(await contract.owner()).to.equal(user.address);
  });
});
function testOverflow() public {
    uint256 max = type(uint256).max;
    
    // Should revert in 0.8+
    vm.expectRevert();
    uint256 overflow = max + 1;
}

CI/CD Integration

Automated Testing Pipeline

.github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: recursive
      
      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npx hardhat test
      
      - name: Check coverage
        run: npx hardhat coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3

Debugging Tests

Enable Verbose Logging

# Hardhat
npx hardhat test --verbose

# Foundry
forge test -vvvv

Use Console.log in Solidity

import "hardhat/console.sol";

contract Debug {
    function testFunction(uint256 x) external {
        console.log("Input value:", x);
        uint256 result = x * 2;
        console.log("Result:", result);
    }
}

Hardhat Debugging

const { ethers } = require("hardhat");

it("Should debug transaction", async function () {
  const tx = await contract.setValue(42);
  const receipt = await tx.wait();
  
  console.log("Transaction hash:", receipt.hash);
  console.log("Block number:", receipt.blockNumber);
  console.log("Gas used:", receipt.gasUsed.toString());
  console.log("Logs:", receipt.logs);
});

Resources

Next Steps