This document explains how to create and run simulation scenarios for the Elata Protocol.
Scenarios define the simulation parameters: agents, duration, metrics, and assertions. They use the defineScenario API from AgentForge.
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { SimulationEngine, createLogger, defineScenario } from 'agentforge';
import { BasicUserAgent, DeveloperAgent } from '../../agents/index.js';
import { scenarioCiMode, scenarioOutDir, scenarioSeed } from '../../lib/index.js';
import { createEltaPack } from '../../packs/EltaPack.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const protocolPath = join(__dirname, '..', '..', '..');
const pack = createEltaPack({
protocolPath,
anvilPort: 8545,
silent: true,
});
const scenario = defineScenario({
name: 'my-scenario',
seed: scenarioSeed(42),
ticks: 30,
tickSeconds: 3600,
pack,
agents: [...],
metrics: {...},
assertions: [...],
});
export { scenario };| Option | Type | Description |
|---|---|---|
name |
string |
Unique scenario identifier |
seed |
number |
RNG seed for reproducibility |
ticks |
number |
Number of simulation ticks |
tickSeconds |
number |
Simulated seconds per tick |
pack |
Pack |
Protocol pack instance |
agents: [
{
type: BasicUserAgent, // Agent class
count: 10, // Number of instances
params: { // Agent-specific parameters
buyProbability: 0.3,
sellProbability: 0.2,
},
},
]metrics: {
sampleEveryTicks: 5, // Sample interval
track: [
'app_count',
'veelta_total_locked',
'fees_collected_total',
'gas_total',
],
}| Metric | Description |
|---|---|
elta_total_supply |
Total ELTA token supply |
veelta_total_locked |
Total veELTA locked |
app_count |
Number of apps created |
graduated_apps |
Number of graduated apps |
fees_collected_total |
Total fees collected |
fees_distributed |
Total fees distributed |
gas_total |
Total gas used |
app_{id}_price |
Price of app token |
app_{id}_raised |
Amount raised for app |
app_{id}_graduated |
Graduation status (0/1) |
app_{id}_price_change_bps |
Price change in basis points |
gas_per_agent_{id} |
Gas used by agent |
gas_per_action_{type} |
Gas used by action type |
agent_{id}_realized_pnl |
Agent realized P&L |
assertions: [
{ type: 'gte', metric: 'app_count', value: 3 },
{ type: 'lte', metric: 'graduated_apps', value: 10 },
{ type: 'eq', metric: 'some_metric', value: expectedValue },
]Assertion Types:
gte: Greater than or equallte: Less than or equalgt: Greater thanlt: Less thaneq: Equal
async function main(): Promise<void> {
const logger = createLogger({ level: 'info', pretty: true });
const engine = new SimulationEngine({ logger });
const result = await engine.run(scenario, {
outDir: scenarioOutDir('./results/my-scenario'),
ci: scenarioCiMode(true),
});
console.log(`Result: ${result.success ? 'PASS' : 'FAIL'}`);
console.log(`Duration: ${result.durationMs}ms`);
// Access final metrics
console.log(result.finalMetrics);
// Access agent stats
for (const stat of result.agentStats) {
console.log(`${stat.id}: ${stat.actionsSucceeded}/${stat.actionsAttempted}`);
}
process.exit(result.failedAssertions.length > 0 ? 1 : 0);
}Add to package.json:
{
"scripts": {
"scenario:my-scenario": "tsx scenarios/my-scenario.ts"
}
}Quick validation tests (< 20 ticks):
// scenarios/smoke/my-smoke-test.ts
const scenario = defineScenario({
name: 'smoke-my-feature',
seed: 42,
ticks: 15,
tickSeconds: 3600,
// ...
});Full flow tests (20-50 ticks):
// scenarios/integration/full-flow.ts
const scenario = defineScenario({
name: 'integration-full-flow',
seed: 100,
ticks: 50,
tickSeconds: 1800,
// ...
});Economic behavior tests (30-100 ticks):
// scenarios/economic/bank-run.ts
const scenario = defineScenario({
name: 'economic-bank-run',
seed: 123,
ticks: 60,
tickSeconds: 1800,
// ...
});const pack = createEltaPack({
// Required
protocolPath: '/path/to/elata-protocol',
// Optional
anvilPort: 8545, // Anvil RPC port
initialEltaSupply: BigInt(10000000e18), // Initial ELTA supply
agentEthBalance: BigInt(1000e18), // ETH per agent
agentEltaBalance: BigInt(10000e18), // ELTA per agent
autoDeploy: true, // Auto-deploy contracts
silent: true, // Quiet Anvil output
deployScript: 'script/Deploy.sol:Deploy', // Deployment script
});// Use different seeds for different test purposes
const scenario = defineScenario({
seed: 42, // Deterministic base
seed: Date.now(), // Random variation
});// Match agent counts to tick count
agents: [
{ type: DeveloperAgent, count: Math.ceil(ticks / 20) },
{ type: BasicUserAgent, count: Math.ceil(ticks / 3) },
]// Mix risk tolerances for realistic behavior
agents: [
{ type: BasicUserAgent, count: 5, params: { riskTolerance: 0.3 } },
{ type: BasicUserAgent, count: 3, params: { riskTolerance: 0.6 } },
{ type: BasicUserAgent, count: 2, params: { riskTolerance: 0.9 } },
]// Avoid port conflicts between parallel tests
const pack = createEltaPack({
anvilPort: 8545 + scenarioIndex,
// ...
});// Log useful information
console.log('\nPrice Analysis:');
for (const [key, value] of Object.entries(result.finalMetrics)) {
if (key.includes('_price')) {
console.log(` ${key}: ${value}`);
}
}Tests normal protocol operation:
- Developers create apps
- Users trade
- Stakers lock veELTA
- Rewards are claimed
Tests concentration effects:
- Few large traders
- High-volume trading
- Impact on prices
Tests adversarial behavior:
- Manipulator agents
- Spammer agents
- System stability
Tests panic scenarios:
- Mass selling
- Price floor behavior
- Liquidity depth
See scenarios/ directory for complete examples.