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
| Function | Action 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
| Function | Action 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
| Field | Description |
|---|---|
primaryTokens | Token addresses to assign simulated prices to. Include every token the action depends on. |
primaryPrices | Simulated oracle prices for each token, in the same order as primaryTokens. Each price uses { min, max }. |
minTimestamp | Lower bound for the simulated oracle timestamp window. |
maxTimestamp | Upper bound for the simulated oracle timestamp window. |