Skip to main content

Simulations

The SimulationRouter and GlvRouter contracts expose public simulation functions that let you dry-run an existing request's execution against supplied prices after request creation and before keeper execution. Running a simulation catches validation errors — such as price impact limits or insufficient output amounts — without spending gas on a failed execution.

For the full CreateOrderParams and CreateDepositParams structures, see ExchangeRouter. For contract addresses, see Contract addresses.

How simulations work

Simulation functions use the withSimulatedOraclePrices modifier in OracleModule. The modifier injects synthetic price data into the oracle, executes the handler logic through the router/controller path, then unconditionally reverts with EndOfOracleSimulation. Because the transaction always reverts, no state changes are persisted.

The caller interprets the revert reason: if the error is EndOfOracleSimulation, the simulation succeeded and the supplied prices passed validation. Any other revert reason indicates an error that would also occur on-chain. In practice, this is a preflight tool for users and integrators; actual request execution is still performed by keeper-only handler functions.

The five simulateExecuteLatest... functions for deposits, withdrawals, shifts, orders, and JIT orders are exposed by SimulationRouter. Create the request through ExchangeRouter first, wait for that transaction to be included, then run the matching simulation function on SimulationRouter as an eth_call (for example, with ethers callStatic). Do not include the simulation call in the ExchangeRouter.multicall.

Example (TypeScript / ethers):

const createTx = await exchangeRouter.multicall(
[
exchangeRouter.interface.encodeFunctionData("sendWnt", [depositVault.address, executionFee]),
exchangeRouter.interface.encodeFunctionData("sendTokens", [
usdc.address,
depositVault.address,
shortTokenAmount,
]),
exchangeRouter.interface.encodeFunctionData("createDeposit", [depositParams]),
],
{ value: executionFee }
);
await createTx.wait();
const currentTimestamp = (await provider.getBlock("latest")).timestamp;

await expect(
simulationRouter.callStatic.simulateExecuteLatestDeposit({
primaryTokens: [wnt.address, usdc.address],
primaryPrices: [
{ min: wethPrice, max: wethPrice },
{ min: usdcPrice, max: usdcPrice },
],
minTimestamp: currentTimestamp,
maxTimestamp: currentTimestamp,
})
).to.be.revertedWithCustomError(errorsContract, "EndOfOracleSimulation");

If the call reverts with EndOfOracleSimulation, the simulated execution passed. If it reverts with any other error, treat that as the actual validation error for the supplied prices.

Available simulation functions

The latest-request helpers use the current global request key. Call the simulation promptly after the request-creation transaction is included. If another request advances the global nonce first, the helper can target that newer request instead.

SimulationRouter functions

FunctionAction type
simulateExecuteLatestDeposit(SimulatePricesParams)GM deposit
simulateExecuteLatestWithdrawal(SimulatePricesParams, SwapPricingType)GM withdrawal
simulateExecuteLatestShift(SimulatePricesParams)GM pool shift
simulateExecuteLatestOrder(SimulatePricesParams)Perpetual or swap order
simulateExecuteLatestJitOrder(GlvShiftUtils.CreateGlvShiftParams[], SimulatePricesParams)JIT order

GlvRouter functions

FunctionAction type
simulateExecuteGlvDeposit(bytes32, SimulatePricesParams)GLV deposit by explicit key
simulateExecuteLatestGlvDeposit(SimulatePricesParams)Latest GLV deposit
simulateExecuteGlvWithdrawal(bytes32, SimulatePricesParams)GLV withdrawal by explicit key
simulateExecuteLatestGlvWithdrawal(SimulatePricesParams)Latest GLV withdrawal

SimulatePricesParams

struct SimulatePricesParams {
address[] primaryTokens; // token addresses to price
Price.Props[] primaryPrices; // { min, max } price for each token
uint256 minTimestamp; // lower bound of the price window
uint256 maxTimestamp; // upper bound of the price window
}

Supply current oracle prices for all tokens involved in the action. The minTimestamp and maxTimestamp fields must bracket the expected execution timestamp; a common pattern is to set both to block.timestamp + 120.

Field notes

FieldDescription
primaryTokensToken addresses to assign simulated prices to. Include every token the action depends on.
primaryPricesSimulated oracle prices for each token, in the same order as primaryTokens. Each price uses { min, max }.
minTimestampLower bound for the simulated oracle timestamp window.
maxTimestampUpper bound for the simulated oracle timestamp window.