Skip to main content

Testing Contracts Locally

Local testing gives you fast iteration and deterministic reproducibility. But local EVM emulators cannot fully model OXN's confidentiality behavior. This page explains what works locally, what doesn't, and how to structure your test pyramid.

What local networks can test

Standard Solidity contract logic — everything that would run on Ethereum unchanged — tests fine locally:

  • Contract state transitions
  • Access control (using msg.sender)
  • Event emission (topic and data structure)
  • Reverts and error conditions
  • Interactions between multiple contracts
  • Gas usage patterns (approximately)

What local networks CANNOT test

The following behaviors depend on OXN's runtime, not the EVM alone:

  • Encrypted calldata handling — a local network sees calldata in plain, so any assertion about "did this call arrive encrypted?" is meaningless.
  • Confidential storage — local storage is public; a test that checks "an outsider cannot read this slot" gives a false positive against every local network.
  • Signed queries — the signed-query path requires the OXN gateway. Locally, eth_call is unauthenticated by default.
  • Confidential precompilesSapphire.randomBytes, Sapphire.encrypt, etc. do not exist on standard Hardhat network.
  • PUSH0 rejection — local networks (Hardhat, Anvil) implement PUSH0 by default. A contract compiled with shanghai will run fine locally and only fail on real OXN.
LayerLocationCoverage
Unit testsHardhat / Foundry localBusiness logic, math, access control, revert conditions.
Integration testsOXN TestnetConfidentiality behavior, encrypted calldata, signed queries.
End-to-end testsOXN Testnet + real walletWallet interaction, faucet flow, user-facing paths.

Every dApp should run at least some integration tests against real OXN. Don't assume Hardhat green means production green.

Hardhat local testing

Standard Hardhat testing works out of the box:

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

describe("MyToken", function () {
it("mints initial supply to deployer", async function () {
const [deployer] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(ethers.parseUnits("1000", 18));

expect(await token.balanceOf(deployer.address))
.to.equal(ethers.parseUnits("1000", 18));
});
});

Run with npx hardhat test. Hardhat spins up an in-memory network, deploys, and asserts.

Foundry local testing

test/MyToken.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

contract MyTokenTest is Test {
MyToken token;
address deployer = address(this);

function setUp() public {
token = new MyToken(1_000_000 ether);
}

function testInitialSupply() public {
assertEq(token.balanceOf(deployer), 1_000_000 ether);
}
}

Run with forge test. Foundry's built-in Test contract provides assertion helpers and cheatcodes.

Fixtures and snapshots

For faster tests, use fixtures to avoid redeploying:

Hardhat with loadFixture
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

async function deployTokenFixture() {
const [deployer, other] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(ethers.parseUnits("1000", 18));
return { token, deployer, other };
}

describe("MyToken transfers", function () {
it("transfers tokens", async function () {
const { token, deployer, other } = await loadFixture(deployTokenFixture);
await token.transfer(other.address, ethers.parseUnits("100", 18));
expect(await token.balanceOf(other.address))
.to.equal(ethers.parseUnits("100", 18));
});
});

Fixtures snapshot the network state after setUp and restore it before each test, making a full test suite 10-100x faster than redeploying.

Time and block manipulation

Hardhat and Foundry both expose "cheatcodes" to move time forward or set specific block conditions in local tests. Use these for time-locked contracts:

Hardhat time cheatcodes
const { time } = require("@nomicfoundation/hardhat-network-helpers");

// Fast-forward 1 hour
await time.increase(3600);

// Jump to a specific timestamp
await time.increaseTo(1700000000);
Foundry time cheatcodes
vm.warp(block.timestamp + 3600); // fast-forward 1 hour
vm.roll(block.number + 100); // advance 100 blocks

These cheatcodes are local-only. Real OXN does not accept time manipulation from clients.

Next steps