use alloc::vec::Vec;
use alloy_consensus::{Transaction, TxType, Typed2718};
use alloy_primitives::B256;
use alloy_rlp::{Buf, Header};
use maili_genesis::{RollupConfig, SystemConfig};
use op_alloy_consensus::OpBlock;
use crate::{
L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoInterop, L1BlockInfoIsthmus, L1BlockInfoTx,
OpBlockConversionError, SpanBatchError, SpanDecodingError,
};
pub fn starts_with_2718_deposit<B>(value: &B) -> bool
where
B: AsRef<[u8]>,
{
value.as_ref().first() == Some(&0x7E)
}
pub fn starts_with_7702_tx<B>(value: &B) -> bool
where
B: AsRef<[u8]>,
{
value.as_ref().first() == Some(&(TxType::Eip7702 as u8))
}
pub fn to_system_config(
block: &OpBlock,
rollup_config: &RollupConfig,
) -> Result<SystemConfig, OpBlockConversionError> {
if block.header.number == rollup_config.genesis.l2.number {
if block.header.hash_slow() != rollup_config.genesis.l2.hash {
return Err(OpBlockConversionError::InvalidGenesisHash(
rollup_config.genesis.l2.hash,
block.header.hash_slow(),
));
}
return rollup_config
.genesis
.system_config
.ok_or(OpBlockConversionError::MissingSystemConfigGenesis);
}
if block.body.transactions.is_empty() {
return Err(OpBlockConversionError::EmptyTransactions(block.header.hash_slow()));
}
let Some(tx) = block.body.transactions[0].as_deposit() else {
return Err(OpBlockConversionError::InvalidTxType(block.body.transactions[0].ty()));
};
let l1_info = L1BlockInfoTx::decode_calldata(tx.input().as_ref())?;
let l1_fee_scalar = match l1_info {
L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => l1_fee_scalar,
L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
base_fee_scalar,
blob_base_fee_scalar,
..
})
| L1BlockInfoTx::Interop(L1BlockInfoInterop {
base_fee_scalar,
blob_base_fee_scalar,
..
})
| L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
base_fee_scalar,
blob_base_fee_scalar,
..
}) => {
let mut buf = B256::ZERO;
buf[0] = 0x01;
buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref());
buf[28..32].copy_from_slice(base_fee_scalar.to_be_bytes().as_ref());
buf.into()
}
};
let mut cfg = SystemConfig {
batcher_address: l1_info.batcher_address(),
overhead: l1_info.l1_fee_overhead(),
scalar: l1_fee_scalar,
gas_limit: block.header.gas_limit,
..Default::default()
};
if rollup_config.is_holocene_active(block.header.timestamp) {
let eip1559_params = block.header.nonce;
cfg.eip1559_denominator = Some(u32::from_be_bytes(
eip1559_params[0..4]
.try_into()
.map_err(|_| OpBlockConversionError::Eip1559DecodeError)?,
));
cfg.eip1559_elasticity = Some(u32::from_be_bytes(
eip1559_params[4..8]
.try_into()
.map_err(|_| OpBlockConversionError::Eip1559DecodeError)?,
));
}
if rollup_config.is_isthmus_active(block.header.timestamp) {
cfg.operator_fee_scalar = Some(l1_info.operator_fee_scalar());
cfg.operator_fee_constant = Some(l1_info.operator_fee_constant());
}
Ok(cfg)
}
pub fn read_tx_data(r: &mut &[u8]) -> Result<(Vec<u8>, TxType), SpanBatchError> {
let mut tx_data = Vec::new();
let first_byte =
*r.first().ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
let mut tx_type = 0;
if first_byte <= 0x7F {
tx_type = first_byte;
tx_data.push(tx_type);
r.advance(1);
}
let rlp_header = Header::decode(&mut (**r).as_ref())
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
let tx_payload = if rlp_header.list {
let payload_length_with_header = rlp_header.payload_length + rlp_header.length();
let payload = r[0..payload_length_with_header].to_vec();
r.advance(payload_length_with_header);
Ok(payload)
} else {
Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))
}?;
tx_data.extend_from_slice(&tx_payload);
Ok((
tx_data,
tx_type
.try_into()
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))?,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{RAW_BEDROCK_INFO_TX, RAW_ECOTONE_INFO_TX, RAW_ISTHMUS_INFO_TX};
use alloy_eips::eip1898::BlockNumHash;
use alloy_primitives::{address, hex, uint, U256};
use maili_genesis::ChainGenesis;
#[test]
fn test_to_system_config_invalid_genesis_hash() {
let block = OpBlock::default();
let rollup_config = RollupConfig::default();
let err = to_system_config(&block, &rollup_config).unwrap_err();
assert_eq!(
err,
OpBlockConversionError::InvalidGenesisHash(
rollup_config.genesis.l2.hash,
block.header.hash_slow(),
)
);
}
#[test]
fn test_to_system_config_missing_system_config_genesis() {
let block = OpBlock::default();
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
..Default::default()
};
let err = to_system_config(&block, &rollup_config).unwrap_err();
assert_eq!(err, OpBlockConversionError::MissingSystemConfigGenesis);
}
#[test]
fn test_to_system_config_from_genesis() {
let block = OpBlock::default();
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
system_config: Some(SystemConfig::default()),
..Default::default()
},
..Default::default()
};
let config = to_system_config(&block, &rollup_config).unwrap();
assert_eq!(config, SystemConfig::default());
}
#[test]
fn test_to_system_config_empty_txs() {
let block = OpBlock {
header: alloy_consensus::Header { number: 1, ..Default::default() },
..Default::default()
};
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
..Default::default()
};
let err = to_system_config(&block, &rollup_config).unwrap_err();
assert_eq!(err, OpBlockConversionError::EmptyTransactions(block_hash));
}
#[test]
fn test_to_system_config_non_deposit() {
use alloy_primitives::U256;
let block = OpBlock {
header: alloy_consensus::Header { number: 1, ..Default::default() },
body: alloy_consensus::BlockBody {
transactions: vec![op_alloy_consensus::OpTxEnvelope::Legacy(
alloy_consensus::Signed::new_unchecked(
alloy_consensus::TxLegacy {
chain_id: Some(1),
nonce: 1,
gas_price: 1,
gas_limit: 1,
to: alloy_primitives::TxKind::Create,
value: alloy_primitives::U256::ZERO,
input: alloy_primitives::Bytes::new(),
},
alloy_primitives::PrimitiveSignature::new(U256::ZERO, U256::ZERO, false),
Default::default(),
),
)],
..Default::default()
},
};
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
..Default::default()
};
let err = to_system_config(&block, &rollup_config).unwrap_err();
assert_eq!(err, OpBlockConversionError::InvalidTxType(0));
}
#[test]
fn test_constructs_bedrock_system_config() {
let block = OpBlock {
header: alloy_consensus::Header { number: 1, ..Default::default() },
body: alloy_consensus::BlockBody {
transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
input: alloy_primitives::Bytes::from(&RAW_BEDROCK_INFO_TX),
..Default::default()
}),
)],
..Default::default()
},
};
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
..Default::default()
};
let config = to_system_config(&block, &rollup_config).unwrap();
let expected = SystemConfig {
batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
overhead: uint!(188_U256),
scalar: uint!(684000_U256),
gas_limit: 0,
base_fee_scalar: None,
blob_base_fee_scalar: None,
eip1559_denominator: None,
eip1559_elasticity: None,
operator_fee_scalar: None,
operator_fee_constant: None,
};
assert_eq!(config, expected);
}
#[test]
fn test_constructs_ecotone_system_config() {
let block = OpBlock {
header: alloy_consensus::Header {
number: 1,
nonce: hex!("0000beef0000babe").into(),
..Default::default()
},
body: alloy_consensus::BlockBody {
transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
input: alloy_primitives::Bytes::from(&RAW_ECOTONE_INFO_TX),
..Default::default()
}),
)],
..Default::default()
},
};
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
holocene_time: Some(0),
..Default::default()
};
assert!(rollup_config.is_holocene_active(block.header.timestamp));
let config = to_system_config(&block, &rollup_config).unwrap();
let expected = SystemConfig {
batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
overhead: U256::ZERO,
scalar: uint!(
452312848583266388373324160190187140051835877600158453279134670530344387928_U256
),
gas_limit: 0,
base_fee_scalar: None,
blob_base_fee_scalar: None,
eip1559_denominator: Some(0xbeef),
eip1559_elasticity: Some(0xbabe),
operator_fee_scalar: None,
operator_fee_constant: None,
};
assert_eq!(config, expected);
}
#[test]
fn test_constructs_isthmus_system_config() {
let block = OpBlock {
header: alloy_consensus::Header {
number: 1,
nonce: hex!("0000beef0000babe").into(),
..Default::default()
},
body: alloy_consensus::BlockBody {
transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
input: alloy_primitives::Bytes::from(&RAW_ISTHMUS_INFO_TX),
..Default::default()
}),
)],
..Default::default()
},
};
let block_hash = block.header.hash_slow();
let rollup_config = RollupConfig {
genesis: ChainGenesis {
l2: BlockNumHash { hash: block_hash, ..Default::default() },
..Default::default()
},
holocene_time: Some(0),
isthmus_time: Some(0),
..Default::default()
};
assert!(rollup_config.is_holocene_active(block.header.timestamp));
let config = to_system_config(&block, &rollup_config).unwrap();
let expected = SystemConfig {
batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
overhead: U256::ZERO,
scalar: uint!(
452312848583266388373324160190187140051835877600158453279134670530344387928_U256
),
gas_limit: 0,
base_fee_scalar: None,
blob_base_fee_scalar: None,
eip1559_denominator: Some(0xbeef),
eip1559_elasticity: Some(0xbabe),
operator_fee_scalar: Some(0xabcd),
operator_fee_constant: Some(0xdcba),
};
assert_eq!(config, expected);
}
}