kona_protocol/info/
ecotone.rs

1//! Contains ecotone-specific L1 block info types.
2
3use crate::DecodeError;
4use alloc::vec::Vec;
5use alloy_primitives::{Address, B256, Bytes, U256};
6
7/// Represents the fields within an Ecotone L1 block info transaction.
8///
9/// Ecotone Binary Format
10/// +---------+--------------------------+
11/// | Bytes   | Field                    |
12/// +---------+--------------------------+
13/// | 4       | Function signature       |
14/// | 4       | BaseFeeScalar            |
15/// | 4       | BlobBaseFeeScalar        |
16/// | 8       | SequenceNumber           |
17/// | 8       | Timestamp                |
18/// | 8       | L1BlockNumber            |
19/// | 32      | BaseFee                  |
20/// | 32      | BlobBaseFee              |
21/// | 32      | BlockHash                |
22/// | 32      | BatcherHash              |
23/// +---------+--------------------------+
24#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct L1BlockInfoEcotone {
27    /// The current L1 origin block number
28    pub number: u64,
29    /// The current L1 origin block's timestamp
30    pub time: u64,
31    /// The current L1 origin block's basefee
32    pub base_fee: u64,
33    /// The current L1 origin block's hash
34    pub block_hash: B256,
35    /// The current sequence number
36    pub sequence_number: u64,
37    /// The address of the batch submitter
38    pub batcher_address: Address,
39    /// The current blob base fee on L1
40    pub blob_base_fee: u128,
41    /// The fee scalar for L1 blobspace data
42    pub blob_base_fee_scalar: u32,
43    /// The fee scalar for L1 data
44    pub base_fee_scalar: u32,
45    /// Indicates that the scalars are empty.
46    /// This is an edge case where the first block in ecotone has no scalars,
47    /// so the bedrock tx l1 cost function needs to be used.
48    pub empty_scalars: bool,
49    /// The l1 fee overhead used along with the `empty_scalars` field for the
50    /// bedrock tx l1 cost function.
51    ///
52    /// This field is deprecated in the Ecotone Hardfork.
53    pub l1_fee_overhead: U256,
54}
55
56impl L1BlockInfoEcotone {
57    /// The type byte identifier for the L1 scalar format in Ecotone.
58    pub const L1_SCALAR: u8 = 1;
59
60    /// The length of an L1 info transaction in Ecotone.
61    pub const L1_INFO_TX_LEN: usize = 4 + 32 * 5;
62
63    /// The 4 byte selector of "setL1BlockValuesEcotone()"
64    pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x44, 0x0a, 0x5e, 0x20];
65
66    /// Encodes the [L1BlockInfoEcotone] object into Ethereum transaction calldata.
67    pub fn encode_calldata(&self) -> Bytes {
68        let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN);
69        buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref());
70        buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref());
71        buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref());
72        buf.extend_from_slice(self.sequence_number.to_be_bytes().as_ref());
73        buf.extend_from_slice(self.time.to_be_bytes().as_ref());
74        buf.extend_from_slice(self.number.to_be_bytes().as_ref());
75        buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_ref());
76        buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref());
77        buf.extend_from_slice(self.block_hash.as_ref());
78        buf.extend_from_slice(self.batcher_address.into_word().as_ref());
79        // Notice: do not include the `empty_scalars` field in the calldata.
80        // Notice: do not include the `l1_fee_overhead` field in the calldata.
81        buf.into()
82    }
83
84    /// Decodes the [L1BlockInfoEcotone] object from ethereum transaction calldata.
85    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
86        if r.len() != Self::L1_INFO_TX_LEN {
87            return Err(DecodeError::InvalidEcotoneLength(Self::L1_INFO_TX_LEN, r.len()));
88        }
89
90        // SAFETY: For all below slice operations, the full
91        //         length is validated above to be `164`.
92
93        // SAFETY: 4 bytes are copied directly into the array
94        let mut base_fee_scalar = [0u8; 4];
95        base_fee_scalar.copy_from_slice(&r[4..8]);
96        let base_fee_scalar = u32::from_be_bytes(base_fee_scalar);
97
98        // SAFETY: 4 bytes are copied directly into the array
99        let mut blob_base_fee_scalar = [0u8; 4];
100        blob_base_fee_scalar.copy_from_slice(&r[8..12]);
101        let blob_base_fee_scalar = u32::from_be_bytes(blob_base_fee_scalar);
102
103        // SAFETY: 8 bytes are copied directly into the array
104        let mut sequence_number = [0u8; 8];
105        sequence_number.copy_from_slice(&r[12..20]);
106        let sequence_number = u64::from_be_bytes(sequence_number);
107
108        // SAFETY: 8 bytes are copied directly into the array
109        let mut time = [0u8; 8];
110        time.copy_from_slice(&r[20..28]);
111        let time = u64::from_be_bytes(time);
112
113        // SAFETY: 8 bytes are copied directly into the array
114        let mut number = [0u8; 8];
115        number.copy_from_slice(&r[28..36]);
116        let number = u64::from_be_bytes(number);
117
118        // SAFETY: 8 bytes are copied directly into the array
119        let mut base_fee = [0u8; 8];
120        base_fee.copy_from_slice(&r[60..68]);
121        let base_fee = u64::from_be_bytes(base_fee);
122
123        // SAFETY: 16 bytes are copied directly into the array
124        let mut blob_base_fee = [0u8; 16];
125        blob_base_fee.copy_from_slice(&r[84..100]);
126        let blob_base_fee = u128::from_be_bytes(blob_base_fee);
127
128        let block_hash = B256::from_slice(r[100..132].as_ref());
129        let batcher_address = Address::from_slice(r[144..164].as_ref());
130
131        Ok(Self {
132            number,
133            time,
134            base_fee,
135            block_hash,
136            sequence_number,
137            batcher_address,
138            blob_base_fee,
139            blob_base_fee_scalar,
140            base_fee_scalar,
141            // Notice: the `empty_scalars` field is not included in the calldata.
142            // This is used by the evm to indicate that the bedrock tx l1 cost function
143            // needs to be used.
144            empty_scalars: false,
145            // Notice: the `l1_fee_overhead` field is not included in the calldata.
146            l1_fee_overhead: U256::ZERO,
147        })
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use alloc::vec;
155
156    #[test]
157    fn test_decode_calldata_ecotone_invalid_length() {
158        let r = vec![0u8; 1];
159        assert_eq!(
160            L1BlockInfoEcotone::decode_calldata(&r),
161            Err(DecodeError::InvalidEcotoneLength(L1BlockInfoEcotone::L1_INFO_TX_LEN, r.len(),))
162        );
163    }
164
165    #[test]
166    fn test_l1_block_info_ecotone_roundtrip_calldata_encoding() {
167        let info = L1BlockInfoEcotone {
168            number: 1,
169            time: 2,
170            base_fee: 3,
171            block_hash: B256::from([4u8; 32]),
172            sequence_number: 5,
173            batcher_address: Address::from([6u8; 20]),
174            blob_base_fee: 7,
175            blob_base_fee_scalar: 8,
176            base_fee_scalar: 9,
177            empty_scalars: false,
178            l1_fee_overhead: U256::ZERO,
179        };
180
181        let calldata = info.encode_calldata();
182        let decoded_info = L1BlockInfoEcotone::decode_calldata(&calldata).unwrap();
183        assert_eq!(info, decoded_info);
184    }
185}