use crate::{
chain::Chain,
specs::{NotificationSpec, NotificationWithSidecars, RuBlockSpec},
};
use alloy::{
consensus::{
constants::GWEI_TO_WEI, BlobTransactionSidecar, Header, Receipt, ReceiptEnvelope,
TxEip1559, TxEip4844,
},
primitives::{Address, Bytes, FixedBytes, Log, LogData, B256, U256},
signers::Signature,
};
use signet_evm::ExecutionOutcome;
use signet_extract::{Events, Extractable, Extracts};
use signet_types::primitives::{
RecoveredBlock, SealedBlock, SealedHeader, Transaction, TransactionSigned,
};
use signet_types::{
constants::{KnownChains, ParseChainError, SignetSystemConstants},
AggregateFills,
};
use signet_zenith::{Passage, RollupOrders, Transactor};
use std::{
borrow::Borrow,
str::FromStr,
sync::atomic::{AtomicU64, Ordering},
};
#[derive(Debug)]
pub struct HostBlockSpec {
pub constants: SignetSystemConstants,
pub receipts: Vec<ReceiptEnvelope>,
pub ru_block: Option<RuBlockSpec>,
pub sidecar: Option<BlobTransactionSidecar>,
pub ru_block_receipt: Option<ReceiptEnvelope>,
pub block_number: AtomicU64,
pub events: Vec<Events>,
}
impl Clone for HostBlockSpec {
fn clone(&self) -> Self {
Self {
constants: self.constants.clone(),
receipts: self.receipts.clone(),
ru_block: self.ru_block.clone(),
sidecar: self.sidecar.clone(),
ru_block_receipt: self.ru_block_receipt.clone(),
block_number: self.block_number().into(),
events: self.events.clone(),
}
}
}
impl From<SignetSystemConstants> for HostBlockSpec {
fn from(constants: SignetSystemConstants) -> Self {
Self::new(constants)
}
}
impl HostBlockSpec {
pub const fn new(constants: SignetSystemConstants) -> Self {
Self {
constants,
receipts: vec![],
ru_block: None,
sidecar: None,
ru_block_receipt: None,
block_number: AtomicU64::new(0),
events: vec![],
}
}
pub const fn mainnet() -> Self {
Self::new(SignetSystemConstants::mainnet())
}
pub const fn parmigiana() -> Self {
Self::new(SignetSystemConstants::parmigiana())
}
#[deprecated(note = "Pecorino is being deprecated in favor of Parmigiana")]
#[allow(deprecated)]
pub const fn pecorino() -> Self {
Self::new(SignetSystemConstants::pecorino())
}
pub const fn test() -> Self {
Self::new(SignetSystemConstants::test())
}
pub fn with_block_number(self, block_number: u64) -> Self {
self.block_number.store(block_number, Ordering::Relaxed);
self
}
pub fn enter_token(mut self, recipient: Address, amount: usize, token: Address) -> Self {
let e = Passage::EnterToken {
rollupChainId: U256::from(self.constants.ru_chain_id()),
rollupRecipient: recipient,
amount: U256::from(amount),
token,
};
self.receipts.push(to_receipt(self.constants.host_passage(), &e));
self.events.push(e.into());
self
}
pub fn ingnored_enter_token(mut self, recipient: Address, amount: u64, token: Address) -> Self {
self.receipts.push(to_receipt(
self.constants.host_passage(),
&Passage::EnterToken {
rollupChainId: U256::ZERO,
rollupRecipient: recipient,
amount: U256::from(amount),
token,
},
));
self
}
pub fn enter_tokens<'a, T>(mut self, enter_tokens: impl IntoIterator<Item = T>) -> Self
where
T: Borrow<(Address, usize, Address)> + 'a,
{
for item in enter_tokens {
let enter_token = item.borrow();
self = self.enter_token(enter_token.0, enter_token.1, enter_token.2);
}
self
}
pub fn enter(mut self, recipient: Address, amount: usize) -> Self {
let e = Passage::Enter {
rollupChainId: U256::from(self.constants.ru_chain_id()),
rollupRecipient: recipient,
amount: U256::from(amount),
};
self.receipts.push(to_receipt(self.constants.host_passage(), &e));
self.events.push(e.into());
self
}
pub fn ignored_enter(mut self, recipient: Address, amount: u64) -> Self {
self.receipts.push(to_receipt(
self.constants.host_passage(),
&Passage::Enter {
rollupChainId: U256::ZERO,
rollupRecipient: recipient,
amount: U256::from(amount),
},
));
self
}
pub fn enters<'a, T>(mut self, enters: impl IntoIterator<Item = T>) -> Self
where
T: Borrow<(Address, usize)> + 'a,
{
for item in enters {
let enter = item.borrow();
self = self.enter(enter.0, enter.1);
}
self
}
pub fn transact(mut self, t: Transactor::Transact) -> Self {
self.receipts.push(to_receipt(self.constants.host_transactor(), &t));
self.events.push(t.into());
self
}
pub fn simple_transact(
self,
sender: Address,
target: Address,
data: impl AsRef<[u8]>,
value: usize,
) -> Self {
let transact = Transactor::Transact {
rollupChainId: U256::from(self.constants.ru_chain_id()),
sender,
to: target,
data: Bytes::copy_from_slice(data.as_ref()),
value: U256::from(value),
gas: U256::from(100_000),
maxFeePerGas: U256::from(GWEI_TO_WEI),
};
self.transact(transact)
}
pub fn fill(mut self, token: Address, recipient: Address, amount: u64) -> Self {
let e = RollupOrders::Filled {
outputs: vec![RollupOrders::Output {
chainId: self.constants.ru_chain_id() as u32,
token,
recipient,
amount: U256::from(amount),
}],
};
self.receipts.push(to_receipt(self.constants.host_orders(), &e));
self.events.push(e.into());
self
}
pub fn ignored_fill(mut self, token: Address, recipient: Address, amount: u64) -> Self {
self.receipts.push(to_receipt(
self.constants.host_orders(),
&RollupOrders::Filled {
outputs: vec![RollupOrders::Output {
chainId: 0,
token,
recipient,
amount: U256::from(amount),
}],
},
));
self
}
pub fn submit_block(mut self, ru_block: RuBlockSpec) -> Self {
let (bs, sidecar) = ru_block.to_block_submitted();
self.ru_block = Some(ru_block);
self.ru_block_receipt = Some(to_receipt(self.constants.host_zenith(), &bs));
self.sidecar = Some(sidecar);
self
}
fn blob_txn(&self) -> Option<TransactionSigned> {
let sidecar = self.sidecar.as_ref()?;
Some(TransactionSigned::new_unhashed(
Transaction::Eip4844(TxEip4844 {
chain_id: self.constants.host_chain_id(),
nonce: 0,
gas_limit: 100_000,
max_fee_per_gas: 100_000,
max_priority_fee_per_gas: 10_000,
to: self.constants.host_zenith(),
value: U256::ZERO,
access_list: Default::default(),
blob_versioned_hashes: sidecar.versioned_hashes().collect(),
max_fee_per_blob_gas: 100_000,
input: Bytes::default(),
}),
Signature::test_signature(),
))
}
fn make_txns(&self) -> Vec<TransactionSigned> {
self.receipts
.iter()
.map(|_| {
Some(
alloy::consensus::Signed::new_unhashed(
TxEip1559::default(),
Signature::test_signature(),
)
.into(),
)
})
.chain(std::iter::once(self.blob_txn()))
.flatten()
.collect()
}
pub fn block_number(&self) -> u64 {
self.block_number.load(Ordering::Relaxed)
}
pub fn set_block_number(&self, block_number: u64) {
self.block_number.store(block_number, Ordering::Relaxed);
}
pub fn header(&self) -> SealedHeader {
let header = Header {
difficulty: U256::from(0x4000_0000),
number: self.block_number(),
mix_hash: B256::repeat_byte(0xed),
nonce: FixedBytes::repeat_byte(0xbe),
timestamp: 1716555576,
excess_blob_gas: Some(0),
..Default::default()
};
SealedHeader::new(header)
}
pub fn sealed_block(&self) -> SealedBlock {
let header = self.header();
SealedBlock::new(header, self.make_txns())
}
pub fn recovered_block(&self) -> RecoveredBlock {
let block = self.sealed_block();
let senders = vec![Address::ZERO; block.transactions().len()];
block.recover_unchecked(senders)
}
pub fn execution_outcome(&self) -> ExecutionOutcome {
let mut receipts = vec![self.receipts.clone()];
if let Some(receipt) = self.ru_block_receipt.clone() {
receipts.first_mut().unwrap().push(receipt);
}
ExecutionOutcome::new(Default::default(), receipts, self.block_number())
}
pub fn to_chain(&self) -> (Chain, Option<BlobTransactionSidecar>) {
let execution_outcome = self.execution_outcome();
let chain = Chain::from_block(self.recovered_block(), execution_outcome);
(chain, self.sidecar.clone())
}
pub fn to_commit_notification_spec(&self) -> NotificationSpec {
NotificationSpec { old: vec![], new: vec![self.clone()] }
}
pub fn to_notification_with_sidecar(&self) -> NotificationWithSidecars {
self.to_commit_notification_spec().to_exex_notification()
}
pub fn to_revert_notification_spec(&self) -> NotificationSpec {
NotificationSpec { old: vec![self.clone()], new: vec![] }
}
pub fn assert_conforms<C: Extractable>(&self, extracts: &Extracts<'_, C>) {
if let Some(ru_block) = &self.ru_block {
ru_block.assert_conforms(extracts);
}
let mut enters = extracts.enters();
let mut enter_tokens = extracts.enter_tokens();
let mut transacts = extracts.transacts();
let mut context = AggregateFills::new();
for event in self.events.iter() {
match event {
Events::Enter(e) => {
assert_eq!(&(enters.next().expect("iter ended early")), e);
}
Events::EnterToken(e) => {
assert_eq!(&(enter_tokens.next().expect("iter ended early")), e);
}
Events::Transact(e) => {
assert_eq!(transacts.next().expect("iter ended early"), e);
}
Events::Filled(e) => {
context.add_fill(self.constants.host_chain_id(), e);
}
Events::BlockSubmitted(_) => {}
}
}
assert!(enters.next().is_none());
assert!(enter_tokens.next().is_none());
assert!(transacts.next().is_none());
assert_eq!(extracts.aggregate_fills(), context);
}
}
impl TryFrom<KnownChains> for HostBlockSpec {
type Error = ParseChainError;
fn try_from(chain: KnownChains) -> Result<Self, Self::Error> {
match chain {
KnownChains::Mainnet => Ok(Self::mainnet()),
KnownChains::Parmigiana => Ok(Self::parmigiana()),
#[allow(deprecated)]
KnownChains::Pecorino => Ok(Self::pecorino()),
KnownChains::Test => Ok(Self::test()),
}
}
}
impl FromStr for HostBlockSpec {
type Err = ParseChainError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<KnownChains>()?.try_into()
}
}
fn to_receipt<T>(address: Address, t: &T) -> ReceiptEnvelope
where
for<'a> &'a T: Into<LogData>,
{
let log = Log { address, data: t.into() };
ReceiptEnvelope::Eip1559(
Receipt { status: true.into(), cumulative_gas_used: 30_000, logs: vec![log] }.into(),
)
}