foundry_evm_core/
buffer.rs

1use alloy_primitives::U256;
2use revm::bytecode::opcode;
3
4/// Used to keep track of which buffer is currently active to be drawn by the debugger.
5#[derive(Debug, PartialEq)]
6pub enum BufferKind {
7    Memory,
8    Calldata,
9    Returndata,
10}
11
12impl BufferKind {
13    /// Helper to cycle through the active buffers.
14    pub fn next(&self) -> Self {
15        match self {
16            Self::Memory => Self::Calldata,
17            Self::Calldata => Self::Returndata,
18            Self::Returndata => Self::Memory,
19        }
20    }
21
22    /// Helper to format the title of the active buffer pane
23    pub fn title(&self, size: usize) -> String {
24        match self {
25            Self::Memory => format!("Memory (max expansion: {size} bytes)"),
26            Self::Calldata => format!("Calldata (size: {size} bytes)"),
27            Self::Returndata => format!("Returndata (size: {size} bytes)"),
28        }
29    }
30}
31
32/// Container for buffer access information.
33pub struct BufferAccess {
34    pub offset: usize,
35    pub len: usize,
36}
37
38/// Container for read and write buffer access information.
39pub struct BufferAccesses {
40    /// The read buffer kind and access information.
41    pub read: Option<(BufferKind, BufferAccess)>,
42    /// The only mutable buffer is the memory buffer, so don't store the buffer kind.
43    pub write: Option<BufferAccess>,
44}
45
46/// A utility function to get the buffer access.
47///
48/// The memory_access variable stores the index on the stack that indicates the buffer
49/// offset/len accessed by the given opcode:
50///    (read buffer, buffer read offset, buffer read len, write memory offset, write memory len)
51///    \>= 1: the stack index
52///    0: no memory access
53///    -1: a fixed len of 32 bytes
54///    -2: a fixed len of 1 byte
55///
56/// The return value is a tuple about accessed buffer region by the given opcode:
57///    (read buffer, buffer read offset, buffer read len, write memory offset, write memory len)
58pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option<BufferAccesses> {
59    let buffer_access = match op {
60        opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => {
61            (Some((BufferKind::Memory, 1, 2)), None)
62        }
63        opcode::CALLDATACOPY => (Some((BufferKind::Calldata, 2, 3)), Some((1, 3))),
64        opcode::RETURNDATACOPY => (Some((BufferKind::Returndata, 2, 3)), Some((1, 3))),
65        opcode::CALLDATALOAD => (Some((BufferKind::Calldata, 1, -1)), None),
66        opcode::CODECOPY => (None, Some((1, 3))),
67        opcode::EXTCODECOPY => (None, Some((2, 4))),
68        opcode::MLOAD => (Some((BufferKind::Memory, 1, -1)), None),
69        opcode::MSTORE => (None, Some((1, -1))),
70        opcode::MSTORE8 => (None, Some((1, -2))),
71        opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => {
72            (Some((BufferKind::Memory, 1, 2)), None)
73        }
74        opcode::CREATE | opcode::CREATE2 => (Some((BufferKind::Memory, 2, 3)), None),
75        opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None),
76        opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None),
77        opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))),
78        opcode::RETURNDATALOAD => (Some((BufferKind::Returndata, 1, -1)), None),
79        opcode::EOFCREATE => (Some((BufferKind::Memory, 3, 4)), None),
80        opcode::RETURNCONTRACT => (Some((BufferKind::Memory, 1, 2)), None),
81        opcode::DATACOPY => (None, Some((1, 3))),
82        opcode::EXTCALL | opcode::EXTSTATICCALL | opcode::EXTDELEGATECALL => {
83            (Some((BufferKind::Memory, 2, 3)), None)
84        }
85        _ => Default::default(),
86    };
87
88    let stack_len = stack.len();
89    let get_size = |stack_index| match stack_index {
90        -2 => Some(1),
91        -1 => Some(32),
92        0 => None,
93        1.. => {
94            if (stack_index as usize) <= stack_len {
95                Some(stack[stack_len - stack_index as usize].saturating_to())
96            } else {
97                None
98            }
99        }
100        _ => panic!("invalid stack index"),
101    };
102
103    if buffer_access.0.is_some() || buffer_access.1.is_some() {
104        let (read, write) = buffer_access;
105        let read_access = read.and_then(|b| {
106            let (buffer, offset, len) = b;
107            Some((buffer, BufferAccess { offset: get_size(offset)?, len: get_size(len)? }))
108        });
109        let write_access = write.and_then(|b| {
110            let (offset, len) = b;
111            Some(BufferAccess { offset: get_size(offset)?, len: get_size(len)? })
112        });
113        Some(BufferAccesses { read: read_access, write: write_access })
114    } else {
115        None
116    }
117}