Skip to content

Testing Framework

How to use it

First, ensure you have installed hyper-evm-lib in your foundry project. Instructions here

Then, in your test file, import CoreSimulatorLib as shown in the example, and call CoreSimulatorLib.init() in the setUp().

Then, call CoreSimulatorLib.nextBlock() in your tests to simulate moving to the next block and execute any queued CoreWriter (or token bridging) actions.

Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
import {Test} from "forge-std/Test.sol";
import {CoreSimulatorLib} from "@hyper-evm-lib/test/simulation/CoreSimulatorLib.sol";
 
contract ExampleTest is Test {
 
    function setUp() public {
        vm.createSelectFork("https://rpc.hyperliquid.xyz/evm");
 
        // initialize the HyperCore simulator
        CoreSimulatorLib.init();
    }
 
    function test() public { 
 
        // Make any smart contract calls, 
        // all CoreWriter and token bridging actions will be queued
 
        ...
 
        // move to the next block, 
        // performing all queued CoreWriter and bridging actions
        CoreSimulatorLib.nextBlock();
 
        // Now, all precompiles calls will be 
        //updated to account for the above executed actions
    }
}

Alternatively, you could inherit BaseSimulatorTest for your tests, which sets up the CoreSimulatorLib in it's setUp() function

Test utility functions

These functions in CoreSimulatorLib work similar to Foundry's cheatcodes and allow you to manipulate state and configuration for testing purposes.

Test Config

// force activate a Core account (without having to transfer tokens and pay the $1 fee)
function forceAccountActivation(address account) internal;

Price Setters

// set the mark price of a perp market
function setMarkPx(uint32 perp, uint64 markPx) internal;
 
// set the mark price by specifying a price difference in bps
function setMarkPx(uint32 perp, uint64 priceDiffBps, bool isIncrease) internal;
 
// set the price of a spot market
function setSpotPx(uint32 spotMarketId, uint64 spotPx) internal;
 
// set the spot price by specifying a price difference in bps
function setSpotPx(uint32 spotMarketId, uint64 priceDiffBps, bool isIncrease) internal;
 
// scale vault equity to simulate profit/loss scenarios
function setVaultMultiplier(address vault, uint64 multiplier) internal;

Balance Setters

// set an account's spot balance for a token
function forceSpotBalance(address account, uint64 token, uint64 _wei) internal;
 
// set an account's withdrawable perp USD
function forcePerpBalance(address account, uint64 usd) internal;
 
// set an account's staking balance
function forceStakingBalance(address account, uint64 _wei) internal;
 
// set an account's delegation amount and locked until timestamp for a validator
function forceDelegation(address account, address validator, uint64 amount, uint64 lockedUntilTimestamp) internal;
 
// set an account's vault equity and lock timestamp for a vault
function forceVaultEquity(address account, address vault, uint64 usd, uint64 lockedUntilTimestamp) internal;

How it works

HyperCore replicates HyperCore state and behaviour for tests. It holds all state (accounts, spot balances, perp positions and margin, staking, vault equity) and exposes read functions used by precompiles.

Precompile calls are handled by PrecompileSim.sol, which is etched at each precompile address. When a precompile is called, its fallback forwards the read into HyperCore via the matching CoreView function. CoreView functions check if the value is already initialized in local state and returns that; if not, it performs a live RPC read via RealL1Read.sol and returns the onchain value.

CoreWriter actions are handled through CoreWriterSim.sol. It stores all incoming CoreWriter calls in a queue. When CoreSimulatorLib.nextBlock() is called, it executes the queued actions by calling the relevant execute functions on HyperCore and applies their state changes. Before applying each write, CoreExecution runs initializers to initialise the relevant state by fetching current onchain values. Then, the local state is updated, and subsequent precompile reads will use the stored state.

Check out these test contracts here for more info.