Function Overrides
Force a contract function on your Stagenet to return a fixed value, without changing its code.
Use this to:
- Mock a price oracle, vault rate, or any external view function during testing.
- Force a contract into a specific branch (e.g.
paused() = true) without writing to storage. - Reproduce a production state that depends on a registry, ACL, or feed you can’t easily seed.
How it works
When you install an override, your Stagenet records it in the DB and loads it into the EVM’s call interceptor. Every call to the target function — from eth_call, an eth_sendTransaction, or an inner call from another contract — returns your fixed value before the contract’s real code runs.
The override sits at the EVM layer, so contracts that staticcall the overridden function see the override too. This is the difference vs. simply changing storage: a function that does extra work (math, lookups, packing) still returns your exact value, because the function body is skipped.
You can target the same selector with multiple overrides — one per set of input params, plus an optional wildcard:
- An override with
inputParamsonly matches calls whose ABI-encoded inputs match exactly. - An override without
inputParamsis a wildcard for every call to that selector. - When both apply, the exact-input override wins.
Methods
stagenet.addFunctionOverride(input)
Install an override.
const override = await stagenet.addFunctionOverride({
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
functionAbi: JSON.stringify({
type: "function",
name: "totalSupply",
inputs: [],
outputs: [{ type: "uint256" }],
stateMutability: "view",
}),
returnValues: ["1000000000000"], // 1M USDC, 6 decimals
});| Field | Type | Description |
|---|---|---|
contractAddress | string | Target contract |
functionAbi | string | object | JSON-encoded ABI fragment, or the fragment object |
inputParams | string[]? | Omit for a wildcard; pass values to match only that input |
returnValues | string[] | One entry per output in the fragment |
Returns the created override ({ id, contractAddress, functionAbi, inputParams, returnValues, enabled, createdAt }).
stagenet.updateFunctionOverride(id, returnValues)
Replace the return values of an existing override. contractAddress, the function selector, and inputParams are immutable — remove and re-add to change them.
await stagenet.updateFunctionOverride(override.id, ["2000000000000"]);Returns the updated override.
stagenet.removeFunctionOverride(id)
Delete an override.
await stagenet.removeFunctionOverride(override.id);Returns { id, removed: true }.
stagenet.enableFunctionOverride(id) / stagenet.disableFunctionOverride(id)
Toggle an override without deleting it. A disabled override stays in the DB but is not applied — calls go through to the real contract.
await stagenet.disableFunctionOverride(override.id);
// ... later ...
await stagenet.enableFunctionOverride(override.id);Returns the updated override.
stagenet.getFunctionOverrides()
List every override on the Stagenet, most recent first.
const overrides = await stagenet.getFunctionOverrides();Returns FunctionOverride[].
stagenet.getFunctionOverridesByContract(contractAddress)
List overrides for a single contract.
const overrides = await stagenet.getFunctionOverridesByContract(usdc);Returns FunctionOverride[].
Usage
Mock a price feed
Pin a Chainlink-style latestAnswer() to a fixed value so the rest of your protocol can be exercised against a known price:
import { createStagenet } from "contract.dev";
const stagenet = createStagenet("<YOUR_STAGENET_RPC_URL>");
const ethUsdFeed = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419";
await stagenet.addFunctionOverride({
contractAddress: ethUsdFeed,
functionAbi: "function latestAnswer() view returns (int256)",
returnValues: ["300000000000"], // $3,000 with 8 decimals
});Per-account balanceOf
Give a single address a fake USDC balance while every other account stays at its real value:
const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const target = "0x1111111111111111111111111111111111111111";
await stagenet.addFunctionOverride({
contractAddress: usdc,
functionAbi: "function balanceOf(address) view returns (uint256)",
inputParams: [target],
returnValues: ["1000000000000"],
});Toggle during a test run
const paused = await stagenet.addFunctionOverride({
contractAddress: vault,
functionAbi: "function paused() view returns (bool)",
returnValues: ["true"],
});
// run "happy path" with paused=true...
await stagenet.disableFunctionOverride(paused.id);
// run "happy path" with the contract's real paused state...When to use storage instead
Overrides intercept reads at the function boundary. If you need state changes that other functions observe (e.g. a balance that downstream transfer() calls should decrement), set storage directly with setStorageAt or set token balances with setERC20Balance instead.