1use crate::{
2 chain::Chain,
3 specs::{NotificationSpec, NotificationWithSidecars, RuBlockSpec},
4};
5use alloy::{
6 consensus::{
7 constants::GWEI_TO_WEI, BlobTransactionSidecar, Header, Receipt, ReceiptEnvelope,
8 TxEip1559, TxEip4844,
9 },
10 primitives::{Address, Bytes, Log, LogData, U256},
11 signers::Signature,
12};
13use signet_evm::ExecutionOutcome;
14use signet_extract::{Events, Extractable, Extracts};
15use signet_types::primitives::{
16 RecoveredBlock, SealedBlock, SignetHeaderV1, Transaction, TransactionSigned,
17};
18use signet_types::{
19 constants::{KnownChains, ParseChainError, SignetSystemConstants},
20 AggregateFills,
21};
22use signet_zenith::{Passage, RollupOrders, Transactor};
23use std::{
24 borrow::Borrow,
25 str::FromStr,
26 sync::atomic::{AtomicU64, Ordering},
27};
28
29#[derive(Debug)]
40pub struct HostBlockSpec {
41 pub constants: SignetSystemConstants,
43
44 pub receipts: Vec<ReceiptEnvelope>,
46 pub ru_block: Option<RuBlockSpec>,
48 pub sidecar: Option<BlobTransactionSidecar>,
50 pub ru_block_receipt: Option<ReceiptEnvelope>,
52 pub block_number: AtomicU64,
54
55 pub events: Vec<Events>,
57}
58
59impl Clone for HostBlockSpec {
60 fn clone(&self) -> Self {
61 Self {
62 constants: self.constants.clone(),
63 receipts: self.receipts.clone(),
64 ru_block: self.ru_block.clone(),
65 sidecar: self.sidecar.clone(),
66 ru_block_receipt: self.ru_block_receipt.clone(),
67 block_number: self.block_number().into(),
68 events: self.events.clone(),
69 }
70 }
71}
72
73impl From<SignetSystemConstants> for HostBlockSpec {
74 fn from(constants: SignetSystemConstants) -> Self {
75 Self::new(constants)
76 }
77}
78
79impl HostBlockSpec {
80 pub const fn new(constants: SignetSystemConstants) -> Self {
82 Self {
83 constants,
84 receipts: vec![],
85 ru_block: None,
86 sidecar: None,
87 ru_block_receipt: None,
88 block_number: AtomicU64::new(0),
89 events: vec![],
90 }
91 }
92
93 pub const fn mainnet() -> Self {
95 Self::new(SignetSystemConstants::mainnet())
96 }
97
98 pub const fn parmigiana() -> Self {
100 Self::new(SignetSystemConstants::parmigiana())
101 }
102
103 pub const fn gouda() -> Self {
105 Self::new(SignetSystemConstants::gouda())
106 }
107
108 #[deprecated(note = "Pecorino is being deprecated in favor of Parmigiana")]
110 #[allow(deprecated)]
111 pub const fn pecorino() -> Self {
112 Self::new(SignetSystemConstants::pecorino())
113 }
114
115 pub const fn test() -> Self {
117 Self::new(SignetSystemConstants::test())
118 }
119
120 pub fn with_block_number(self, block_number: u64) -> Self {
122 self.block_number.store(block_number, Ordering::Relaxed);
123 self
124 }
125
126 pub fn enter_token(mut self, recipient: Address, amount: usize, token: Address) -> Self {
128 let e = Passage::EnterToken {
129 rollupChainId: U256::from(self.constants.ru_chain_id()),
130 rollupRecipient: recipient,
131 amount: U256::from(amount),
132 token,
133 };
134 self.receipts.push(to_receipt(self.constants.host_passage(), &e));
135 self.events.push(e.into());
136
137 self
138 }
139
140 pub fn ingnored_enter_token(mut self, recipient: Address, amount: u64, token: Address) -> Self {
142 self.receipts.push(to_receipt(
143 self.constants.host_passage(),
144 &Passage::EnterToken {
145 rollupChainId: U256::ZERO,
146 rollupRecipient: recipient,
147 amount: U256::from(amount),
148 token,
149 },
150 ));
151 self
152 }
153
154 pub fn enter_tokens<'a, T>(mut self, enter_tokens: impl IntoIterator<Item = T>) -> Self
156 where
157 T: Borrow<(Address, usize, Address)> + 'a,
158 {
159 for item in enter_tokens {
160 let enter_token = item.borrow();
161 self = self.enter_token(enter_token.0, enter_token.1, enter_token.2);
162 }
163 self
164 }
165
166 pub fn enter(mut self, recipient: Address, amount: usize) -> Self {
168 let e = Passage::Enter {
169 rollupChainId: U256::from(self.constants.ru_chain_id()),
170 rollupRecipient: recipient,
171 amount: U256::from(amount),
172 };
173 self.receipts.push(to_receipt(self.constants.host_passage(), &e));
174 self.events.push(e.into());
175
176 self
177 }
178
179 pub fn ignored_enter(mut self, recipient: Address, amount: u64) -> Self {
182 self.receipts.push(to_receipt(
183 self.constants.host_passage(),
184 &Passage::Enter {
185 rollupChainId: U256::ZERO,
186 rollupRecipient: recipient,
187 amount: U256::from(amount),
188 },
189 ));
190 self
191 }
192
193 pub fn enters<'a, T>(mut self, enters: impl IntoIterator<Item = T>) -> Self
195 where
196 T: Borrow<(Address, usize)> + 'a,
197 {
198 for item in enters {
199 let enter = item.borrow();
200 self = self.enter(enter.0, enter.1);
201 }
202 self
203 }
204
205 pub fn transact(mut self, t: Transactor::Transact) -> Self {
207 self.receipts.push(to_receipt(self.constants.host_transactor(), &t));
208 self.events.push(t.into());
209 self
210 }
211
212 pub fn simple_transact(
214 self,
215 sender: Address,
216 target: Address,
217 data: impl AsRef<[u8]>,
218 value: usize,
219 ) -> Self {
220 let transact = Transactor::Transact {
221 rollupChainId: U256::from(self.constants.ru_chain_id()),
222 sender,
223 to: target,
224 data: Bytes::copy_from_slice(data.as_ref()),
225 value: U256::from(value),
226 gas: U256::from(100_000),
227 maxFeePerGas: U256::from(GWEI_TO_WEI),
228 };
229 self.transact(transact)
230 }
231
232 pub fn fill(mut self, token: Address, recipient: Address, amount: u64) -> Self {
234 let e = RollupOrders::Filled {
235 outputs: vec![RollupOrders::Output {
236 chainId: self.constants.ru_chain_id() as u32,
237 token,
238 recipient,
239 amount: U256::from(amount),
240 }],
241 };
242 self.receipts.push(to_receipt(self.constants.host_orders(), &e));
243 self.events.push(e.into());
244 self
245 }
246
247 pub fn ignored_fill(mut self, token: Address, recipient: Address, amount: u64) -> Self {
250 self.receipts.push(to_receipt(
251 self.constants.host_orders(),
252 &RollupOrders::Filled {
253 outputs: vec![RollupOrders::Output {
254 chainId: 0,
255 token,
256 recipient,
257 amount: U256::from(amount),
258 }],
259 },
260 ));
261 self
262 }
263
264 pub fn submit_block(mut self, ru_block: RuBlockSpec) -> Self {
266 let (bs, sidecar) = ru_block.to_block_submitted();
267
268 self.ru_block = Some(ru_block);
269 self.ru_block_receipt = Some(to_receipt(self.constants.host_zenith(), &bs));
270 self.sidecar = Some(sidecar);
271 self
272 }
273
274 fn blob_txn(&self) -> Option<TransactionSigned> {
276 let sidecar = self.sidecar.as_ref()?;
277
278 Some(TransactionSigned::new_unhashed(
279 Transaction::Eip4844(TxEip4844 {
280 chain_id: self.constants.host_chain_id(),
281 nonce: 0,
282 gas_limit: 100_000,
283 max_fee_per_gas: 100_000,
284 max_priority_fee_per_gas: 10_000,
285 to: self.constants.host_zenith(),
286 value: U256::ZERO,
287 access_list: Default::default(),
288 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
289 max_fee_per_blob_gas: 100_000,
290 input: Bytes::default(),
291 }),
292 Signature::test_signature(),
293 ))
294 }
295
296 fn make_txns(&self) -> Vec<TransactionSigned> {
298 self.receipts
299 .iter()
300 .map(|_| {
301 Some(
302 alloy::consensus::Signed::new_unhashed(
303 TxEip1559::default(),
304 Signature::test_signature(),
305 )
306 .into(),
307 )
308 })
309 .chain(std::iter::once(self.blob_txn()))
310 .flatten()
311 .collect()
312 }
313
314 pub fn block_number(&self) -> u64 {
316 self.block_number.load(Ordering::Relaxed)
317 }
318
319 pub fn set_block_number(&self, block_number: u64) {
321 self.block_number.store(block_number, Ordering::Relaxed);
322 }
323
324 pub fn header(&self) -> SignetHeaderV1 {
326 let header =
327 Header { number: self.block_number(), timestamp: 1716555576, ..Default::default() };
328 SignetHeaderV1::try_from(header).expect("test header is valid V1")
329 }
330
331 pub fn sealed_block(&self) -> SealedBlock {
333 let header = self.header();
334 SealedBlock::new(header, self.make_txns())
335 }
336
337 pub fn recovered_block(&self) -> RecoveredBlock {
339 let block = self.sealed_block();
340 let senders = vec![Address::ZERO; block.transactions().len()];
341 block.recover_unchecked(senders)
342 }
343
344 pub fn execution_outcome(&self) -> ExecutionOutcome {
346 let mut receipts = vec![self.receipts.clone()];
347 if let Some(receipt) = self.ru_block_receipt.clone() {
348 receipts.first_mut().unwrap().push(receipt);
349 }
350
351 ExecutionOutcome::new(Default::default(), receipts, self.block_number())
352 }
353
354 pub fn to_chain(&self) -> (Chain, Option<BlobTransactionSidecar>) {
356 let execution_outcome = self.execution_outcome();
357
358 let chain = Chain::from_block(self.recovered_block(), execution_outcome);
359
360 (chain, self.sidecar.clone())
361 }
362
363 pub fn to_commit_notification_spec(&self) -> NotificationSpec {
365 NotificationSpec { old: vec![], new: vec![self.clone()] }
366 }
367
368 pub fn to_notification_with_sidecar(&self) -> NotificationWithSidecars {
370 self.to_commit_notification_spec().to_exex_notification()
371 }
372
373 pub fn to_revert_notification_spec(&self) -> NotificationSpec {
375 NotificationSpec { old: vec![self.clone()], new: vec![] }
376 }
377
378 pub fn assert_conforms<C: Extractable>(&self, extracts: &Extracts<'_, C>) {
380 if let Some(ru_block) = &self.ru_block {
381 ru_block.assert_conforms(extracts);
382 }
383
384 let mut enters = extracts.enters();
385 let mut enter_tokens = extracts.enter_tokens();
386 let mut transacts = extracts.transacts();
387
388 let mut context = AggregateFills::new();
389
390 for event in self.events.iter() {
391 match event {
392 Events::Enter(e) => {
393 assert_eq!(&(enters.next().expect("iter ended early")), e);
394 }
395 Events::EnterToken(e) => {
396 assert_eq!(&(enter_tokens.next().expect("iter ended early")), e);
397 }
398 Events::Transact(e) => {
399 assert_eq!(transacts.next().expect("iter ended early"), e);
400 }
401 Events::Filled(e) => {
402 context.add_fill(self.constants.host_chain_id(), e);
403 }
404 Events::BlockSubmitted(_) => {}
405 }
406 }
407 assert!(enters.next().is_none());
408 assert!(enter_tokens.next().is_none());
409 assert!(transacts.next().is_none());
410 assert_eq!(extracts.aggregate_fills(), context);
411 }
412}
413
414impl TryFrom<KnownChains> for HostBlockSpec {
415 type Error = ParseChainError;
416
417 fn try_from(chain: KnownChains) -> Result<Self, Self::Error> {
418 match chain {
419 KnownChains::Mainnet => Ok(Self::mainnet()),
420 KnownChains::Parmigiana => Ok(Self::parmigiana()),
421 KnownChains::Gouda => Ok(Self::gouda()),
422 #[allow(deprecated)]
423 KnownChains::Pecorino => Ok(Self::pecorino()),
424 KnownChains::Test => Ok(Self::test()),
425 }
426 }
427}
428
429impl FromStr for HostBlockSpec {
430 type Err = ParseChainError;
431
432 fn from_str(s: &str) -> Result<Self, Self::Err> {
433 s.parse::<KnownChains>()?.try_into()
434 }
435}
436
437fn to_receipt<T>(address: Address, t: &T) -> ReceiptEnvelope
438where
439 for<'a> &'a T: Into<LogData>,
440{
441 let log = Log { address, data: t.into() };
442 ReceiptEnvelope::Eip1559(
443 Receipt { status: true.into(), cumulative_gas_used: 30_000, logs: vec![log] }.into(),
444 )
445}