use alloy_primitives::B256;
pub const CODE_CHUNK_DATA_SIZE: usize = 31;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CodeChunk {
pub leading_pushdata: u8,
pub data: [u8; CODE_CHUNK_DATA_SIZE],
}
impl CodeChunk {
pub const fn new(leading_pushdata: u8, data: [u8; CODE_CHUNK_DATA_SIZE]) -> Self {
Self {
leading_pushdata,
data,
}
}
pub fn encode(&self) -> B256 {
let mut bytes = [0u8; 32];
bytes[0] = self.leading_pushdata;
bytes[1..32].copy_from_slice(&self.data);
B256::from(bytes)
}
pub fn decode(value: B256) -> Self {
let bytes = value.as_slice();
let mut data = [0u8; CODE_CHUNK_DATA_SIZE];
data.copy_from_slice(&bytes[1..32]);
Self {
leading_pushdata: bytes[0],
data,
}
}
}
pub fn chunkify_code(bytecode: &[u8]) -> Vec<CodeChunk> {
if bytecode.is_empty() {
return vec![];
}
let num_chunks = bytecode.len().div_ceil(CODE_CHUNK_DATA_SIZE);
let mut chunks = Vec::with_capacity(num_chunks);
let mut chunk_offset: usize = 0;
let mut code_offset: usize = 0;
for i in 0..num_chunks {
let end = std::cmp::min(bytecode.len(), CODE_CHUNK_DATA_SIZE * (i + 1));
let mut data = [0u8; CODE_CHUNK_DATA_SIZE];
let src = &bytecode[CODE_CHUNK_DATA_SIZE * i..end];
data[..src.len()].copy_from_slice(src);
if chunk_offset > CODE_CHUNK_DATA_SIZE {
#[allow(clippy::cast_possible_truncation)]
let md = CODE_CHUNK_DATA_SIZE as u8; chunks.push(CodeChunk::new(md, data));
chunk_offset = 1;
continue;
}
#[allow(clippy::cast_possible_truncation)]
let leading = chunk_offset as u8;
chunks.push(CodeChunk::new(leading, data));
chunk_offset = 0;
while code_offset < end {
let opcode = bytecode[code_offset];
if (0x60..=0x7f).contains(&opcode) {
code_offset += (opcode - 0x60 + 1) as usize;
if code_offset + 1 >= CODE_CHUNK_DATA_SIZE * (i + 1) {
code_offset += 1;
chunk_offset = code_offset - CODE_CHUNK_DATA_SIZE * (i + 1);
break;
}
}
code_offset += 1;
}
}
chunks
}
#[allow(dead_code)]
pub fn dechunkify_code(chunks: &[CodeChunk], code_size: usize) -> Vec<u8> {
let mut bytecode = Vec::with_capacity(code_size);
for chunk in chunks {
let remaining = code_size.saturating_sub(bytecode.len());
let to_copy = std::cmp::min(remaining, CODE_CHUNK_DATA_SIZE);
bytecode.extend_from_slice(&chunk.data[..to_copy]);
}
bytecode
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_code() {
let chunks = chunkify_code(&[]);
assert!(chunks.is_empty());
}
#[test]
fn test_simple_code() {
let code = vec![0x60, 0x00, 0x60, 0x00]; let chunks = chunkify_code(&code);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0].leading_pushdata, 0);
assert_eq!(&chunks[0].data[..4], &code[..]);
}
#[test]
fn test_push_spanning_chunks() {
let mut code = vec![0x7f]; code.extend_from_slice(&[0x11; 32]);
let chunks = chunkify_code(&code);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].leading_pushdata, 0); assert_eq!(chunks[1].leading_pushdata, 2); }
#[test]
fn test_roundtrip() {
let original = vec![0x60, 0x80, 0x60, 0x40, 0x52]; let chunks = chunkify_code(&original);
let recovered = dechunkify_code(&chunks, original.len());
assert_eq!(original, recovered);
}
#[test]
fn test_multi_chunk_push_overflow() {
let code = hex::decode(
"d76cd86a332eccc8fc09612a4bee018df261bf00aa5076287faf5f09b0737f\
4224338e894f4f24665e21d39d4c6ec40310ffefe6923cdcc93ab9709d0c58\
4d11f505f95d4a42bf22c7",
)
.unwrap();
let chunks = chunkify_code(&code);
assert_eq!(chunks[0].leading_pushdata, 0x00);
assert_eq!(chunks[1].leading_pushdata, 0x0f); assert_eq!(chunks[2].leading_pushdata, 0x0e); }
#[test]
fn test_push32_full_chunk_overflow() {
let mut code = vec![0x00; 30]; code.push(0x7f); code.extend_from_slice(&[0xAA; 32]);
let chunks = chunkify_code(&code);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0].leading_pushdata, 0); assert_eq!(chunks[1].leading_pushdata, 31); assert_eq!(chunks[2].leading_pushdata, 1); }
#[test]
fn test_truncated_push_at_eof() {
let mut code = vec![0x00; 29]; code.push(0x63); code.push(0xBB);
let chunks = chunkify_code(&code);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0].leading_pushdata, 0);
}
#[test]
fn test_code_chunk_encode_decode() {
let mut data = [0u8; 31];
data[0] = 0x60;
data[1] = 0x80;
let chunk = CodeChunk::new(5, data);
let encoded = chunk.encode();
let decoded = CodeChunk::decode(encoded);
assert_eq!(chunk, decoded);
}
}