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,
}
}
}
fn push_size(opcode: u8) -> usize {
if (0x60..=0x7f).contains(&opcode) {
(opcode - 0x5f) as usize } else {
0
}
}
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 pushdata_remaining: usize = 0;
for chunk_idx in 0..num_chunks {
let start = chunk_idx * CODE_CHUNK_DATA_SIZE;
let end = std::cmp::min(start + CODE_CHUNK_DATA_SIZE, bytecode.len());
let leading_pushdata: u8 = std::cmp::min(pushdata_remaining, end - start)
.try_into()
.expect("leading pushdata count must fit in u8 (<= 31)");
let mut data = [0u8; CODE_CHUNK_DATA_SIZE];
let chunk_data = &bytecode[start..end];
data[..chunk_data.len()].copy_from_slice(chunk_data);
chunks.push(CodeChunk::new(leading_pushdata, data));
if pushdata_remaining > CODE_CHUNK_DATA_SIZE {
pushdata_remaining -= CODE_CHUNK_DATA_SIZE;
} else {
pushdata_remaining = 0;
let mut i = pushdata_remaining.max(leading_pushdata as usize);
while i < chunk_data.len() {
let opcode = chunk_data[i];
let push_bytes = push_size(opcode);
if push_bytes > 0 {
let bytes_in_chunk = chunk_data.len() - i - 1;
if push_bytes > bytes_in_chunk {
pushdata_remaining = push_bytes - bytes_in_chunk;
}
i += push_bytes + 1;
} else {
i += 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_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);
}
}