Skip to main content

Overview

The ank-engine module coordinates protocol execution, per-user portfolios, optional gas/fee policies, and per-tick lifecycle. It provides the main orchestration layer for running backtests and simulations.

Engine

pub struct Engine {
    pub protocols: IndexMap<String, Box<dyn Protocol>>,
    pub portfolios: IndexMap<UserId, Balances>,
    pub ts: Timestamp,
    pub step_idx: u64,
    pub metrics: EngineMetrics,
}
Orchestrates protocols, user portfolios, and optional fee policy.
protocols
IndexMap<String, Box<dyn Protocol>>
Registered protocol instances (by id)
portfolios
IndexMap<UserId, Balances>
User wallets (token → amount e18)
ts
Timestamp
Current timestamp
step_idx
u64
Current tick index
metrics
EngineMetrics
Metrics counters

Methods

new

pub fn new(protocols: IndexMap<String, Box<dyn Protocol>>, start_ts: Timestamp) -> Self
Create a new engine starting at start_ts.
protocols
IndexMap<String, Box<dyn Protocol>>
required
Map of protocol id to protocol instance
start_ts
Timestamp
required
Initial simulation timestamp
return
Engine
New engine instance

set_fees

pub fn set_fees(&mut self, f: Option<FeesConfig>)
Set or clear the fee configuration. Only available when the fees feature is enabled.
f
Option<FeesConfig>
required
Fee configuration, or None to disable fees

balances_mut

pub fn balances_mut(&mut self, user: UserId) -> &mut Balances
Mutable access to a user’s balances (creates an empty wallet if missing).
user
UserId
required
User identifier
return
&mut Balances
Mutable reference to user’s balances

balances

pub fn balances(&self, user: UserId) -> Balances
Cloned snapshot of a user’s balances.
user
UserId
required
User identifier
return
Balances
Cloned balances (empty if user not found)

balances_ref

pub fn balances_ref(&self, user: UserId) -> Option<&Balances>
Immutable reference to a user’s balances.
user
UserId
required
User identifier
return
Option<&Balances>
Reference to user’s balances, or None if not found

tick

pub fn tick<F>(&mut self, user: UserId, plan: F) -> Result<Vec<Vec<ExecOutcome>>>
where
    F: FnMut(EngineCtx, &IndexMap<String, Box<dyn Protocol>>, &IndexMap<UserId, Balances>) -> Vec<TxBundle>
Execute one planning pass: call plan, then execute the produced bundles for user. The engine first calls on_tick for all protocols, then invokes the planner callback to get bundles, and finally executes them.
user
UserId
required
User identifier executing actions
plan
F
required
Planner callback that produces transaction bundles based on current context
return
Result<Vec<Vec<ExecOutcome>>>
Execution outcomes for each bundle

tick_with_bundles

pub fn tick_with_bundles(
    &mut self,
    user: UserId,
    bundles: Vec<TxBundle>,
) -> Result<Vec<Vec<ExecOutcome>>>
Convenience: run exactly the provided bundles for one user this tick.
user
UserId
required
User identifier executing actions
bundles
Vec<TxBundle>
required
Pre-built transaction bundles to execute
return
Result<Vec<Vec<ExecOutcome>>>
Execution outcomes for each bundle

tick_multi

pub fn tick_multi(
    &mut self,
    plans: Vec<(UserId, Vec<TxBundle>)>,
) -> Result<Vec<Vec<ExecOutcome>>>
Execute bundles for multiple users in one global tick (shared timestamp).
plans
Vec<(UserId, Vec<TxBundle>)>
required
List of (user, bundles) pairs to execute in this tick
return
Result<Vec<Vec<ExecOutcome>>>
All execution outcomes

Supporting Types

EngineCtx

pub struct EngineCtx {
    pub ts: Timestamp,
    pub step_idx: u64,
}
Execution context passed to planners.
ts
Timestamp
Simulation timestamp (e.g., block index)
step_idx
u64
Monotonic tick counter

EngineMetrics

pub struct EngineMetrics {
    pub steps: u64,
    pub bundles_submitted: u64,
    pub txs_executed: u64,
    pub txs_rejected_gas: u64,
}
High-level counters for observability.
steps
u64
Number of ticks processed
bundles_submitted
u64
Total bundles submitted
txs_executed
u64
Total txs executed successfully
txs_rejected_gas
u64
Txs rejected at precharge (fees shortfall)

Fee Configuration (requires fees feature)

FeesConfig

pub struct FeesConfig {
    pub gas_token: TokenId,
    pub price: GasPricePolicy,
    pub on_shortfall: OnShortfall,
}
Full fee configuration.
gas_token
TokenId
required
The token used to pay gas (e.g., native coin)
price
GasPricePolicy
required
Gas price policy (gwei)
on_shortfall
OnShortfall
required
Behavior when funds are insufficient

GasPricePolicy

pub enum GasPricePolicy {
    FixedGwei(u64),
    Callback(Arc<dyn Fn(Timestamp) -> u64 + Send + Sync>),
}
How the engine obtains gas price (in gwei).
FixedGwei
u64
Fixed gas price in gwei (1e9 wei)
Callback
Arc<dyn Fn(Timestamp) -> u64 + Send + Sync>
Callback: compute gas price (in gwei) for the given timestamp

OnShortfall

pub enum OnShortfall {
    RejectTx,
    AllowDebt,
}
What to do if the user cannot pay fees.
RejectTx
variant
Reject the tx before execution (only in precharge path)
AllowDebt
variant
Allow negative balance on the gas token

Usage Example

use ank_engine::{Engine, EngineCtx};
use ank_exec::TxBundle;
use indexmap::IndexMap;

let protocols = IndexMap::new();
let mut engine = Engine::new(protocols, 0);

// Set initial balances
let user = 1u64;
engine.balances_mut(user).set(TokenId(0), 1_000_000_000_000_000_000u128);

// Execute a tick with a planner
let outcomes = engine.tick(user, |ctx, protocols, portfolios| {
    // Your planning logic here
    vec![TxBundle::default()]
})?;

Build docs developers (and LLMs) love