From ebd2dadaeeaa927f4721cbc5edf535c418a11cb8 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 16:10:33 +0400 Subject: [PATCH 01/12] evm: fix SAR implementation --- src/evm/vm.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 6758975..42c6f55 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -350,12 +350,10 @@ where op::SAR => self.bop(op, |_, s0, _, s1| { ( 3, - if s0 < VAL_256 { - s1 >> s0 - } else if s1.bit(U256::BITS - 1) { - U256::MAX + if s0 >= VAL_256 { + if s1.bit(U256::BITS - 1) { U256::MAX } else { U256::ZERO } } else { - U256::ZERO + I256::from_raw(s1).asr(s0.to()).into_raw() }, ) }), @@ -830,4 +828,52 @@ mod tests { assert_eq!(r, expected); } } + + #[test] + fn test_sar() { + // SAR(shift, value): s0=shift (top), s1=value; arithmetic (sign-extending) right shift. + // In the test array: (shift, op::SAR, value, expected). + // push_uint(rhs=value) first (→ s1), then push_uint(lhs=shift) (→ s0). + let mut vm = Vm::new(&[], &DummyCallData {}); + let neg1 = I256::unchecked_from(-1_i32).into_raw(); // 0xFF..FF + + let cases: &[(U256, op::OpCode, U256, U256)] = &[ + // Positive value: arithmetic shift == logical shift + (U256::from(1u32), op::SAR, U256::from(4u32), U256::from(2u32)), + // Shift by 0: identity + (U256::ZERO, op::SAR, U256::from(0x42u32), U256::from(0x42u32)), + // -1 >> 1 == -1 (sign extends; old logical-shift gave 0x7FFF..FF) + (U256::from(1u32), op::SAR, neg1, neg1), + // -1 >> 255 == -1 + (U256::from(255u32), op::SAR, neg1, neg1), + // I256::MIN >> 1 == 0xC000..00 (old code gave 0x4000..00) + ( + U256::from(1u32), + op::SAR, + I256::MIN.into_raw(), + uint!(0xC000000000000000000000000000000000000000000000000000000000000000_U256), + ), + // I256::MIN >> 255 == -1 + (U256::from(255u32), op::SAR, I256::MIN.into_raw(), neg1), + // I256::MAX >> 255 == 0 (positive, so no sign extension) + (U256::from(255u32), op::SAR, I256::MAX.into_raw(), U256::ZERO), + // shift >= 256, negative value → U256::MAX + (U256::from(256u32), op::SAR, neg1, neg1), + // shift >= 256, positive value → 0 + (U256::from(256u32), op::SAR, U256::from(1u32), U256::ZERO), + // shift = U256::MAX, negative → U256::MAX + (U256::MAX, op::SAR, neg1, neg1), + ]; + + for (shift, op, value, expected) in cases.iter().copied() { + vm.stack.push_uint(value); + vm.stack.push_uint(shift); + assert!(vm.exec_opcode(op).is_ok()); + let r = vm.stack.pop_uint().unwrap(); + assert_eq!( + r, expected, + "SAR(shift={shift}, value={value:#x}): got {r:#x}, want {expected:#x}" + ); + } + } } From 0b00082e3a7db75909f6547ba89696191f6fef4c Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 16:15:37 +0400 Subject: [PATCH 02/12] move calldataimpl from storage to evm/ --- src/evm/calldata.rs | 220 ++++++++++++++++++++++++++++++++++++- src/storage/calldata.rs | 235 ---------------------------------------- src/storage/mod.rs | 5 +- 3 files changed, 219 insertions(+), 241 deletions(-) delete mode 100644 src/storage/calldata.rs diff --git a/src/evm/calldata.rs b/src/evm/calldata.rs index 4ca8b1d..6ee37ca 100644 --- a/src/evm/calldata.rs +++ b/src/evm/calldata.rs @@ -1,6 +1,6 @@ -use super::{U256, element::Element}; +use super::{U256, VAL_131072, element::Element}; use crate::DynSolType; -use std::error; +use std::{collections::BTreeMap, error, marker::PhantomData}; pub trait CallData { fn load32(&self, offset: U256) -> Element; @@ -27,3 +27,219 @@ pub enum CallDataLabelType { pub trait CallDataLabel: Sized { fn label(n: usize, tp: &DynSolType, label_type: CallDataLabelType) -> Option; } + +/// ABI-aware calldata implementation. Encodes argument types and values into offset-based maps so the EVM can load labeled elements from calldata. +#[derive(Debug)] +pub struct CallDataImpl { + pub selector: [u8; 4], + arg_types: BTreeMap, + arg_vals: BTreeMap, + _phantom: PhantomData, +} + +impl CallDataImpl { + pub fn new(selector: [u8; 4], arguments: &[DynSolType]) -> Self { + let (_, types, vals) = encode(arguments); + Self { + selector, + arg_types: BTreeMap::from_iter(types), + arg_vals: BTreeMap::from_iter(vals), + _phantom: PhantomData, + } + } +} + +impl CallData for CallDataImpl { + fn load32(&self, offset: U256) -> Element { + let mut data = [0; 32]; + let mut label = None; + + if let Ok(off) = usize::try_from(offset) { + if off < 4 { + data[..4 - off].copy_from_slice(&self.selector[off..]); + } else { + let xoff = off - 4; + if let Some(val) = self.arg_vals.get(&xoff) { + data = U256::from(*val).to_be_bytes(); + } + if let Some((tp, label_type)) = self.arg_types.get(&xoff) { + label = T::label(xoff, tp, *label_type); + } + } + } + Element { data, label } + } + + fn load( + &self, + offset: U256, + size: U256, + ) -> Result<(Vec, Option), Box> { + let mut data = vec![0; u8::try_from(size)? as usize]; // max len limited to max_u8 + let mut label = None; + + if let Ok(off) = usize::try_from(offset) { + if off < 4 { + let nlen = std::cmp::min(data.len(), 4 - off); + data[..nlen].copy_from_slice(&self.selector[off..off + nlen]); + } else { + let xoff = off - 4; + if let Some(val) = self.arg_vals.get(&xoff) { + //TODO: look to the left to find proper element + data = U256::from(*val).to_be_bytes_vec(); + } + if let Some((tp, label_type)) = self.arg_types.get(&xoff) { + label = T::label(xoff, tp, *label_type); + } + } + } + + Ok((data, label)) + } + + fn selector(&self) -> [u8; 4] { + self.selector + } + + fn len(&self) -> U256 { + VAL_131072 + } +} + +fn is_dynamic(ty: &DynSolType) -> bool { + match ty { + DynSolType::Bool + | DynSolType::Int(_) + | DynSolType::Uint(_) + | DynSolType::Address + | DynSolType::FixedBytes(_) => false, + DynSolType::FixedArray(val, _) => is_dynamic(val), + DynSolType::Bytes | DynSolType::String | DynSolType::Array(_) => true, + DynSolType::Tuple(val) => val.iter().any(is_dynamic), + _ => unreachable!("Unexpected type {:?}", ty), + } +} + +type ArgTypes = Vec<(usize, (DynSolType, CallDataLabelType))>; +type ArgNonZero = Vec<(usize, usize)>; + +fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { + // (offset, type) + let mut ret_types: ArgTypes = Vec::with_capacity(elements.len()); + + // (offset, value) + let mut ret_nonzero: ArgNonZero = Vec::with_capacity(elements.len()); + + let mut off = 0; + + // (offset, type) + let mut dynamic: Vec<(usize, &DynSolType)> = Vec::with_capacity(elements.len() / 2); + + for ty in elements.iter() { + if is_dynamic(ty) { + dynamic.push((off, ty)); + off += 32; + } else { + match ty { + DynSolType::FixedArray(val, sz) => { + for _ in 0..*sz { + ret_types.push((off, (*val.clone(), CallDataLabelType::RealValue))); + off += 32; + } + } + DynSolType::Tuple(val) => { + for v in val { + ret_types.push((off, (v.clone(), CallDataLabelType::RealValue))); + off += 32; + } + } + _ => { + ret_types.push((off, (ty.clone(), CallDataLabelType::RealValue))); + off += 32; + } + } + } + } + + for (el_off, ty) in dynamic.into_iter() { + ret_nonzero.push((el_off, off)); + ret_types.push((el_off, (ty.clone(), CallDataLabelType::Offset))); + + match ty { + DynSolType::Bytes | DynSolType::String => { + // string '0x41' with len = 1 + ret_nonzero.push((off, 32)); + ret_nonzero.push((off + 32, 0x41)); // TODO: pad right, not left + ret_types.push((off, (ty.clone(), CallDataLabelType::DynLen))); + ret_types.push((off + 32, (ty.clone(), CallDataLabelType::RealValue))); + off += 64; + } + DynSolType::Array(val) => { + // len = 1 + ret_nonzero.push((off, 1)); + off += 32; + + let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); + ret_types.extend(dyn_ret_types.into_iter().map(|(o, v)| (o + off, v))); + ret_nonzero.extend(dyn_ret_nonzero.into_iter().map(|(o, v)| (o + off, v))); + off += dyn_off; + } + DynSolType::Tuple(val) => { + let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(val); + ret_types.extend(dyn_ret_types.into_iter().map(|(o, v)| (o + off, v))); + ret_nonzero.extend(dyn_ret_nonzero.into_iter().map(|(o, v)| (o + off, v))); + off += dyn_off; + } + DynSolType::FixedArray(val, sz) => { + let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); + let data_start = 32 * sz; + for i in 0..*sz { + ret_nonzero.push((off, data_start + i * (dyn_off - 32))); + off += 32; + } + for _ in 0..*sz { + ret_types.extend(dyn_ret_types.iter().map(|(o, v)| (o + off - 32, v.clone()))); + ret_nonzero.extend( + dyn_ret_nonzero + .iter() + .skip(1) + .map(|(o, v)| (o + off - 32, *v)), + ); + off += dyn_off - 32; + } + } + _ => panic!("Unexpected type {ty:?}"), + } + } + + (off, ret_types, ret_nonzero) +} + +// #[cfg(test)] +// mod test { +// use super::encode; +// use std::collections::BTreeMap; +// +// #[test] +// fn test_encode() { +// let x = vec![ +// "string[3]".parse().unwrap(), +// // "(string)[3]".parse().unwrap(), +// // "(uint8, string)[3]".parse().unwrap(), +// ]; +// let (end_off, a, b) = encode(&x); +// println!("{}", end_off); +// println!("{:?}", a); +// println!("{:?}", b); +// +// let ma = BTreeMap::from_iter(a); +// let mb = BTreeMap::from_iter(b); +// +// for off in (0..end_off).step_by(32) { +// println!("{:064x} - {:?}", +// mb.get(&off).unwrap_or(&0), +// ma.get(&off), +// ); +// } +// } +// } diff --git a/src/storage/calldata.rs b/src/storage/calldata.rs deleted file mode 100644 index 8ea5672..0000000 --- a/src/storage/calldata.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::{collections::BTreeMap, marker::PhantomData}; - -use crate::{ - DynSolType, - evm::{ - U256, VAL_131072, - calldata::{CallData, CallDataLabel, CallDataLabelType}, - element::Element, - }, -}; -use std::error; - -#[derive(Debug)] -pub struct CallDataImpl { - pub selector: [u8; 4], - arg_types: BTreeMap, - arg_vals: BTreeMap, - - _phantom: std::marker::PhantomData, -} - -impl CallDataImpl { - pub fn new(selector: [u8; 4], arguments: &[DynSolType]) -> Self { - let (_, types, vals) = encode(arguments); - Self { - selector, - arg_types: BTreeMap::from_iter(types), - arg_vals: BTreeMap::from_iter(vals), - _phantom: PhantomData, - } - } -} - -impl CallData for CallDataImpl { - fn load32(&self, offset: U256) -> Element { - let mut data = [0; 32]; - let mut label = None; - - if let Ok(off) = usize::try_from(offset) { - if off < 4 { - data[..4 - off].copy_from_slice(&self.selector[off..]); - } else { - let xoff = off - 4; - if let Some(val) = self.arg_vals.get(&xoff) { - data = U256::from(*val).to_be_bytes(); - } - if let Some((tp, label_type)) = self.arg_types.get(&xoff) { - label = T::label(xoff, tp, *label_type); - } - } - } - Element { data, label } - } - - fn load( - &self, - offset: U256, - size: U256, - ) -> Result<(Vec, Option), Box> { - let mut data = vec![0; u8::try_from(size)? as usize]; // max len limited to max_u8 - let mut label = None; - - if let Ok(off) = usize::try_from(offset) { - if off < 4 { - let nlen = std::cmp::min(data.len(), 4 - off); - data[..nlen].copy_from_slice(&self.selector[off..off + nlen]); - } else { - let xoff = off - 4; - if let Some(val) = self.arg_vals.get(&xoff) { - //TODO: look to the left to find proper element - data = U256::from(*val).to_be_bytes_vec(); - } - if let Some((tp, label_type)) = self.arg_types.get(&xoff) { - label = T::label(xoff, tp, *label_type); - } - } - } - - Ok((data, label)) - } - - fn selector(&self) -> [u8; 4] { - self.selector - } - - fn len(&self) -> U256 { - VAL_131072 - } -} - -fn is_dynamic(ty: &DynSolType) -> bool { - match ty { - DynSolType::Bool - | DynSolType::Int(_) - | DynSolType::Uint(_) - | DynSolType::Address - | DynSolType::FixedBytes(_) => false, - DynSolType::FixedArray(val, _) => is_dynamic(val), - DynSolType::Bytes | DynSolType::String | DynSolType::Array(_) => true, - DynSolType::Tuple(val) => val.iter().any(is_dynamic), - _ => unreachable!("Unexpected type {:?}", ty), - } -} - -type ArgTypes = Vec<(usize, (DynSolType, CallDataLabelType))>; -type ArgNonZero = Vec<(usize, usize)>; - -fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { - // (offset, type) - let mut ret_types: ArgTypes = Vec::with_capacity(elements.len()); - - // (offset, value) - let mut ret_nonzero: ArgNonZero = Vec::with_capacity(elements.len()); - - let mut off = 0; - - // (offset, type) - let mut dynamic: Vec<(usize, &DynSolType)> = Vec::with_capacity(elements.len() / 2); - - for ty in elements.iter() { - if is_dynamic(ty) { - dynamic.push((off, ty)); - off += 32; - } else { - match ty { - DynSolType::FixedArray(val, sz) => { - for _ in 0..*sz { - ret_types.push((off, (*val.clone(), CallDataLabelType::RealValue))); - off += 32; - } - } - DynSolType::Tuple(val) => { - for v in val { - ret_types.push((off, (v.clone(), CallDataLabelType::RealValue))); - off += 32; - } - } - - _ => { - ret_types.push((off, (ty.clone(), CallDataLabelType::RealValue))); - off += 32; - } - } - } - } - - for (el_off, ty) in dynamic.into_iter() { - ret_nonzero.push((el_off, off)); - - ret_types.push((el_off, (ty.clone(), CallDataLabelType::Offset))); - - match ty { - DynSolType::Bytes | DynSolType::String => { - // string '0x41' with len = 1 - ret_nonzero.push((off, 32)); - ret_nonzero.push((off + 32, 0x41)); // TODO: padd right, not left - - ret_types.push((off, (ty.clone(), CallDataLabelType::DynLen))); // strlen - ret_types.push((off + 32, (ty.clone(), CallDataLabelType::RealValue))); - off += 64; - } - DynSolType::Array(val) => { - // len = 1 - ret_nonzero.push((off, 1)); - off += 32; - - let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); - - ret_types.extend(dyn_ret_types.into_iter().map(|(o, v)| (o + off, v))); - ret_nonzero.extend(dyn_ret_nonzero.into_iter().map(|(o, v)| (o + off, v))); - off += dyn_off; - } - DynSolType::Tuple(val) => { - let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(val); - - ret_types.extend(dyn_ret_types.into_iter().map(|(o, v)| (o + off, v))); - ret_nonzero.extend(dyn_ret_nonzero.into_iter().map(|(o, v)| (o + off, v))); - off += dyn_off; - } - - DynSolType::FixedArray(val, sz) => { - let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); - - let data_start = 32 * sz; - for i in 0..*sz { - ret_nonzero.push((off, data_start + i * (dyn_off - 32))); - off += 32; - } - - for _ in 0..*sz { - ret_types.extend(dyn_ret_types.iter().map(|(o, v)| (o + off - 32, v.clone()))); - ret_nonzero.extend( - dyn_ret_nonzero - .iter() - .skip(1) - .map(|(o, v)| (o + off - 32, *v)), - ); - off += dyn_off - 32; - } - } - _ => panic!("Unexpected type {ty:?}"), - } - } - - (off, ret_types, ret_nonzero) -} - -// #[cfg(test)] -// mod test { -// use super::encode; -// use std::collections::BTreeMap; -// -// #[test] -// fn test_encode() { -// let x = vec![ -// "string[3]".parse().unwrap(), -// // "(string)[3]".parse().unwrap(), -// // "(uint8, string)[3]".parse().unwrap(), -// ]; -// let (end_off, a, b) = encode(&x); -// println!("{}", end_off); -// println!("{:?}", a); -// println!("{:?}", b); -// -// let ma = BTreeMap::from_iter(a); -// let mb = BTreeMap::from_iter(b); -// -// for off in (0..end_off).step_by(32) { -// println!("{:064x} - {:?}", -// mb.get(&off).unwrap_or(&0), -// ma.get(&off), -// ); -// } -// } -// } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 53ed98b..1cedf78 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -6,7 +6,7 @@ use crate::{ collections::HashMap, evm::{ U256, VAL_1, VAL_1_B, VAL_32_B, - calldata::{CallDataLabel, CallDataLabelType}, + calldata::{CallDataImpl, CallDataLabel, CallDataLabelType}, element::Element, op, vm::{StepResult, Vm}, @@ -19,9 +19,6 @@ use std::{ rc::Rc, }; -pub(crate) mod calldata; -use calldata::CallDataImpl; - mod keccak_precalc; use keccak_precalc::KEC_PRECALC; From bce4b9ec053cabbfeb6d1396d88e5caebfc1caa5 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 16:27:38 +0400 Subject: [PATCH 03/12] control_flow_graph: add id field and remove INVALID_JUMP_START - Terminate block generated --- evmole.pyi | 14 ++++---- go/README.md | 22 +++++++++++-- go/types.go | 4 +++ javascript/README.md | 25 +++++++------- python/README.md | 16 +++++---- src/control_flow_graph/initial.rs | 51 +++++++++++++++-------------- src/control_flow_graph/mod.rs | 7 ++-- src/control_flow_graph/reachable.rs | 6 ++-- src/interface_js.rs | 19 ++++++----- src/interface_py.rs | 5 ++- src/interface_wasm.rs | 2 ++ 11 files changed, 101 insertions(+), 70 deletions(-) diff --git a/evmole.pyi b/evmole.pyi index 7f755a7..04497fc 100644 --- a/evmole.pyi +++ b/evmole.pyi @@ -41,8 +41,8 @@ class DynamicJump: Represents a dynamic jump destination in the control flow. Attributes: - path (List[int]): Path of basic blocks leading to this jump. - to (Optional[int]): Target basic block offset if known, None otherwise. + path (List[int]): Path of block IDs leading to this jump. + to (Optional[int]): Destination block ID if known, None otherwise; use Block.start to get the bytecode offset. """ path: List[int] to: Optional[int] @@ -58,12 +58,12 @@ class BlockType: class Jump: """Block ends with unconditional jump""" - to: int # Destination basic block offset + to: int # Destination block ID; use Block.start to get the bytecode offset class Jumpi: """Block ends with conditional jump""" - true_to: int # Destination if condition is true - false_to: int # Destination if condition is false (fall-through) + true_to: int # Destination block ID if condition is true; use Block.start to get the bytecode offset + false_to: int # Destination block ID if condition is false; use Block.start to get the bytecode offset class DynamicJump: """Block ends with jump to computed destination""" @@ -72,17 +72,19 @@ class BlockType: class DynamicJumpi: """Block ends with conditional jump to computed destination""" true_to: List[DynamicJump] # Possible computed jump destinations if true - false_to: int # Destination if condition is false (fall-through) + false_to: int # Destination block ID if condition is false; use Block.start to get the bytecode offset class Block: """ Represents a basic block in the control flow graph. Attributes: + id (int): Unique block identifier (CFG key) start (int): Byte offset where the block's first opcode begins end (int): Byte offset where the block's last opcode begins btype (BlockType): Type of the block and its control flow. """ + id: int start: int end: int btype: BlockType diff --git a/go/README.md b/go/README.md index 0fc3e97..0eea2d5 100644 --- a/go/README.md +++ b/go/README.md @@ -104,16 +104,22 @@ info, err := evmole.ContractInfo(ctx, bytecode, evmole.Options{ ControlFlowGraph: true, // Note: also enables BasicBlocks }) +// Build a map from block ID to bytecode start offset +idToStart := make(map[int]int, len(info.ControlFlowGraph.Blocks)) +for _, b := range info.ControlFlowGraph.Blocks { + idToStart[b.ID] = b.Start +} + for _, block := range info.ControlFlowGraph.Blocks { fmt.Printf("Block %d-%d: ", block.Start, block.End) switch block.Type.Kind { case evmole.BlockKindTerminate: fmt.Printf("Terminate(success=%v)\n", block.Type.Terminate.Success) case evmole.BlockKindJump: - fmt.Printf("Jump(to=%d)\n", block.Type.Jump.To) + fmt.Printf("Jump(to=%d)\n", idToStart[block.Type.Jump.To]) case evmole.BlockKindJumpi: fmt.Printf("Jumpi(true=%d, false=%d)\n", - block.Type.Jumpi.TrueTo, block.Type.Jumpi.FalseTo) + idToStart[block.Type.Jumpi.TrueTo], idToStart[block.Type.Jumpi.FalseTo]) } } ``` @@ -178,6 +184,18 @@ type StorageRecord struct { } ``` +#### Block +```go +type Block struct { + ID int // Unique block identifier (CFG key) + Start int // Byte offset where the block's first opcode begins + End int // Byte offset where the block's last opcode begins + Type BlockType // Control flow type +} +``` + +Jump destination fields (`Jump.To`, `Jumpi.TrueTo`, `Jumpi.FalseTo`, `DynamicJumpi.FalseTo`, `DynamicJump.To`) are **block IDs**, not bytecode offsets. Use `Block.Start` (looked up by ID) to get the actual bytecode offset. + ## Thread Safety The `Analyzer` type is safe for concurrent use from multiple goroutines. Internally, it uses a mutex to serialize WASM execution (since WASM is single-threaded). For high-concurrency workloads, consider creating a pool of analyzers. diff --git a/go/types.go b/go/types.go index fa7eea4..b38a6a8 100644 --- a/go/types.go +++ b/go/types.go @@ -104,6 +104,8 @@ type ControlFlowGraph struct { // Block is a basic block in the control flow graph. type Block struct { + // ID is the unique block identifier (CFG key). + ID int `json:"id"` // Start is the byte offset where the block's first opcode begins. Start int `json:"start"` // End is the byte offset where the block's last opcode begins. @@ -116,6 +118,7 @@ type Block struct { func (b *Block) UnmarshalJSON(data []byte) error { // First unmarshal the basic fields type blockAlias struct { + ID int `json:"id"` Start int `json:"start"` End int `json:"end"` Type string `json:"type"` @@ -125,6 +128,7 @@ func (b *Block) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &alias); err != nil { return err } + b.ID = alias.ID b.Start = alias.Start b.End = alias.End diff --git a/javascript/README.md b/javascript/README.md index 70b9f3b..5a2cc3e 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -184,11 +184,12 @@ Represents the control flow graph of the contract bytecode ### Block : Object Represents a basic block in the control flow graph -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | | --- | --- | --- | +| id | number | Unique block identifier (CFG key) | | start | number | Byte offset where the block's first opcode begins | | end | number | Byte offset where the block's last opcode begins | | type | 'Terminate' \| 'Jump' \| 'Jumpi' \| 'DynamicJump' \| 'DynamicJumpi' | Block type | @@ -207,28 +208,28 @@ Represents a basic block in the control flow graph ### DataJump : Object -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | | --- | --- | --- | -| to | number | Destination basic block offset | +| to | number | Destination block ID; use `Block.start` to get the bytecode offset | ### DataJumpi : Object -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | | --- | --- | --- | -| true_to | number | Destination if condition is true | -| false_to | number | Destination if condition is false (fall-through) | +| true_to | number | Destination block ID if condition is true; use `Block.start` to get the bytecode offset | +| false_to | number | Destination block ID if condition is false; use `Block.start` to get the bytecode offset | ### DataDynamicJump : Object -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | @@ -238,23 +239,23 @@ Represents a basic block in the control flow graph ### DataDynamicJumpi : Object -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | | --- | --- | --- | | true_to | [DynamicJump](#DynamicJump) | Possible computed jump destinations if true | -| false_to | number | Destination if condition is false (fall-through) | +| false_to | number | Destination block ID if condition is false; use `Block.start` to get the bytecode offset | ### DynamicJump : Object Represents a dynamic jump destination in the control flow -**Kind**: global typedef +**Kind**: global typedef **Properties** | Name | Type | Description | | --- | --- | --- | -| path | Array.<number> | Path of basic blocks leading to this jump | -| [to] | number | Target basic block offset if known. Optional | +| path | Array.<number> | Path of block IDs leading to this jump | +| [to] | number | Destination block ID if known; use `Block.start` to get the bytecode offset. Optional | diff --git a/python/README.md b/python/README.md index 7d9b224..a1cf17c 100644 --- a/python/README.md +++ b/python/README.md @@ -124,6 +124,7 @@ Represents the control flow graph of the contract bytecode. ```python class Block(): + id: int start: int end: int btype: BlockType @@ -133,6 +134,7 @@ Represents a basic block in the control flow graph. **Attributes**: +- `id` - Unique block identifier (CFG key) - `start` - Byte offset where the block's first opcode begins - `end` - Byte offset where the block's last opcode begins - `btype` - Type of the block and its control flow @@ -171,12 +173,12 @@ Block terminates execution #### Jump Block ends with unconditional jump -- `to` - Destination basic block offset +- `to` - Destination block ID; use `Block.start` to get the bytecode offset #### Jumpi Block ends with conditional jump -- `true_to` - Destination if condition is true -- `false_to` - Destination if condition is false (fall-through) +- `true_to` - Destination block ID if condition is true; use `Block.start` to get the bytecode offset +- `false_to` - Destination block ID if condition is false; use `Block.start` to get the bytecode offset #### DynamicJump Block ends with jump to computed destination @@ -184,8 +186,8 @@ Block ends with jump to computed destination #### DynamicJumpi Block ends with conditional jump to computed destination -- `true_to` - Possible computed jump destinations if true -- `false_to` - Destination if condition is false (fall-through) +- `true_to` - Possible computed jump destinations if true +- `false_to` - Destination block ID if condition is false; use `Block.start` to get the bytecode offset ### DynamicJump @@ -200,6 +202,6 @@ Represents a dynamic jump destination in the control flow. **Attributes**: -- `path` - Path of basic blocks leading to this jump -- `to` - Target basic block offset if known, None otherwise +- `path` - Path of block IDs leading to this jump +- `to` - Destination block ID if known, None otherwise; use `Block.start` to get the bytecode offset diff --git a/src/control_flow_graph/initial.rs b/src/control_flow_graph/initial.rs index 37a7640..357e7e0 100644 --- a/src/control_flow_graph/initial.rs +++ b/src/control_flow_graph/initial.rs @@ -7,7 +7,7 @@ use crate::evm::{ op, }; -use super::{Block, BlockType, INVALID_JUMP_START}; +use super::{Block, BlockType}; fn is_static_jump(code: &[u8], prev_pc: usize) -> Option { match code[prev_pc] { @@ -23,6 +23,7 @@ fn is_static_jump(code: &[u8], prev_pc: usize) -> Option { fn new_block(start: usize) -> Block { Block { + id: start, start, end: 0, btype: BlockType::Terminate { success: false }, // always overwritten @@ -39,6 +40,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { for (pc, CodeOp { op, opi, .. }) in iterate_code(code, 0, None) { if wait_jumpdest { if op == op::JUMPDEST { + block.id = pc; block.start = pc; wait_jumpdest = false; } @@ -52,7 +54,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { // jdest could be after jumpi - already new block block.end = prev_pc; block.btype = BlockType::Jump { to: pc }; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc); } } @@ -70,7 +72,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { } }; block.end = pc; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc + opi.size); } @@ -81,7 +83,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { BlockType::DynamicJump { to: Vec::new() } }; block.end = pc; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc + opi.size); wait_jumpdest = true; } @@ -92,7 +94,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { }; block.end = pc; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc + opi.size); wait_jumpdest = true; } @@ -105,7 +107,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { if block.start == 0 { block.end = prev_pc; block.btype = BlockType::Terminate { success: false }; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc /* start will be overwritten in wait_jumpdest*/); break; } @@ -114,7 +116,7 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { // have valid instructions in block block.end = prev_pc; block.btype = BlockType::Terminate { success: false }; - blocks.insert(block.start, block); + blocks.insert(block.id, block); block = new_block(pc /* start will be overwritten in wait_jumpdest*/); } } @@ -128,12 +130,11 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { // jdest could be after jumpi - already new block block.end = prev_pc; block.btype = BlockType::Terminate { success: false }; - blocks.insert(block.start, block); + blocks.insert(block.id, block); } let keys: HashSet<_> = blocks.keys().copied().collect(); - let mut next_invalid_jmp = INVALID_JUMP_START; for bl in blocks.values_mut() { assert!( bl.end >= bl.start, @@ -141,27 +142,27 @@ pub fn initial_blocks(code: &[u8]) -> BTreeMap { bl, op::info(code[bl.start]) ); - match bl.btype { - BlockType::Jump { ref mut to } => { + let new_btype = match &bl.btype { + BlockType::Jump { to } => { if !keys.contains(to) || code[*to] != op::JUMPDEST { - next_invalid_jmp += 1; - *to = next_invalid_jmp; /* greater than max code size */ + Some(BlockType::Terminate { success: false }) + } else { + None } } - BlockType::Jumpi { - ref mut true_to, - ref mut false_to, - } => { - if !keys.contains(true_to) || code[*true_to] != op::JUMPDEST { - next_invalid_jmp += 1; - *true_to = next_invalid_jmp; /* greater than max code size */ - } - if !keys.contains(false_to) { - next_invalid_jmp += 1; - *false_to = next_invalid_jmp; /* greater than max code size */ + BlockType::Jumpi { true_to, false_to } => { + let true_valid = keys.contains(true_to) && code[*true_to] == op::JUMPDEST; + let false_valid = keys.contains(false_to); + match (true_valid, false_valid) { + (true, true) => None, + (false, true) => Some(BlockType::Jump { to: *false_to }), + (_, false) => Some(BlockType::Terminate { success: false }), } } - _ => {} + _ => None, + }; + if let Some(btype) = new_btype { + bl.btype = btype; } } blocks diff --git a/src/control_flow_graph/mod.rs b/src/control_flow_graph/mod.rs index cffe274..ae70761 100644 --- a/src/control_flow_graph/mod.rs +++ b/src/control_flow_graph/mod.rs @@ -18,17 +18,14 @@ mod reachable; mod resolver; mod state; -/// Constant used to mark invalid jump destinations (jumps not to JUMPDEST). -/// Any jump destination value equal to or greater than this constant should be considered invalid. -/// Value is deliberately chosen to be larger than the maximum possible EVM contract -/// code size, ensuring it cannot occur in valid code. -pub const INVALID_JUMP_START: usize = 30_000; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] /// A basic block in the control flow graph representing a sequence of instructions /// with a single entry point and a single exit point pub struct Block { + /// Unique CFG block identity, can be not equal to start + pub id: usize, /// Byte offset where the block's first opcode begins pub start: usize, /// Byte offset where the block's last opcode begins diff --git a/src/control_flow_graph/reachable.rs b/src/control_flow_graph/reachable.rs index 3879205..58eaf74 100644 --- a/src/control_flow_graph/reachable.rs +++ b/src/control_flow_graph/reachable.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use crate::collections::HashSet; -use super::{Block, BlockType, INVALID_JUMP_START}; +use super::{Block, BlockType}; pub fn get_reachable_nodes( blocks: &BTreeMap, @@ -16,9 +16,6 @@ pub fn get_reachable_nodes( let mut visited = HashSet::default(); let mut queue = vec![from]; while let Some(current) = queue.pop() { - if current >= INVALID_JUMP_START { - continue; - } if !visited.insert(current) { continue; } @@ -69,6 +66,7 @@ mod tests { fn create_basic_block(btype: BlockType) -> Block { Block { + id: 0, start: 0, end: 1, btype, diff --git a/src/interface_js.rs b/src/interface_js.rs index 7ada504..e4a51bf 100644 --- a/src/interface_js.rs +++ b/src/interface_js.rs @@ -125,12 +125,14 @@ pub fn dummy_control_flow_graph() {} const DOC_BLOCK: &'static str = r#" /** Represents a basic block in the control flow graph + * @property id - Unique block identifier (CFG key) * @property start - Byte offset where the block's first opcode begins * @property end - Byte offset where the block's last opcode begins * @property type - Block type * @property data - Type-specific data */ export type Block = { + id: number, start: number, end: number, type: 'Terminate' | 'Jump' | 'Jumpi' | 'DynamicJump' | 'DynamicJumpi'; @@ -160,6 +162,7 @@ export type Block = { /// @typedef {Object} Block /// @description Represents a basic block in the control flow graph +/// @property {number} id - Unique block identifier (CFG key) /// @property {number} start - Byte offset where the block's first opcode begins /// @property {number} end - Byte offset where the block's last opcode begins /// @property {('Terminate'|'Jump'|'Jumpi'|'DynamicJump'|'DynamicJumpi')} type - Block type @@ -177,15 +180,15 @@ pub fn dummy_block_data_terminate() {} // {{{ Data Jump Block /// @typedef {Object} DataJump -/// @property {number} to - Destination basic block offset +/// @property {number} to - Destination block ID; use Block.start to get the bytecode offset #[wasm_bindgen(skip_jsdoc)] pub fn dummy_block_data_jump() {} // }}} // {{{ Data Jumpi Block /// @typedef {Object} DataJumpi -/// @property {number} true_to - Destination if condition is true -/// @property {number} false_to - Destination if condition is false (fall-through) +/// @property {number} true_to - Destination block ID if condition is true; use Block.start to get the bytecode offset +/// @property {number} false_to - Destination block ID if condition is false; use Block.start to get the bytecode offset #[wasm_bindgen(skip_jsdoc)] pub fn dummy_block_data_jumpi() {} // }}} @@ -200,7 +203,7 @@ pub fn dummy_block_data_dynamic_jump() {} // {{{ Data DynamicJumpi Block /// @typedef {Object} DataDynamicJumpi /// @property {DynamicJump} true_to - Possible computed jump destinations if true -/// @property {number} false_to - Destination if condition is false (fall-through) +/// @property {number} false_to - Destination block ID if condition is false; use Block.start to get the bytecode offset #[wasm_bindgen(skip_jsdoc)] pub fn dummy_block_data_dynamic_jumpi() {} // }}} @@ -210,8 +213,8 @@ pub fn dummy_block_data_dynamic_jumpi() {} const DOC_DYNAMIC_JUMP: &'static str = r#" /** Represents a dynamic jump destination in the control flow - * @property path - Path of basic blocks leading to this jump - * @property to - Target basic block offset if known + * @property path - Path of block IDs leading to this jump + * @property to - Destination block ID if known; use Block.start to get the bytecode offset */ export type DynamicJump = { path: number[]; @@ -220,8 +223,8 @@ export type DynamicJump = { "#; /// @typedef {Object} DynamicJump /// @description Represents a dynamic jump destination in the control flow -/// @property {number[]} path - Path of basic blocks leading to this jump -/// @property {number} [to] - Target basic block offset if known. Optional +/// @property {number[]} path - Path of block IDs leading to this jump +/// @property {number} [to] - Destination block ID if known; use Block.start to get the bytecode offset. Optional #[wasm_bindgen(skip_jsdoc)] pub fn dummy_dynamic_jump() {} // }}} diff --git a/src/interface_py.rs b/src/interface_py.rs index a6ca923..5e4ca75 100644 --- a/src/interface_py.rs +++ b/src/interface_py.rs @@ -162,6 +162,7 @@ mod evmole { #[pyclass(name = "Block", get_all)] #[derive(Clone)] struct PyBlock { + id: usize, start: usize, end: usize, btype: PyBlockType, @@ -170,7 +171,8 @@ mod evmole { impl PyBlock { fn __repr__(&self) -> String { format!( - "Block(start={}, end={}, btype=BlockType.{})", + "Block(id={}, start={}, end={}, btype=BlockType.{})", + self.id, self.start, self.end, self.btype.__repr__() @@ -325,6 +327,7 @@ mod evmole { .blocks .into_values() .map(|bl| PyBlock { + id: bl.id, start: bl.start, end: bl.end, btype: match bl.btype { diff --git a/src/interface_wasm.rs b/src/interface_wasm.rs index 28c0877..5628845 100644 --- a/src/interface_wasm.rs +++ b/src/interface_wasm.rs @@ -155,6 +155,7 @@ struct ControlFlowGraphResult { #[derive(serde::Serialize)] struct BlockResult { + id: usize, start: usize, end: usize, #[serde(flatten)] @@ -225,6 +226,7 @@ impl ContractResult { .blocks .into_values() .map(|bl| BlockResult { + id: bl.id, start: bl.start, end: bl.end, btype: match bl.btype { From 311fa983ac1febd6c98f834bfc0e249d6790c0eb Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 16:34:53 +0400 Subject: [PATCH 04/12] benchmark: use updated control_flow_graph API --- benchmark/providers/evmole-go/main.go | 16 ++++--- benchmark/providers/evmole-js/main.mjs | 13 +++--- benchmark/providers/evmole-py/main.py | 16 ++++--- benchmark/providers/evmole-rs/src/main.rs | 55 ++++++++++++++--------- src/contract_info.rs | 25 ++++++----- 5 files changed, 72 insertions(+), 53 deletions(-) diff --git a/benchmark/providers/evmole-go/main.go b/benchmark/providers/evmole-go/main.go index 99d250d..6688459 100644 --- a/benchmark/providers/evmole-go/main.go +++ b/benchmark/providers/evmole-go/main.go @@ -177,27 +177,31 @@ func main() { if err != nil { panic(err) } + idToStart := make(map[int]int, len(info.ControlFlowGraph.Blocks)) + for _, b := range info.ControlFlowGraph.Blocks { + idToStart[b.ID] = b.Start + } edges := make([][2]int, 0) for _, block := range info.ControlFlowGraph.Blocks { switch block.Type.Kind { case evmole.BlockKindJump: - edges = append(edges, [2]int{block.Start, block.Type.Jump.To}) + edges = append(edges, [2]int{block.Start, idToStart[block.Type.Jump.To]}) case evmole.BlockKindJumpi: - edges = append(edges, [2]int{block.Start, block.Type.Jumpi.TrueTo}) - edges = append(edges, [2]int{block.Start, block.Type.Jumpi.FalseTo}) + edges = append(edges, [2]int{block.Start, idToStart[block.Type.Jumpi.TrueTo]}) + edges = append(edges, [2]int{block.Start, idToStart[block.Type.Jumpi.FalseTo]}) case evmole.BlockKindDynamicJump: for _, v := range block.Type.DynamicJump.To { if v.To != nil { - edges = append(edges, [2]int{block.Start, *v.To}) + edges = append(edges, [2]int{block.Start, idToStart[*v.To]}) } } case evmole.BlockKindDynamicJumpi: for _, v := range block.Type.DynamicJumpi.TrueTo { if v.To != nil { - edges = append(edges, [2]int{block.Start, *v.To}) + edges = append(edges, [2]int{block.Start, idToStart[*v.To]}) } } - edges = append(edges, [2]int{block.Start, block.Type.DynamicJumpi.FalseTo}) + edges = append(edges, [2]int{block.Start, idToStart[block.Type.DynamicJumpi.FalseTo]}) case evmole.BlockKindTerminate: // do nothing } diff --git a/benchmark/providers/evmole-js/main.mjs b/benchmark/providers/evmole-js/main.mjs index e305c66..a918e73 100644 --- a/benchmark/providers/evmole-js/main.mjs +++ b/benchmark/providers/evmole-js/main.mjs @@ -49,29 +49,30 @@ function extract(fileData, mode, fname) { return [duration_us, r.basicBlocks]; } else if (mode === 'flow') { let [duration_us, r] = timeit(() => contractInfo(code, {controlFlowGraph: true})); + const blockStart = new Map(r.controlFlowGraph.blocks.map(b => [b.get('id'), b.get('start')])); let ret = [] for (const b of r.controlFlowGraph.blocks) { let bt = b.get('type'); let start = b.get('start'); let data = b.get('data'); if (bt === 'Jump') { - ret.push([start, data.to]) + ret.push([start, blockStart.get(data.to)]) } else if (bt === 'Jumpi') { - ret.push([start, data.true_to]) - ret.push([start, data.false_to]) + ret.push([start, blockStart.get(data.true_to)]) + ret.push([start, blockStart.get(data.false_to)]) } else if (bt === 'DynamicJump') { for (let v of data.to) { if(v.to) { - ret.push([start, v.to]) + ret.push([start, blockStart.get(v.to)]) } } } else if (bt === 'DynamicJumpi') { for (let v of data.true_to) { if(v.to) { - ret.push([start, v.to]) + ret.push([start, blockStart.get(v.to)]) } } - ret.push([start, data.false_to]) + ret.push([start, blockStart.get(data.false_to)]) } else if (bt === 'Terminate') { // do nothing } else { diff --git a/benchmark/providers/evmole-py/main.py b/benchmark/providers/evmole-py/main.py index 13d95e4..dea7edc 100644 --- a/benchmark/providers/evmole-py/main.py +++ b/benchmark/providers/evmole-py/main.py @@ -53,22 +53,24 @@ r = info.basic_blocks elif cfg.mode == 'flow': r = [] - for bl in info.control_flow_graph.blocks: + blocks = info.control_flow_graph.blocks + block_start = {bl.id: bl.start for bl in blocks} + for bl in blocks: match bl.btype: case BlockType.Jump(to): - r.append((bl.start, to)) + r.append((bl.start, block_start[to])) case BlockType.Jumpi(true_to, false_to): - r.append((bl.start, true_to)) - r.append((bl.start, false_to)) + r.append((bl.start, block_start[true_to])) + r.append((bl.start, block_start[false_to])) case BlockType.DynamicJump(to): for v in to: if v.to is not None: - r.append((bl.start, v.to)) + r.append((bl.start, block_start[v.to])) case BlockType.DynamicJumpi(true_to, false_to): for v in true_to: if v.to is not None: - r.append((bl.start, v.to)) - r.append((bl.start, false_to)) + r.append((bl.start, block_start[v.to])) + r.append((bl.start, block_start[false_to])) case BlockType.Terminate: pass else: diff --git a/benchmark/providers/evmole-rs/src/main.rs b/benchmark/providers/evmole-rs/src/main.rs index d96dc28..598c1af 100644 --- a/benchmark/providers/evmole-rs/src/main.rs +++ b/benchmark/providers/evmole-rs/src/main.rs @@ -25,7 +25,6 @@ enum Mode { Flow, } - #[derive(Parser)] struct Args { mode: Mode, @@ -43,8 +42,7 @@ struct Args { filter_selector: Option, } -fn timeit(args: evmole::ContractInfoArgs) -> (evmole::Contract, u64) -{ +fn timeit(args: evmole::ContractInfoArgs) -> (evmole::Contract, u64) { let now = Instant::now(); let result = evmole::contract_info(args); let duration_us = now.elapsed().as_micros() as u64; @@ -60,7 +58,7 @@ fn main() -> Result<(), Box> { Mode::Arguments | Mode::Mutability => { let file_content = fs::read_to_string(cfg.selectors_file.unwrap())?; serde_json::from_str(&file_content)? - }, + } _ => HashMap::default(), }; @@ -160,7 +158,8 @@ fn main() -> Result<(), Box> { &selectors[&fname].1 }; - let (info, dur) = timeit(evmole::ContractInfoArgs::new(&code).with_state_mutability()); + let (info, dur) = + timeit(evmole::ContractInfoArgs::new(&code).with_state_mutability()); let smut: HashMap = info .functions .unwrap() @@ -208,37 +207,49 @@ fn main() -> Result<(), Box> { Mode::Blocks => { let (info, dur) = timeit(evmole::ContractInfoArgs::new(&code).with_basic_blocks()); - ret_flow.insert(fname, (dur, info.basic_blocks.unwrap().into_iter().collect())); + ret_flow.insert( + fname, + (dur, info.basic_blocks.unwrap().into_iter().collect()), + ); } Mode::Flow => { - let (info, dur) = timeit(evmole::ContractInfoArgs::new(&code).with_control_flow_graph()); + let (info, dur) = + timeit(evmole::ContractInfoArgs::new(&code).with_control_flow_graph()); + let cfg = info.control_flow_graph.unwrap(); + + // node id to block's bytecode start + let real_offset = |id: usize| -> usize { cfg.blocks[&id].start }; + let mut flow: BTreeSet<(usize, usize)> = BTreeSet::new(); - for block in info.control_flow_graph.unwrap().blocks.values() { + for block in cfg.blocks.values() { match block.btype { - BlockType::Jump{to} => { - flow.insert((block.start, to)); - }, - BlockType::Jumpi{true_to, false_to} => { - flow.insert((block.start, true_to)); - flow.insert((block.start, false_to)); - }, + BlockType::Jump { to } => { + flow.insert((block.start, real_offset(to))); + } + BlockType::Jumpi { true_to, false_to } => { + flow.insert((block.start, real_offset(true_to))); + flow.insert((block.start, real_offset(false_to))); + } BlockType::DynamicJump { ref to } => { for x in to { if let Some(v) = x.to { - flow.insert((block.start, v)); + flow.insert((block.start, real_offset(v))); } } - }, - BlockType::DynamicJumpi { ref true_to, false_to } => { + } + BlockType::DynamicJumpi { + ref true_to, + false_to, + } => { for x in true_to { if let Some(v) = x.to { - flow.insert((block.start, v)); + flow.insert((block.start, real_offset(v))); } } - flow.insert((block.start, false_to)); - }, - BlockType::Terminate{..} => {}, + flow.insert((block.start, real_offset(false_to))); + } + BlockType::Terminate { .. } => {} } } ret_flow.insert(fname, (dur, flow)); diff --git a/src/contract_info.rs b/src/contract_info.rs index 365d082..262e35e 100644 --- a/src/contract_info.rs +++ b/src/contract_info.rs @@ -175,6 +175,19 @@ impl<'a> ContractInfoArgs<'a> { pub fn contract_info(args: ContractInfoArgs) -> Contract { const GAS_LIMIT: u32 = 0; + let (basic_blocks, control_flow_graph): (Option>, _) = if args.need_basic_blocks { + let bb = basic_blocks(args.code); + let blocks = Some(bb.values().map(|bl| (bl.start, bl.end)).collect()); + let cfg = if args.need_control_flow_graph { + Some(control_flow_graph(args.code, bb)) + } else { + None + }; + (blocks, cfg) + } else { + (None, None) + }; + let functions = if args.need_selectors { let (selectors, _selectors_gas_used) = function_selectors(args.code, GAS_LIMIT); Some( @@ -218,18 +231,6 @@ pub fn contract_info(args: ContractInfoArgs) -> Contract { None }; - let (basic_blocks, control_flow_graph): (Option>, _) = if args.need_basic_blocks { - let bb = basic_blocks(args.code); - let blocks = Some(bb.values().map(|bl| (bl.start, bl.end)).collect()); - let cfg = if args.need_control_flow_graph { - Some(control_flow_graph(args.code, bb)) - } else { - None - }; - (blocks, cfg) - } else { - (None, None) - }; Contract { functions, From a7421cef3a7873f173e4d0580800e6227ef195fe Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 17:05:20 +0400 Subject: [PATCH 05/12] cargo fmt fix --- src/contract_info.rs | 1 - src/control_flow_graph/mod.rs | 1 - src/evm/vm.rs | 27 +++++++++++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/contract_info.rs b/src/contract_info.rs index 262e35e..492ed64 100644 --- a/src/contract_info.rs +++ b/src/contract_info.rs @@ -231,7 +231,6 @@ pub fn contract_info(args: ContractInfoArgs) -> Contract { None }; - Contract { functions, storage, diff --git a/src/control_flow_graph/mod.rs b/src/control_flow_graph/mod.rs index ae70761..30e836d 100644 --- a/src/control_flow_graph/mod.rs +++ b/src/control_flow_graph/mod.rs @@ -18,7 +18,6 @@ mod reachable; mod resolver; mod state; - #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] /// A basic block in the control flow graph representing a sequence of instructions diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 42c6f55..6bec967 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -351,7 +351,11 @@ where ( 3, if s0 >= VAL_256 { - if s1.bit(U256::BITS - 1) { U256::MAX } else { U256::ZERO } + if s1.bit(U256::BITS - 1) { + U256::MAX + } else { + U256::ZERO + } } else { I256::from_raw(s1).asr(s0.to()).into_raw() }, @@ -839,9 +843,19 @@ mod tests { let cases: &[(U256, op::OpCode, U256, U256)] = &[ // Positive value: arithmetic shift == logical shift - (U256::from(1u32), op::SAR, U256::from(4u32), U256::from(2u32)), + ( + U256::from(1u32), + op::SAR, + U256::from(4u32), + U256::from(2u32), + ), // Shift by 0: identity - (U256::ZERO, op::SAR, U256::from(0x42u32), U256::from(0x42u32)), + ( + U256::ZERO, + op::SAR, + U256::from(0x42u32), + U256::from(0x42u32), + ), // -1 >> 1 == -1 (sign extends; old logical-shift gave 0x7FFF..FF) (U256::from(1u32), op::SAR, neg1, neg1), // -1 >> 255 == -1 @@ -856,7 +870,12 @@ mod tests { // I256::MIN >> 255 == -1 (U256::from(255u32), op::SAR, I256::MIN.into_raw(), neg1), // I256::MAX >> 255 == 0 (positive, so no sign extension) - (U256::from(255u32), op::SAR, I256::MAX.into_raw(), U256::ZERO), + ( + U256::from(255u32), + op::SAR, + I256::MAX.into_raw(), + U256::ZERO, + ), // shift >= 256, negative value → U256::MAX (U256::from(256u32), op::SAR, neg1, neg1), // shift >= 256, positive value → 0 From b7b33885ce8bf9fb6bc962cbd3c769f3f528ffa7 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 17:09:11 +0400 Subject: [PATCH 06/12] build: remove python 3.9 build, end-of-life --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80938a0..355d23d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -155,7 +155,7 @@ jobs: if: always() && needs.rust-test.result == 'success' strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14'] platform: - runner: ubuntu-latest os: linux From 02288a55c070314bb734ce4615761e280360e9c8 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 17:12:47 +0400 Subject: [PATCH 07/12] cargo & npm update --- Cargo.lock | 82 ++++++------- javascript/package-lock.json | 232 +++++++++++++++++------------------ javascript/package.json | 6 +- 3 files changed, 160 insertions(+), 160 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc44d3d..969f27d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -92,7 +92,7 @@ dependencies = [ "proc-macro2", "quote", "sha3", - "syn 2.0.116", + "syn 2.0.117", "syn-solidity", ] @@ -108,7 +108,7 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "syn-solidity", ] @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ark-ff" @@ -223,7 +223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -261,7 +261,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -341,7 +341,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -406,9 +406,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -571,7 +571,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.116", + "syn 2.0.117", "unicode-xid", ] @@ -625,7 +625,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -670,7 +670,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -905,7 +905,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -955,9 +955,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" dependencies = [ "once_cell", "wasm-bindgen", @@ -1033,7 +1033,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1111,7 +1111,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1162,7 +1162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1204,7 +1204,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1280,7 +1280,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1293,7 +1293,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1395,9 +1395,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.0" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" dependencies = [ "rustversion", ] @@ -1599,7 +1599,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1697,9 +1697,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1715,7 +1715,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1874,9 +1874,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" dependencies = [ "cfg-if", "once_cell", @@ -1887,9 +1887,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1897,22 +1897,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" dependencies = [ "unicode-ident", ] @@ -2005,7 +2005,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2021,7 +2021,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2089,7 +2089,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2109,7 +2109,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] diff --git a/javascript/package-lock.json b/javascript/package-lock.json index 9e64e3b..b0a4b72 100644 --- a/javascript/package-lock.json +++ b/javascript/package-lock.json @@ -10,9 +10,9 @@ "license": "MIT", "devDependencies": { "jsdoc-to-markdown": "^9.1", - "rollup": "^4.28", - "typescript": "^5.7.3", - "wasm-pack": "^0.13" + "rollup": "^4.59", + "typescript": "^5.9.3", + "wasm-pack": "^0.14" } }, "node_modules/@babel/helper-string-parser": { @@ -117,9 +117,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -131,9 +131,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -145,9 +145,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -159,9 +159,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -173,9 +173,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -187,9 +187,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -201,9 +201,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -215,9 +215,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -229,9 +229,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -243,9 +243,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -257,9 +257,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -271,9 +271,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -285,9 +285,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -299,9 +299,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -313,9 +313,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -327,9 +327,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -341,9 +341,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -355,9 +355,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -369,9 +369,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -383,9 +383,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -397,9 +397,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -411,9 +411,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -425,9 +425,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -439,9 +439,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -453,9 +453,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -1325,9 +1325,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -1517,9 +1517,9 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -1533,31 +1533,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -1735,9 +1735,9 @@ } }, "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "dev": true, "license": "MIT" }, @@ -1752,14 +1752,14 @@ } }, "node_modules/wasm-pack": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.13.1.tgz", - "integrity": "sha512-P9exD4YkjpDbw68xUhF3MDm/CC/3eTmmthyG5bHJ56kalxOTewOunxTke4SyF8MTXV6jUtNjXggPgrGmMtczGg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.14.0.tgz", + "integrity": "sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==", "dev": true, "hasInstallScript": true, "license": "MIT OR Apache-2.0", "dependencies": { - "binary-install": "^1.0.1" + "binary-install": "^1.1.2" }, "bin": { "wasm-pack": "run.js" diff --git a/javascript/package.json b/javascript/package.json index fd5c679..7eb4b05 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -46,8 +46,8 @@ }, "devDependencies": { "jsdoc-to-markdown": "^9.1", - "rollup": "^4.28", - "typescript": "^5.7.3", - "wasm-pack": "^0.13" + "rollup": "^4.59", + "typescript": "^5.9.3", + "wasm-pack": "^0.14" } } From dcd1312239526425d535b138206135a4fb884f4e Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Sun, 22 Feb 2026 17:13:46 +0400 Subject: [PATCH 08/12] update maturin to 1.12.4 --- .github/workflows/build.yml | 2 +- benchmark/providers/evmole-py/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 355d23d..1f9ce3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: type: string env: - MATURIN_VERSION: 1.10.0 + MATURIN_VERSION: 1.12.4 jobs: prepare-release: diff --git a/benchmark/providers/evmole-py/Dockerfile b/benchmark/providers/evmole-py/Dockerfile index bc1a245..b0914c9 100644 --- a/benchmark/providers/evmole-py/Dockerfile +++ b/benchmark/providers/evmole-py/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 FROM rust:1.92 AS build -RUN wget https://github.com/PyO3/maturin/releases/download/v1.10.0/maturin-x86_64-unknown-linux-musl.tar.gz \ +RUN wget https://github.com/PyO3/maturin/releases/download/v1.12.4/maturin-x86_64-unknown-linux-musl.tar.gz \ && tar xf maturin-*.tar.gz && mv maturin /usr/local/bin/ COPY ./rust /workdir WORKDIR /workdir From 35b4bb56e1bc733bfec8fdb1e5ff197a32841bcc Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Thu, 26 Feb 2026 12:32:17 +0400 Subject: [PATCH 09/12] calldata: fix string padding --- src/evm/calldata.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/evm/calldata.rs b/src/evm/calldata.rs index 6ee37ca..dd7c39b 100644 --- a/src/evm/calldata.rs +++ b/src/evm/calldata.rs @@ -33,7 +33,7 @@ pub trait CallDataLabel: Sized { pub struct CallDataImpl { pub selector: [u8; 4], arg_types: BTreeMap, - arg_vals: BTreeMap, + arg_vals: BTreeMap, _phantom: PhantomData, } @@ -60,7 +60,7 @@ impl CallData for CallDataImpl { } else { let xoff = off - 4; if let Some(val) = self.arg_vals.get(&xoff) { - data = U256::from(*val).to_be_bytes(); + data = val.to_be_bytes(); } if let Some((tp, label_type)) = self.arg_types.get(&xoff) { label = T::label(xoff, tp, *label_type); @@ -86,7 +86,7 @@ impl CallData for CallDataImpl { let xoff = off - 4; if let Some(val) = self.arg_vals.get(&xoff) { //TODO: look to the left to find proper element - data = U256::from(*val).to_be_bytes_vec(); + data = val.to_be_bytes_vec(); } if let Some((tp, label_type)) = self.arg_types.get(&xoff) { label = T::label(xoff, tp, *label_type); @@ -121,7 +121,7 @@ fn is_dynamic(ty: &DynSolType) -> bool { } type ArgTypes = Vec<(usize, (DynSolType, CallDataLabelType))>; -type ArgNonZero = Vec<(usize, usize)>; +type ArgNonZero = Vec<(usize, U256)>; fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { // (offset, type) @@ -162,21 +162,21 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { } for (el_off, ty) in dynamic.into_iter() { - ret_nonzero.push((el_off, off)); + ret_nonzero.push((el_off, U256::from(off))); ret_types.push((el_off, (ty.clone(), CallDataLabelType::Offset))); match ty { DynSolType::Bytes | DynSolType::String => { - // string '0x41' with len = 1 - ret_nonzero.push((off, 32)); - ret_nonzero.push((off + 32, 0x41)); // TODO: pad right, not left + // string "A" (0x41) with len = 1; data is right-padded in 32-byte slot + ret_nonzero.push((off, U256::from(1))); + ret_nonzero.push((off + 32, U256::from(0x41) << 248)); ret_types.push((off, (ty.clone(), CallDataLabelType::DynLen))); ret_types.push((off + 32, (ty.clone(), CallDataLabelType::RealValue))); off += 64; } DynSolType::Array(val) => { // len = 1 - ret_nonzero.push((off, 1)); + ret_nonzero.push((off, U256::from(1u64))); off += 32; let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); @@ -194,7 +194,7 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { let (dyn_off, dyn_ret_types, dyn_ret_nonzero) = encode(&[*val.clone()]); let data_start = 32 * sz; for i in 0..*sz { - ret_nonzero.push((off, data_start + i * (dyn_off - 32))); + ret_nonzero.push((off, U256::from(data_start + i * (dyn_off - 32)))); off += 32; } for _ in 0..*sz { From 16e19049a4cbef067089c32fd3ccbcab7ba493bf Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Thu, 26 Feb 2026 12:36:42 +0400 Subject: [PATCH 10/12] calldata: fix load() size --- src/evm/calldata.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/evm/calldata.rs b/src/evm/calldata.rs index dd7c39b..03bc4ce 100644 --- a/src/evm/calldata.rs +++ b/src/evm/calldata.rs @@ -86,7 +86,9 @@ impl CallData for CallDataImpl { let xoff = off - 4; if let Some(val) = self.arg_vals.get(&xoff) { //TODO: look to the left to find proper element - data = val.to_be_bytes_vec(); + let word: [u8; 32] = val.to_be_bytes(); + let n = std::cmp::min(data.len(), word.len()); + data[..n].copy_from_slice(&word[..n]); } if let Some((tp, label_type)) = self.arg_types.get(&xoff) { label = T::label(xoff, tp, *label_type); @@ -243,3 +245,28 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { // } // } // } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq)] + struct TestLabel; + + impl CallDataLabel for TestLabel { + fn label(_: usize, _: &DynSolType, _: CallDataLabelType) -> Option { + None + } + } + + #[test] + fn load_preserves_requested_size_for_word_offsets() { + let cd = CallDataImpl::::new([0, 0, 0, 0], &[DynSolType::Bytes]); + + let (data, _) = cd.load(U256::from(4), U256::from(1)).unwrap(); + assert_eq!(data.len(), 1); + + let (data, _) = cd.load(U256::from(4), U256::from(40)).unwrap(); + assert_eq!(data.len(), 40); + } +} From a4544fae1c8283e2f62056c0f5f70f3e700bb022 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Thu, 26 Feb 2026 12:57:36 +0400 Subject: [PATCH 11/12] calldata: fix FixedArray and Tuple encoding, add tests --- src/evm/calldata.rs | 144 ++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 46 deletions(-) diff --git a/src/evm/calldata.rs b/src/evm/calldata.rs index 03bc4ce..cc725b3 100644 --- a/src/evm/calldata.rs +++ b/src/evm/calldata.rs @@ -144,16 +144,17 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { } else { match ty { DynSolType::FixedArray(val, sz) => { - for _ in 0..*sz { - ret_types.push((off, (*val.clone(), CallDataLabelType::RealValue))); - off += 32; - } + let (sz_off, sz_types, sz_nonzero) = + encode(&vec![*val.clone(); *sz]); + ret_types.extend(sz_types.into_iter().map(|(o, v)| (o + off, v))); + ret_nonzero.extend(sz_nonzero.into_iter().map(|(o, v)| (o + off, v))); + off += sz_off; } DynSolType::Tuple(val) => { - for v in val { - ret_types.push((off, (v.clone(), CallDataLabelType::RealValue))); - off += 32; - } + let (sz_off, sz_types, sz_nonzero) = encode(val); + ret_types.extend(sz_types.into_iter().map(|(o, v)| (o + off, v))); + ret_nonzero.extend(sz_nonzero.into_iter().map(|(o, v)| (o + off, v))); + off += sz_off; } _ => { ret_types.push((off, (ty.clone(), CallDataLabelType::RealValue))); @@ -217,56 +218,107 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { (off, ret_types, ret_nonzero) } -// #[cfg(test)] -// mod test { -// use super::encode; -// use std::collections::BTreeMap; -// -// #[test] -// fn test_encode() { -// let x = vec![ -// "string[3]".parse().unwrap(), -// // "(string)[3]".parse().unwrap(), -// // "(uint8, string)[3]".parse().unwrap(), -// ]; -// let (end_off, a, b) = encode(&x); -// println!("{}", end_off); -// println!("{:?}", a); -// println!("{:?}", b); -// -// let ma = BTreeMap::from_iter(a); -// let mb = BTreeMap::from_iter(b); -// -// for off in (0..end_off).step_by(32) { -// println!("{:064x} - {:?}", -// mb.get(&off).unwrap_or(&0), -// ma.get(&off), -// ); -// } -// } -// } - #[cfg(test)] mod tests { use super::*; + use std::collections::BTreeMap; + + fn encode_maps( + elements: &[DynSolType], + ) -> ( + usize, + BTreeMap, + BTreeMap, + ) { + let (size, types, vals) = encode(elements); + (size, BTreeMap::from_iter(types), BTreeMap::from_iter(vals)) + } #[derive(Clone, Debug, PartialEq, Eq)] - struct TestLabel; - - impl CallDataLabel for TestLabel { + struct NoLabel; + impl CallDataLabel for NoLabel { fn label(_: usize, _: &DynSolType, _: CallDataLabelType) -> Option { None } } + // --- string / bytes encoding --- + + #[test] + fn encode_string_slot_layout() { + // layout: [head: offset=32] [len=1] [data="A" right-padded] + let (size, types, vals) = encode_maps(&[DynSolType::String]); + assert_eq!(size, 96); + + assert_eq!(vals[&0], U256::from(32u64)); // offset ptr → data area + assert_eq!(types[&0].1, CallDataLabelType::Offset); + + assert_eq!(vals[&32], U256::from(1u64)); // length = 1 + assert_eq!(types[&32].1, CallDataLabelType::DynLen); + + // "A" must be right-padded: 0x4100...00, not left-padded 0x000...41 + assert_eq!(vals[&64], U256::from(0x41u64) << 248); + assert_eq!(types[&64].1, CallDataLabelType::RealValue); + } + + #[test] + fn load32_string_data_is_right_padded() { + let cd = CallDataImpl::::new([0; 4], &[DynSolType::String]); + // data slot is at calldata offset 4 (selector) + 64 (two words into args) + let elem = cd.load32(U256::from(4 + 64)); + assert_eq!(elem.data[0], 0x41, "first byte must be 0x41"); + assert_eq!(&elem.data[1..], &[0u8; 31], "rest must be zero-padded"); + } + #[test] - fn load_preserves_requested_size_for_word_offsets() { - let cd = CallDataImpl::::new([0, 0, 0, 0], &[DynSolType::Bytes]); + fn load_string_data_respects_size() { + let cd = CallDataImpl::::new([0; 4], &[DynSolType::String]); + let base = U256::from(4 + 64); // calldata offset of data slot - let (data, _) = cd.load(U256::from(4), U256::from(1)).unwrap(); - assert_eq!(data.len(), 1); + let (data, _) = cd.load(base, U256::from(1)).unwrap(); + assert_eq!(data, [0x41]); - let (data, _) = cd.load(U256::from(4), U256::from(40)).unwrap(); - assert_eq!(data.len(), 40); + let (data, _) = cd.load(base, U256::from(4)).unwrap(); + assert_eq!(data, [0x41, 0, 0, 0]); + } + + // --- nested static FixedArray --- + + #[test] + fn encode_nested_fixed_array_size() { + // uint256[2][3] = FixedArray(FixedArray(uint256, 2), 3) → 6 slots, 192 bytes + let ty: DynSolType = "uint256[2][3]".parse().unwrap(); + let (size, types, vals) = encode_maps(&[ty]); + assert_eq!(size, 192); + assert_eq!(types.len(), 6); + assert!(vals.is_empty()); + for i in 0..6usize { + assert!(types.contains_key(&(i * 32)), "missing slot at offset {}", i * 32); + } + } + + // --- nested static Tuple --- + + #[test] + fn encode_nested_tuple_size() { + // (uint256, uint256[2]) → 1 + 2 = 3 slots, 96 bytes + let ty: DynSolType = "(uint256, uint256[2])".parse().unwrap(); + let (size, types, vals) = encode_maps(&[ty]); + assert_eq!(size, 96); + assert_eq!(types.len(), 3); + assert!(vals.is_empty()); + for i in 0..3usize { + assert!(types.contains_key(&(i * 32)), "missing slot at offset {}", i * 32); + } + } + + #[test] + fn encode_flat_tuple_unchanged() { + // (uint256, address) → 2 slots, 64 bytes — baseline to confirm no regression + let ty: DynSolType = "(uint256, address)".parse().unwrap(); + let (size, types, vals) = encode_maps(&[ty]); + assert_eq!(size, 64); + assert_eq!(types.len(), 2); + assert!(vals.is_empty()); } } From 1719dc9381b2042372f7d4b55b9263962faae154 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Thu, 26 Feb 2026 18:06:49 +0400 Subject: [PATCH 12/12] cargo fmt --- src/evm/calldata.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/evm/calldata.rs b/src/evm/calldata.rs index cc725b3..e1beade 100644 --- a/src/evm/calldata.rs +++ b/src/evm/calldata.rs @@ -144,8 +144,7 @@ fn encode(elements: &[DynSolType]) -> (usize, ArgTypes, ArgNonZero) { } else { match ty { DynSolType::FixedArray(val, sz) => { - let (sz_off, sz_types, sz_nonzero) = - encode(&vec![*val.clone(); *sz]); + let (sz_off, sz_types, sz_nonzero) = encode(&vec![*val.clone(); *sz]); ret_types.extend(sz_types.into_iter().map(|(o, v)| (o + off, v))); ret_nonzero.extend(sz_nonzero.into_iter().map(|(o, v)| (o + off, v))); off += sz_off; @@ -293,7 +292,11 @@ mod tests { assert_eq!(types.len(), 6); assert!(vals.is_empty()); for i in 0..6usize { - assert!(types.contains_key(&(i * 32)), "missing slot at offset {}", i * 32); + assert!( + types.contains_key(&(i * 32)), + "missing slot at offset {}", + i * 32 + ); } } @@ -308,7 +311,11 @@ mod tests { assert_eq!(types.len(), 3); assert!(vals.is_empty()); for i in 0..3usize { - assert!(types.contains_key(&(i * 32)), "missing slot at offset {}", i * 32); + assert!( + types.contains_key(&(i * 32)), + "missing slot at offset {}", + i * 32 + ); } }