Skip to content

Ethereum transaction simulator leveraging Foundry's codebase

License

Notifications You must be signed in to change notification settings

QiLOL/transaction-simulator

 
 

Repository files navigation

Enso Transaction 🧐 Simulator 🧐

A simple API which simulates a given transaction request.

test

cover

📫 API 📫

POST /api/v1/simulate

Simulates a single transaction against a local EVM.

See the full request and response types below.

Example body:

{
  "chainId": 1,
  "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  "to": "0x66fc62c1748e45435b06cf8dd105b73e9855f93e",
  "data": "0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a0",
  "gasLimit": 500000,
  "value": "100000",
  "blockNumber": 16784600
}

Example response:

{
  "gasUsed": 214622,
  "blockNumber": 16784600,
  "success": true,
  "trace": { ... },
  "logs": [ ... ],
  "exitReason": "Return"
}

Notes:

  • blockNumber can be omitted and the latest block will be used, however providing a blockNumber is recommended where possible to use the cache.

POST /api/v1/simulate-bundle

Simulates a bundle of transactions in order against the same EVM.

See the full request and response types below.

Example body:

[
  {
    "chainId": 1,
    "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "to": "0x66fc62c1748e45435b06cf8dd105b73e9855f93e",
    "data": "0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a0",
    "gasLimit": 500000,
    "value": "100000",
    "blockNumber": 16784600
  }
]

Example response:

[{
  "gasUsed": 214622,
  "blockNumber": 16784600,
  "success": true,
  "trace": { ... },
  "logs": [ ... ],
  "exitReason": "Return"
}]

Notes:

  • chainId must be the same in all transactions.
  • blockNumber must be the same in all transactions, or omitted in all transactions to use latest.

Authentication

If you set an API_KEY environment variable then all calls to the API must be accompanied by a X-API-KEY header which contains this API Key.

🏃‍♂️ Running 🏃‍♂️

Locally

Copy .env.example to .env, fill out required values and run:

$ cargo run

If you want the server to restart on any code changes run:

$ cargo watch -x run

🧪 Test 🧪

Run:

$ cargo test

Manual Testing

body.json contains a simple request in the root of the project so once the API is running you can just run:

$ curl -H "Content-Type: application/json" --data @tests/body.json http://localhost:8080/api/v1/simulate

If you have jq installed, you can run this to see pretty traces:

$ curl -H "Content-Type: application/json" --data @tests/body.json http://localhost:8080/api/v1/simulate | jq -r ".formattedTrace"

🧭 Roadmap 🧭

  • Support any RPC endpoint, not just Alchemy
  • Connect to local node via IPC
  • Connect to local reth DB
  • Support simulating a bundle of transactions against different blocks, applying state as the simulation progresses. Would help support paradigmxyz/reth#2018
  • Support more authentication methods

Contributing

See CONTRIBUTING.md.

Types

export type SimulationRequest = {
  chainId: number;
  from: string;
  to: string;
  data?: string;
  gasLimit: number;
  value: string;
  blockNumber?: number; // if not specified, latest used,
  formatTrace?: boolean;
};

export type SimulationResponse = {
  simulationId: string;
  gasUsed: number;
  blockNumber: number;
  success: boolean;
  trace: CallTrace[];
  logs?: Log[];
  exitReason?: Reason;
  formattedTrace?: string;
};

export type Log = {
  topics: string[];
  data: string;
  address: string;
};

export type CallTrace = {
  callType: CallType;
  from: string;
  to: string;
  value: string;
};

export enum CallType {
  CALL,
  STATICCALL,
  CALLCODE,
  DELEGATECALL,
  CREATE,
  CREATE2,
}

export enum Reason {
  //success codes
  Continue,
  Stop,
  Return,
  SelfDestruct,

  // revert code
  Revert, // revert opcode
  CallTooDeep,
  OutOfFund,

  // error codes
  OutOfGas,
  OpcodeNotFound,
  CallNotAllowedInsideStatic,
  InvalidOpcode,
  InvalidJump,
  InvalidMemoryRange,
  NotActivated,
  StackUnderflow,
  StackOverflow,
  OutOfOffset,
  FatalExternalError,
  GasMaxFeeGreaterThanPriorityFee,
  PrevrandaoNotSet,
  GasPriceLessThenBasefee,
  CallerGasLimitMoreThenBlock,
  /// EIP-3607 Reject transactions from senders with deployed code
  RejectCallerWithCode,
  LackOfFundForGasLimit,
  CreateCollision,
  OverflowPayment,
  PrecompileError,
  NonceOverflow,
  /// Create init code exceeds limit (runtime).
  CreateContractLimit,
  /// Error on created contract that begins with EF
  CreateContractWithEF,
}

🙏 Thanks 🙏

About

Ethereum transaction simulator leveraging Foundry's codebase

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 99.3%
  • Dockerfile 0.7%