use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum ExecutionPhase {
Validation,
Execution,
Settlement,
}
impl ExecutionPhase {
pub fn as_str(&self) -> &'static str {
match self {
Self::Validation => "validation",
Self::Execution => "execution",
Self::Settlement => "settlement",
}
}
}
impl std::str::FromStr for ExecutionPhase {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"validation" => Ok(Self::Validation),
"execution" => Ok(Self::Execution),
"settlement" => Ok(Self::Settlement),
_ => Err(()),
}
}
}
impl std::fmt::Display for ExecutionPhase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum AALayer {
Bundler,
Account,
Paymaster,
Protocol,
EntryPoint,
}
impl AALayer {
pub fn as_str(&self) -> &'static str {
match self {
Self::Bundler => "bundler",
Self::Account => "account",
Self::Paymaster => "paymaster",
Self::Protocol => "protocol",
Self::EntryPoint => "entrypoint",
}
}
}
impl std::str::FromStr for AALayer {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bundler" => Ok(Self::Bundler),
"account" => Ok(Self::Account),
"paymaster" => Ok(Self::Paymaster),
"protocol" => Ok(Self::Protocol),
"entrypoint" => Ok(Self::EntryPoint),
_ => Err(()),
}
}
}
impl std::fmt::Display for AALayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AAContext {
pub current_phase: Option<ExecutionPhase>,
pub layer_state: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
pub phase_snapshots: BTreeMap<String, BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
pub user_op: Option<UserOpData>,
pub account_state: Option<AccountState>,
pub paymaster_state: Option<PaymasterState>,
pub entry_point_state: Option<EntryPointState>,
}
impl AAContext {
pub fn set_phase(&mut self, phase: ExecutionPhase) {
self.current_phase = Some(phase);
}
pub fn get_phase(&self) -> Option<ExecutionPhase> {
self.current_phase
}
pub fn in_phase(&self, phase: ExecutionPhase) -> bool {
self.current_phase == Some(phase)
}
pub fn snapshot_phase(&mut self, phase: ExecutionPhase) {
self.phase_snapshots
.insert(phase.to_string(), self.layer_state.clone());
}
pub fn get_phase_snapshot(
&self,
phase: ExecutionPhase,
) -> Option<&BTreeMap<String, BTreeMap<String, serde_json::Value>>> {
self.phase_snapshots.get(phase.as_str())
}
pub fn get_layer_var(&self, layer: &str, var: &str) -> Option<&serde_json::Value> {
self.layer_state.get(layer)?.get(var)
}
pub fn set_layer_var(&mut self, layer: String, var: String, value: serde_json::Value) {
self.layer_state
.entry(layer)
.or_default()
.insert(var, value);
}
pub fn get_layer_var_at_phase(
&self,
phase: ExecutionPhase,
layer: &str,
var: &str,
) -> Option<&serde_json::Value> {
self.phase_snapshots
.get(phase.as_str())?
.get(layer)?
.get(var)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserOpData {
pub sender: String,
pub nonce: u128,
pub init_code: Vec<u8>,
pub call_data: Vec<u8>,
pub call_gas_limit: u128,
pub verification_gas_limit: u128,
pub pre_op_gas: u128,
pub max_gas_price: u128,
pub max_priority_fee_per_gas: u128,
pub paymaster_and_data: Vec<u8>,
pub signature: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountState {
pub nonce: u128,
pub balance: u128,
pub expected_signer: String,
pub signature_valid: bool,
pub reentrancy_locked: bool,
pub execution_failed: bool,
pub state_hash_before: String,
pub state_hash_after: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymasterState {
pub address: String,
pub deposit: u128,
pub nonce: u128,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntryPointState {
pub address: String,
pub block_number: u128,
pub block_timestamp: u128,
pub authenticated_caller: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossLayerCheckResult {
pub invariant_name: String,
pub layers_involved: Vec<String>,
pub holds: bool,
pub failure_reason: Option<String>,
pub variables_used: BTreeMap<String, serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_execution_phase_from_str() {
assert_eq!(
ExecutionPhase::from_str("validation"),
Ok(ExecutionPhase::Validation)
);
assert_eq!(
ExecutionPhase::from_str("execution"),
Ok(ExecutionPhase::Execution)
);
assert_eq!(
ExecutionPhase::from_str("settlement"),
Ok(ExecutionPhase::Settlement)
);
assert_eq!(ExecutionPhase::from_str("invalid"), Err(()));
}
#[test]
fn test_aa_layer_from_str() {
assert_eq!(AALayer::from_str("bundler"), Ok(AALayer::Bundler));
assert_eq!(AALayer::from_str("account"), Ok(AALayer::Account));
assert_eq!(AALayer::from_str("paymaster"), Ok(AALayer::Paymaster));
assert_eq!(AALayer::from_str("protocol"), Ok(AALayer::Protocol));
assert_eq!(AALayer::from_str("invalid"), Err(()));
}
#[test]
fn test_aa_context_layer_vars() {
let mut ctx = AAContext::default();
ctx.set_layer_var(
"bundler".to_string(),
"nonce".to_string(),
serde_json::json!(42),
);
let value = ctx.get_layer_var("bundler", "nonce");
assert_eq!(value, Some(&serde_json::json!(42)));
}
#[test]
fn test_phase_tracking() {
let mut ctx = AAContext::default();
assert_eq!(ctx.get_phase(), None);
ctx.set_phase(ExecutionPhase::Validation);
assert!(ctx.in_phase(ExecutionPhase::Validation));
assert!(!ctx.in_phase(ExecutionPhase::Execution));
ctx.set_layer_var(
"account".to_string(),
"balance".to_string(),
serde_json::json!(1000),
);
ctx.snapshot_phase(ExecutionPhase::Validation);
ctx.set_phase(ExecutionPhase::Execution);
ctx.set_layer_var(
"account".to_string(),
"balance".to_string(),
serde_json::json!(500),
);
let pre_exec_balance =
ctx.get_layer_var_at_phase(ExecutionPhase::Validation, "account", "balance");
assert_eq!(pre_exec_balance, Some(&serde_json::json!(1000)));
}
}