diff --git a/offchain/src/compiler/ast.rs b/offchain/src/compiler/ast.rs index a8929d7..a8ca27b 100644 --- a/offchain/src/compiler/ast.rs +++ b/offchain/src/compiler/ast.rs @@ -1,375 +1,39 @@ -// pub trait Push3Ast { -// /// Convert this AST into a bytecode array (Push3 tokens). -// fn to_bytecode(&self) -> Vec; +//! # ast.rs +//! This module defines the **Abstract Syntax Trees (ASTs)** for our Push3-like language. +//! +//! Currently, we provide only an **untyped AST** (`UntypedAst`), which allows any sequence +//! of instructions, sublists, or integer literals. This is easy to parse and mutate, but +//! **does not** enforce correctness of operand types (for example, `INTEGER_PLUS` might +//! appear even if there's only one integer on the stack). +//! +//! ## Future: Typed AST +//! We **plan** to add a **typed AST** implementation, where each node ensures the correct +//! number and type of arguments at compile-time. This would prevent invalid programs from +//! being constructed in the first place. However, that is not implemented here yet—it’s +//! an **eventual goal** for more advanced genetic programming or verification workflows. -// // Parse an AST from bytecode (the "decompiler"), if possible. -// // Possibly returns a Result in case of partial/invalid parse. -// // fn from_bytecode(data: &[u8]) -> Self -// // where -// // Self: Sized; - -// // Randomly mutate this AST in-place (GP mutation). -// // fn mutate(&mut self, rng: &mut impl rand::Rng); - -// // Possibly a "crossover" method, or we do it externally. -// // fn crossover(&self, other: &Self, rng: &mut impl rand::Rng) -> Self; - -// // Evaluate the AST with some input(s). -// // Optionally do we want this or do we always run in EVM? -// // We can stub it or do an interpret approach for local test. -// // fn interpret_locally(&self, inputs: &[i32]) -> Vec; -// } - - -// #[derive(Debug, Clone)] -// pub enum TypedAst { -// IntLiteral(i32), -// Instruction(InstructionKind), -// } - -// #[derive(Debug, Clone)] -// pub enum InstructionKind { -// Plus(Box, Box), -// Minus(Box, Box), -// Mult(Box, Box), -// Dup, // 0 children -// Pop, // 0 children -// } - -// impl Push3Ast for TypedAst { -// fn to_bytecode(&self) -> Vec { -// // do typed encoding: -// // - for Plus(e1, e2): e1 -> bytecode, e2 -> bytecode, then PLUS opcode -// unimplemented!() -// } - -// // fn from_bytecode(data: &[u8]) -> Self { -// // // parse in a strictly typed manner; might fail or panic -// // // if the code doesn't line up properly -// // unimplemented!() -// // } - -// // fn mutate(&mut self, rng: &mut impl rand::Rng) { -// // // e.g. randomly pick a node, replace it with something -// // // that respects the typed structure -// // unimplemented!() -// // } - -// // fn interpret_locally(&self, inputs: &[i32]) -> Vec { -// // // do a normal expression evaluation. If a node is "Plus(a,b)", -// // // interpret (a) + interpret(b). -// // unimplemented!() -// // } -// } - -// #[derive(Debug, Clone)] -// pub enum UntypedAst { -// /// A single token in a list-based approach, e.g. "Integer literal" -// IntLiteral(i32), - -// /// "Instruction" might be a simple variant with no children, -// /// because the stack-based evaluation decides how many arguments it consumes. -// Instruction(OpCode), - -// /// A sublist of tokens, e.g. "( 2 3 DUP + )" -// Sublist(Vec), -// } - -// /// Possibly an enum for OpCode: -// #[derive(Debug, Clone)] -// pub enum OpCode { -// Noop, -// Plus, -// Minus, -// Mult, -// Dup, -// Pop, -// // maybe Unknown => treat as no-op -// } - -// // impl Push3Ast for UntypedAst { -// // fn to_bytecode(&self) -> Vec { -// // // flatten out sublists vs instructions -// // unimplemented!() -// // } - -// // // fn from_bytecode(data: &[u8]) -> Self { -// // // // parse "0x02 => INT_LITERAL => read next 4 bytes" -// // // // instructions might have no subchildren -// // // unimplemented!() -// // // } - -// // // fn mutate(&mut self, rng: &mut impl rand::Rng) { -// // // // can randomly insert unknown tokens, reorder sublists, etc. -// // // // possibly produce "invalid ops => noops" -// // // unimplemented!() -// // // } - -// // // fn interpret_locally(&self, inputs: &[i32]) -> Vec { -// // // // push-based interpretation: -// // // // sublist => interpret each item in sequence -// // // // if an instruction sees insufficient args => no-op -// // // unimplemented!() -// // // } -// // } - -// use crate::compiler::ast::Push3Ast; - -// /// An untyped AST node, matching your Push3 structure. -// #[derive(Debug, Clone)] -// pub enum UntypedAst { -// /// A single integer literal. -// IntLiteral(i32), -// /// A single push instruction (no subchildren). -// Instruction(OpCode), -// /// A sublist of items, each of which is another UntypedAst. -// Sublist(Vec), -// } - -// /// Your Push3 opcodes in an untyped form. -// #[derive(Debug, Clone)] -// pub enum OpCode { -// Noop, -// Plus, -// Minus, -// Mult, -// Dup, -// Pop, -// } - -// /// Implementation of `Push3Ast` for `UntypedAst`. -// /// -// /// This will produce **bytecode** compatible with your Solidity `Push3Interpreter`: -// /// -// /// - `0x00` => NOOP -// /// - `0x01` => INTEGER_PLUS -// /// - `0x02` => INT_LITERAL => next 4 bytes (little-endian i32) -// /// - `0x03` => SUBLIST => next 2 bytes (big-endian `u16` length), then the sublist contents -// /// - `0x04` => INTEGER_MINUS -// /// - `0x05` => INTEGER_MULT -// /// - `0x06` => INTEGER_DUP -// /// - `0x07` => INTEGER_POP -// /// -// /// In the Solidity parser, `SUBLIST (0x03)` consumes two bytes to determine -// /// how many bytes of tokens to read in that sublist. -// impl Push3Ast for UntypedAst { -// /// Convert this untyped AST into a flattened push3 bytecode sequence. -// fn to_bytecode(&self) -> Vec { -// match self { -// UntypedAst::IntLiteral(val) => { -// // 0x02 followed by 4 bytes of i32 (little-endian) -// let mut bytes = Vec::with_capacity(1 + 4); -// bytes.push(0x02); -// bytes.extend_from_slice(&val.to_le_bytes()); -// bytes -// } -// UntypedAst::Instruction(op) => { -// // Single opcode byte -// let opcode_byte = match op { -// OpCode::Noop => 0x00, -// OpCode::Plus => 0x01, -// OpCode::Minus => 0x04, -// OpCode::Mult => 0x05, -// OpCode::Dup => 0x06, -// OpCode::Pop => 0x07, -// }; -// vec![opcode_byte] -// } -// UntypedAst::Sublist(children) => { -// // 0x03 => SUBLIST -// // Then 2 bytes for length (big-endian u16), -// // Then the concatenated bytecode for all children. -// let mut payload = Vec::new(); -// for child in children { -// let child_bytes = child.to_bytecode(); -// payload.extend_from_slice(&child_bytes); -// } -// let sub_len = payload.len() as u16; // assume it fits in 65535 -// let mut bytes = Vec::with_capacity(1 + 2 + payload.len()); -// bytes.push(0x03); -// // Store the sublist length as big-endian, because readUint16 in solidity -// // does `val = uint16(word >> 240)` (effectively big-endian). -// bytes.extend_from_slice(&sub_len.to_be_bytes()); -// // Then the actual sublist payload -// bytes.extend_from_slice(&payload); -// bytes -// } -// } -// } -// } - - -// #[derive(Debug, Clone, PartialEq)] -// pub enum SExpr { -// Atom(String), // e.g. "3", "DUP", "+" -// List(Vec), // e.g. (3 5 +) -// } - -// fn tokenize(s: &str) -> Vec { -// let mut tokens = Vec::new(); -// let mut current = String::new(); - -// for c in s.chars() { -// match c { -// '(' | ')' => { -// // If we were building up an atom, push it first -// if !current.trim().is_empty() { -// tokens.push(current.trim().to_string()); -// } -// current.clear(); -// // Then push the single-char token -// tokens.push(c.to_string()); -// } -// ' ' | '\t' | '\n' | '\r' => { -// // Whitespace => if we have an atom, finish it -// if !current.trim().is_empty() { -// tokens.push(current.trim().to_string()); -// current.clear(); -// } -// } -// _ => { -// // Part of an atom -// current.push(c); -// } -// } -// } - -// // If something is left in 'current' at the end -// if !current.trim().is_empty() { -// tokens.push(current.trim().to_string()); -// } - -// tokens -// } - - -// fn parse_sexpr(tokens: &[String], pos: &mut usize) -> Result { -// // If we’re out of tokens, error -// if *pos >= tokens.len() { -// return Err("Unexpected end of tokens".into()); -// } - -// let token = &tokens[*pos]; -// match token.as_str() { -// "(" => { -// // Parse a list -// *pos += 1; // consume '(' -// let mut list_items = Vec::new(); - -// while *pos < tokens.len() && tokens[*pos] != ")" { -// let expr = parse_sexpr(tokens, pos)?; -// list_items.push(expr); -// } - -// if *pos >= tokens.len() { -// return Err("Missing closing parenthesis".into()); -// } -// // consume ")" -// *pos += 1; -// Ok(SExpr::List(list_items)) -// } -// ")" => { -// // Should never parse_sexpr starting with ')' -// Err("Unexpected ')'".into()) -// } -// _ => { -// // Atom -// let atom_str = token.clone(); -// *pos += 1; -// Ok(SExpr::Atom(atom_str)) -// } -// } -// } - -// /// Parse an entire string into a single top-level `SExpr`. -// fn parse_string_to_sexpr(s: &str) -> Result { -// let tokens = tokenize(s); -// let mut pos = 0; -// let expr = parse_sexpr(&tokens, &mut pos)?; - -// // If there's leftover tokens, optionally check -// if pos < tokens.len() { -// return Err(format!("Extra tokens after parse: {:?}", &tokens[pos..])); -// } - -// Ok(expr) -// } - -// fn sexpr_to_untyped(expr: &SExpr) -> Result { -// match expr { -// SExpr::Atom(text) => { -// // 1) try to parse as i32 -// if let Ok(val) = text.parse::() { -// Ok(UntypedAst::IntLiteral(val)) -// } else { -// // 2) otherwise parse as OpCode symbol -// match text.to_uppercase().as_str() { -// "+" => Ok(UntypedAst::Instruction(OpCode::Plus)), -// "-" => Ok(UntypedAst::Instruction(OpCode::Minus)), -// "*" => Ok(UntypedAst::Instruction(OpCode::Mult)), -// "DUP" => Ok(UntypedAst::Instruction(OpCode::Dup)), -// "POP" => Ok(UntypedAst::Instruction(OpCode::Pop)), -// // more opcodes ... -// _ => Ok(UntypedAst::Instruction(OpCode::Noop)) -// } -// } -// } -// SExpr::List(list) => { -// // parse each child into UntypedAst -// let mut result = Vec::new(); -// for child in list { -// result.push(sexpr_to_untyped(child)?); -// } -// Ok(UntypedAst::Sublist(result)) -// } -// } -// } - -// src/compiler/ast.rs +use serde::{Deserialize, Serialize}; +// use std::fmt; +/// A trait describing the core operation: converting an AST to Push3 bytecode. +/// +/// We might later add more methods here (like parsing from bytecode or mutation). pub trait Push3Ast { - /// Convert this AST into a bytecode array (Push3 tokens). fn to_bytecode(&self) -> Vec; } -/* -------------------------------------------- - 1) TypedAst stubs (optional) --------------------------------------------- */ - -#[derive(Debug, Clone)] -pub enum TypedAst { - IntLiteral(i32), - Instruction(InstructionKind), -} - -#[derive(Debug, Clone)] -pub enum InstructionKind { - Plus(Box, Box), - Minus(Box, Box), - Mult(Box, Box), - Dup, - Pop, -} - -// Example stub; not fully implemented -impl Push3Ast for TypedAst { - fn to_bytecode(&self) -> Vec { - unimplemented!("TypedAst::to_bytecode not yet implemented"); - } -} - -/* -------------------------------------------- - 2) UntypedAst + OpCode --------------------------------------------- */ - -#[derive(Debug, Clone)] +/// The untyped AST: each node is either an integer literal, a single instruction (no children), +/// or a sublist (which recursively contains more AST nodes). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum UntypedAst { IntLiteral(i32), Instruction(OpCode), Sublist(Vec), } -#[derive(Debug, Clone)] +/// The set of push3 instructions recognized in our untyped AST. +/// Each corresponds to a single token byte in `to_bytecode`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum OpCode { Noop, Plus, @@ -379,39 +43,54 @@ pub enum OpCode { Pop, } -/// Implementation of `Push3Ast` for `UntypedAst`. -/// Matches the Solidity-based Push3Interpreter token IDs. +/// Implementation of `Push3Ast` for the **untyped** AST. +/// +/// This produces bytecode recognized by the Push3Interpreter contract: +/// +/// - `0x00` => NOOP +/// - `0x01` => INTEGER_PLUS +/// - `0x02` => INT_LITERAL => next 4 bytes (little-endian i32) +/// - `0x03` => SUBLIST => next 2 bytes (big-endian `u16` length), then the sublist contents +/// - `0x04` => INTEGER_MINUS +/// - `0x05` => INTEGER_MULT +/// - `0x06` => INTEGER_DUP +/// - `0x07` => INTEGER_POP impl Push3Ast for UntypedAst { fn to_bytecode(&self) -> Vec { match self { UntypedAst::IntLiteral(val) => { - // 0x02 => INT_LITERAL => next 4 bytes (little-endian i32) + // 0x02 => INT_LITERAL => then 4 bytes little-endian let mut bytes = Vec::with_capacity(1 + 4); bytes.push(0x02); bytes.extend_from_slice(&val.to_be_bytes()); bytes } UntypedAst::Instruction(op) => { - let opcode_byte = match op { - OpCode::Noop => 0x00, - OpCode::Plus => 0x01, + // Single byte representing the opcode + let byte = match op { + OpCode::Noop => 0x00, + OpCode::Plus => 0x01, OpCode::Minus => 0x04, - OpCode::Mult => 0x05, - OpCode::Dup => 0x06, - OpCode::Pop => 0x07, + OpCode::Mult => 0x05, + OpCode::Dup => 0x06, + OpCode::Pop => 0x07, }; - vec![opcode_byte] + vec![byte] } UntypedAst::Sublist(children) => { - // 0x03 => SUBLIST => next 2 bytes = big-endian length, then sub-payload + // 0x03 => SUBLIST + // Then 2 bytes for the length (big-endian `u16`), + // Then the concatenated child bytecode. let mut payload = Vec::new(); for child in children { - payload.extend(child.to_bytecode()); + let child_bytes = child.to_bytecode(); + payload.extend_from_slice(&child_bytes); } + let sub_len = payload.len() as u16; let mut bytes = Vec::with_capacity(1 + 2 + payload.len()); bytes.push(0x03); - bytes.extend_from_slice(&sub_len.to_be_bytes()); // big-endian + bytes.extend_from_slice(&sub_len.to_be_bytes()); // big-endian length bytes.extend_from_slice(&payload); bytes } @@ -419,15 +98,18 @@ impl Push3Ast for UntypedAst { } } -/* -------------------------------------------- - 3) S-expression parsing helpers --------------------------------------------- */ +// ---------------------------------------------------------------------------- +// S-Expression Parsing Helpers +// ---------------------------------------------------------------------------- + +/// A simple S-expression tree for textual representation: either an `Atom` or a `List`. #[derive(Debug, Clone, PartialEq)] pub enum SExpr { Atom(String), List(Vec), } +/// Tokenize a string into parentheses and symbols (atoms). pub fn tokenize(s: &str) -> Vec { let mut tokens = Vec::new(); let mut current = String::new(); @@ -460,9 +142,10 @@ pub fn tokenize(s: &str) -> Vec { tokens } +/// Recursive helper to parse a single S-expression from a list of tokens, advancing `pos`. fn parse_sexpr_internal(tokens: &[String], pos: &mut usize) -> Result { if *pos >= tokens.len() { - return Err("Unexpected end of tokens".into()); + return Err("Unexpected end of tokens".to_string()); } let token = &tokens[*pos]; @@ -475,47 +158,52 @@ fn parse_sexpr_internal(tokens: &[String], pos: &mut usize) -> Result= tokens.len() { - return Err("Missing closing parenthesis".into()); + return Err("Missing closing parenthesis".to_string()); } *pos += 1; // consume ')' Ok(SExpr::List(items)) } - ")" => { - Err("Unexpected ')'".into()) - } + ")" => Err("Unexpected ')'".to_string()), _ => { - let atom = token.clone(); + let atom_str = token.clone(); *pos += 1; - Ok(SExpr::Atom(atom)) + Ok(SExpr::Atom(atom_str)) } } } -/// Parse an entire string into a single top-level `SExpr`. +/// Parse a full string into a single top-level `SExpr`, assuming balanced parentheses. pub fn parse_string_to_sexpr(s: &str) -> Result { let tokens = tokenize(s); let mut pos = 0; let expr = parse_sexpr_internal(&tokens, &mut pos)?; if pos < tokens.len() { - return Err(format!("Extra tokens after parse: {:?}", &tokens[pos..])); + Err(format!( + "Extra tokens after parse: {:?}", + &tokens[pos..] + )) + } else { + Ok(expr) } - Ok(expr) } -/// Convert an SExpr => UntypedAst +/// Convert an S-expression to an **untyped** AST node. pub fn sexpr_to_untyped(expr: &SExpr) -> Result { match expr { SExpr::Atom(text) => { + // 1) Try parse as integer if let Ok(val) = text.parse::() { Ok(UntypedAst::IntLiteral(val)) } else { + // 2) Otherwise interpret as an opcode match text.to_uppercase().as_str() { - "+" => Ok(UntypedAst::Instruction(OpCode::Plus)), - "-" => Ok(UntypedAst::Instruction(OpCode::Minus)), - "*" => Ok(UntypedAst::Instruction(OpCode::Mult)), + "+" => Ok(UntypedAst::Instruction(OpCode::Plus)), + "-" => Ok(UntypedAst::Instruction(OpCode::Minus)), + "*" => Ok(UntypedAst::Instruction(OpCode::Mult)), "DUP" => Ok(UntypedAst::Instruction(OpCode::Dup)), "POP" => Ok(UntypedAst::Instruction(OpCode::Pop)), - _ => Ok(UntypedAst::Instruction(OpCode::Noop)), + // unknown => treat as Noop + _ => Ok(UntypedAst::Instruction(OpCode::Noop)), } } } @@ -528,3 +216,31 @@ pub fn sexpr_to_untyped(expr: &SExpr) -> Result { } } } + +// ---------------------------------------------------------------------------- +// (Optional) Placeholder for a future typed AST +// ---------------------------------------------------------------------------- + +// /// In the future, we plan to define a `TypedAst` enum that ensures each operation +// /// has the correct number of typed children. We might also handle errors +// /// if an operation expects two integer children, but the user tries to attach +// /// a sublist or zero children, etc. +// /// +// /// For now, the untyped AST suffices for quick prototyping, but typed checks +// /// will be crucial in more advanced genetic programming or static analysis scenarios. +// #[derive(Debug, Clone)] +// pub enum TypedAst { +// // e.g. IntLiteral(i32), +// // Instruction(InstructionKind), +// } + +// #[derive(Debug, Clone)] +// pub enum InstructionKind { +// // e.g. Plus(Box, Box), +// // Minus(Box, Box), +// // Mult(Box, Box), +// // Dup, +// // Pop, +// } + +// // etc. diff --git a/offchain/src/main.rs b/offchain/src/main.rs index 5a473d0..d931b73 100644 --- a/offchain/src/main.rs +++ b/offchain/src/main.rs @@ -1,3 +1,5 @@ +// src/main.rs + mod compiler; use std::env; @@ -21,29 +23,36 @@ use revm::{ }; use ethers::abi::{encode, Token}; use ethers::abi::{ParamType, decode}; -use ethers::types::U256; // Use ethers' U256 instead +use ethers::types::U256; +use serde::Deserialize; // <--- NEW: for deserializing from JSON +#[derive(Debug, Deserialize)] +struct BytecodeObject { + object: String, +} +#[derive(Debug, Deserialize)] +struct MyContractArtifact { + bytecode: BytecodeObject, +} fn main() -> Result<()> { - // 1) Read CLI arguments let args: Vec = std::env::args().collect(); if args.len() < 2 { eprintln!("Usage: cargo run -- ''"); eprintln!("Example: cargo run -- '((3 5 +) DUP MUL)'"); - // If you want to indicate a failure exit code: std::process::exit(1); } let program_str = &args[1]; // 2) Parse the string into an S-expression let sexpr = parse_string_to_sexpr(program_str) - .map_err(|e| anyhow::anyhow!("Error parsing S-expression: {}", e))?; + .map_err(|e| anyhow!("Error parsing S-expression: {}", e))?; // 3) Convert SExpr => UntypedAst let ast: UntypedAst = sexpr_to_untyped(&sexpr) - .map_err(|e| anyhow::anyhow!("Error converting to UntypedAst: {}", e))?; + .map_err(|e| anyhow!("Error converting to UntypedAst: {}", e))?; // 4) Convert UntypedAst => Push3 bytecode let bytecode = ast.to_bytecode(); @@ -51,16 +60,35 @@ fn main() -> Result<()> { // 5) Print the resulting AST and bytecode (in hex) println!("AST: {:?}", ast); println!("Bytecode (hex): 0x{}", hex::encode(&bytecode)); + + // ---------------------------------------------------------------------- + // NEW: Read the contract creation code (bytecode) from a JSON artifact + // ---------------------------------------------------------------------- + // Example JSON (Forge artifact) might look like: + // { + // "bytecode": "60808060...0033" <--- entire creation code + // ... other fields ... + // } + + // Path to your JSON artifact. Adjust as needed: + let creation_hex_filename = "../onchain/out/Push3Interpreter.sol/Push3Interpreter.json"; + + // Read and parse the JSON artifact + let creation_json = std::fs::read_to_string(creation_hex_filename) + .map_err(|e| anyhow!("Failed to read JSON file {}: {}", creation_hex_filename, e))?; + let contract_artifact: MyContractArtifact = serde_json::from_str(&creation_json) + .map_err(|e| anyhow!("Failed to parse JSON artifact: {}", e))?; - // 1) Suppose you have compiled your contract with Forge. - // Grab the "bytecode" field from e.g. `out/MyContract.sol/MyContract.json`. - // We'll store it here as a string. This is your entire "creation code." - // (Example placeholder below.) - let creation_hex = "6080806040523461001657610cd8908161001b8239f35b5f80fdfe6080604052600480361015610012575f80fd5b5f803560e01c918263093b62c0146100395750506336d9ea8514610034575f80fd5b6101ce565b346101045760803660031901126101045780359167ffffffffffffffff918284116101045736602385011215610104578381013590838211610100573660248387010111610100576024358481116100fc576100989036908301610108565b9290916044358681116100f8576100b29036908301610108565b9690956064359182116100f5576100f16100e260248b8b8b8b8b8b6100d9368c8e01610108565b97909601610480565b60409391935193849384610170565b0390f35b80fd5b8580fd5b8380fd5b8280fd5b5080fd5b9181601f840112156101395782359167ffffffffffffffff8311610139576020808501948460051b01011161013957565b5f80fd5b9081518082526020808093019301915f5b82811061015c575050505090565b83518552938101939281019260010161014e565b906101866101959160608452606084019061013d565b6020938382038585015261013d565b9060408183039101528180845192838152019301915f5b8281106101ba575050505090565b8351855293810193928101926001016101ac565b346101395760803660031901126101395760043560048110156101395763ffffffff60243581811681036101395760443591821682036101395760643590600160b81b82101561024c576020936040519363ffffffff60b81b9060b81b169163ffffffff60d81b9060d81b169060ff60f81b9060f81b161717178152f35b60405162461bcd60e51b81526020600482015260136024820152726c6f77313834206f7574206f662072616e676560681b6044820152606490fd5b634e487b7160e01b5f52602160045260245ffd5b600411156102a557565b610287565b600160b81b81101561024c57600160f91b1790565b634e487b7160e01b5f52601160045260245ffd5b9061010082018092116102e257565b6102bf565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff81116103135760051b60200190565b6102e7565b90610322826102fb565b60405190601f1990601f018116820167ffffffffffffffff8111838210176103135760405283825261035482946102fb565b0190602036910137565b5f1981146102e25760010190565b634e487b7160e01b5f52603260045260245ffd5b91908110156103905760051b0190565b61036c565b80518210156103905760209160051b010190565b5f198101919082116102e257565b6001198101919082116102e257565b919082039182116102e257565b80156102e2575f190190565b90600263ffffffff809316019182116102e257565b90600463ffffffff809316019182116102e257565b91909163ffffffff808094169116019182116102e257565b600611156102a557565b81810292915f8212600160ff1b8214166102e25781840514901517156102e257565b81810392915f1380158285131691841216176102e257565b9190915f83820193841291129080158216911516176102e257565b9794929197969396610499610494846102d3565b610318565b985f5b8481106108ac5750506104b1610494856102d3565b97845f5b81811061088c57505050836104cc610494876102d3565b97865f5b818110610866575050505b6105855750506104ea81610318565b965f5b8281106105675750505061050081610318565b945f5b8281106105495750505061051681610318565b925f5b82811061052557505050565b806105336105449284610395565b5161053e8288610395565b5261035e565b610519565b806105576105629284610395565b5161053e828a610395565b610503565b806105756105809284610395565b5161053e828c610395565b6104ed565b6105a161059a610594866103a9565b8a610395565b51946103d3565b93846105af8260f81c6108c5565b916105b98361029b565b600192808403610777575060ff6105d091166108d4565b916105da83610421565b826105e8575b5090506104db565b6105f183610421565b82810361065c57505050600285101561060e575b835b805f6105e0565b6106558561064b61062861062288996103a9565b8b610395565b5161064561063e610638856103b7565b8d610395565b51936103b7565b92610465565b61053e828b610395565b9450610605565b61066583610421565b6002928084036106b157505050851015610680575b83610607565b6106aa8561064b61069461062288996103a9565b516106a461063e610638856103b7565b9261044d565b945061067a565b6106ba81610421565b600381036106fc575050508510156106d25783610607565b6106aa8561064b6106e661062288996103a9565b516106f661063e610638856103b7565b9261042b565b9091925061070981610421565b6004810361073d575090508510156107215783610607565b6106aa8561073261059487986103a9565b5161053e828b610395565b80610749600592610421565b14610755575b50610607565b9050851015610766575b835f61074f565b61077084956103a9565b945061075f565b909291506107848161029b565b600281036107ad575050849563ffffffff6107a7921660030b61053e828b610395565b946104db565b916003836107bb899561029b565b0361085f5763ffffffff908582808360d81c169260b81c16926107de8484610409565b1611156107ee575b5050506104db565b829350906107fd918686610904565b5f925b61080c575b86926107e6565b9091958a825180891015610857578261084f9261053e879561084261083c8e610837610849986103a9565b6103c6565b89610395565b5192610395565b9761035e565b929190610800565b505095610805565b50506104db565b82935061087881836108839495610380565b3561053e828d610395565b908692916104d0565b808b61053e826108a06108a7958789610380565b3592610395565b6104b5565b808b61053e826108a06108c0958a88610380565b61049c565b60ff1660048110156102a55790565b60ff1660068110156102a55790565b90821015610390570190565b63ffffffff8091169081146102e25760010190565b919063ffffffff92610917848616610318565b95846109245f9786610409565b16955b85851687811015610bbd5784811015610bbd5761096a61096461095e61095060ff9489896108e3565b356001600160f81b03191690565b60f81c90565b966108ef565b95168061098d575061098590600160f81b61053e828b610395565b955b95610927565b600181036109b257506109ac906001600160f81b0161053e828b610395565b95610987565b60028103610a495750866109d16109c8876103f4565b63ffffffff1690565b111580610a34575b15610a01576109ac9061064b876109fa6109f4898989610bca565b986103f4565b97166102aa565b945094505050505b610a1281610318565b925f5b828110610a2157505050565b80610533610a2f9284610395565b610a15565b5083610a426109c8876103f4565b11156109d9565b9096949060038103610b2c575084610a636109c8836103df565b111580610b17575b15610b0b57610a84610a7e828686610c36565b916103df565b9661ffff82169186610a996109c8858c610409565b111580610af5575b15610ae757610ae19291610adb9160d88b901b63ffffffff60d81b1660b89190911b61ffff60b81b1617600360f81b1761053e828d610395565b97610409565b93610987565b509550509450505050610a09565b5085610b046109c8858c610409565b1115610aa1565b50949350505050610a09565b5083610b256109c8836103df565b1115610a6b565b9496909460048103610b4f57506109ac906002600160f81b0161053e828b610395565b60058103610b6e57506109ac906003600160f81b0161053e828b610395565b60068103610b8d57506109ac906004600160f81b0161053e828b610395565b600703610baa576109ac906005600160f81b0161053e828b610395565b6109ac90600160f81b61053e828b610395565b5094509450505050610a09565b919063ffffffff610bda836103f4565b1611610bf157600490604051920182375160e01c90565b60405162461bcd60e51b815260206004820152601760248201527f7265616455696e743332206f7574206f662072616e67650000000000000000006044820152606490fd5b919063ffffffff610c46836103df565b1611610c5d57600290604051920182375160f01c90565b60405162461bcd60e51b815260206004820152601760248201527f7265616455696e743136206f7574206f662072616e67650000000000000000006044820152606490fdfea26469706673582212208a8e0c1517585160ff23b605caf6916a05dae2b541b9387b0dfab23ea71f563964736f6c63430008140033"; - // ^^^^^ Replace with your actual creation code from forge. + // Extract the "bytecode" field from the JSON + let raw_hex = &contract_artifact.bytecode.object; + let stripped_hex = raw_hex.trim_start_matches("0x"); + + // ---------------------------------------------------------------------- + // The rest of the deployment+call logic remains mostly unchanged + // ---------------------------------------------------------------------- // 2) Convert hex string to raw bytes. - let creation_bytes = match hex::decode(creation_hex) { + let creation_bytes = match hex::decode(stripped_hex) { Ok(bytes) => bytes, Err(e) => bail!("Invalid hex for creation code: {e}"), }; @@ -71,8 +99,7 @@ fn main() -> Result<()> { Context::builder() .modify_tx_chained(|tx| { tx.transact_to = TxKind::Create; - tx.data = Bytes::from(creation_bytes.clone()); - // If you want to attach a value, e.g. tx.value = + tx.data = Bytes::from(creation_bytes.clone()); }) // b) Provide an in-memory DB for ephemeral state .with_db(CacheDB::::default()), @@ -83,7 +110,7 @@ fn main() -> Result<()> { println!("Deploying contract with creation code size: {} bytes", creation_bytes.len()); // 4) Execute the CREATE transaction - let creation_result = evm.exec_commit()?; + let creation_result = evm.exec_commit()?; // => an ExecutionResult // 5) Check success @@ -96,52 +123,49 @@ fn main() -> Result<()> { }; println!("Deployed contract at: 0x{:x}", deployed_addr); - + // 1) Prepare the function selector for runInterpreter let func_selector = ðers::utils::id("runInterpreter(bytes,uint256[],uint256[],int256[])")[0..4]; - + // 2) Convert your compiled “Push3” bytecode into the `bytes` argument let code_arg = Token::Bytes(bytecode.clone()); - + // 3) Build a “SUBLIST descriptor” for the entire bytecode. - // In Solidity, SUBLIST = CodeTag.SUBLIST = 3. The layout is: - // [255..248]=tag, [247..216]=offset, [215..184]=length, [183..0]=low184 let tag_bits = U256::from(3u64) << 248; // CodeTag.SUBLIST = 3 let offset_bits = U256::from(0u64) << 216; // offset=0 let length_bits = U256::from(bytecode.len() as u64) << 184; // length = bytecode.len() - let low_184 = U256::zero(); - + let low_184 = U256::zero(); + let sublist_descriptor = tag_bits | offset_bits | length_bits | low_184; - // Now we put *that* descriptor as the one and only item in `initExecStack`. + // Now we put that descriptor as the one and only item in `initExecStack`. let init_exec_stack = Token::Array(vec![ Token::Uint(sublist_descriptor) ]); - + // For the other two arrays (codeStack, intStack), we’ll just leave them empty: let empty_uint256_array = Token::Array(vec![]); // codeStack let empty_int256_array = Token::Array(vec![]); // intStack // 4) Encode all arguments in the correct order: - // (bytes code, uint256[] initCodeStack, uint256[] initExecStack, int256[] initIntStack) let encoded_args = encode(&[ code_arg, empty_uint256_array.clone(), // initCodeStack init_exec_stack, // initExecStack - empty_uint256_array.clone(), // initIntStack (or empty_int256_array if needed) + empty_uint256_array.clone(), // initIntStack ]); - + // 5) Concatenate selector + encoded arguments to form the full call data let mut call_data = Vec::from(func_selector); call_data.extend_from_slice(&encoded_args); - + // 6) Modify the transaction to call `runInterpreter` with the encoded data evm.context.modify_tx(|tx| { tx.transact_to = TxKind::Call(deployed_addr); tx.data = Bytes::from(call_data); tx.nonce += 1; // increment nonce to avoid reuse }); - + // 7) Execute the call. let call_result = evm.transact()?; match &call_result.result { @@ -150,10 +174,8 @@ fn main() -> Result<()> { .. } => { println!("Call succeeded, returned data: 0x{}", hex::encode(return_data)); - + // Decode (uint256[], uint256[], int256[]) - use ethers::abi::{decode, ParamType}; - let param_types = &[ ParamType::Array(Box::new(ParamType::Uint(256))), // finalCodeStack ParamType::Array(Box::new(ParamType::Uint(256))), // finalExecStack @@ -171,6 +193,6 @@ fn main() -> Result<()> { other => { bail!("Call failed: {other:?}"); } - } + } Ok(()) }