1use alloy_primitives::{keccak256, Address, B256, U256};
2use revm::Database;
3
4use arb_storage::{
5 initialize_queue, open_queue, Queue, Storage, StorageBackedAddress, StorageBackedAddressOrNil,
6 StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64,
7};
8
9pub const RETRYABLE_LIFETIME_SECONDS: u64 = 7 * 24 * 60 * 60; pub const RETRYABLE_REAP_PRICE: u64 = 58000;
11
12const TIMEOUT_QUEUE_KEY: &[u8] = &[0];
13const CALLDATA_KEY: &[u8] = &[1];
14
15const NUM_TRIES_OFFSET: u64 = 0;
17const FROM_OFFSET: u64 = 1;
18const TO_OFFSET: u64 = 2;
19const CALLVALUE_OFFSET: u64 = 3;
20const BENEFICIARY_OFFSET: u64 = 4;
21const TIMEOUT_OFFSET: u64 = 5;
22const TIMEOUT_WINDOWS_LEFT_OFFSET: u64 = 6;
23
24pub struct RetryableState<D> {
26 retryables: Storage<D>,
27 pub timeout_queue: Queue<D>,
28}
29
30pub struct Retryable<D> {
32 pub id: B256,
33 #[allow(dead_code)]
34 backing_storage: Storage<D>,
35 num_tries: StorageBackedUint64<D>,
36 from: StorageBackedAddress<D>,
37 to: StorageBackedAddressOrNil<D>,
38 callvalue: StorageBackedBigUint<D>,
39 beneficiary: StorageBackedAddress<D>,
40 calldata: StorageBackedBytes<D>,
41 timeout: StorageBackedUint64<D>,
42 timeout_windows_left: StorageBackedUint64<D>,
43}
44
45pub fn initialize_retryable_state<D: Database>(sto: &Storage<D>) -> Result<(), ()> {
46 initialize_queue(&sto.open_sub_storage(TIMEOUT_QUEUE_KEY))
47}
48
49pub fn open_retryable_state<D: Database>(sto: Storage<D>) -> RetryableState<D> {
50 let queue_sto = sto.open_sub_storage(TIMEOUT_QUEUE_KEY);
51 RetryableState {
52 timeout_queue: open_queue(queue_sto),
53 retryables: sto,
54 }
55}
56
57impl<D: Database> RetryableState<D> {
58 pub fn initialize(sto: &Storage<D>) -> Result<(), ()> {
59 initialize_retryable_state(sto)
60 }
61
62 pub fn open(sto: Storage<D>) -> Self {
63 open_retryable_state(sto)
64 }
65
66 pub fn create_retryable(
68 &self,
69 id: B256,
70 timeout: u64,
71 from: Address,
72 to: Option<Address>,
73 callvalue: U256,
74 beneficiary: Address,
75 calldata: &[u8],
76 ) -> Result<Retryable<D>, ()> {
77 let ret = self.internal_open(id);
78 ret.num_tries.set(0)?;
79 ret.from.set(from)?;
80 ret.to.set(to)?;
81 ret.callvalue.set(callvalue)?;
82 ret.beneficiary.set(beneficiary)?;
83 ret.calldata.set(calldata)?;
84 ret.timeout.set(timeout)?;
85 ret.timeout_windows_left.set(0)?;
86 self.timeout_queue.put(id)?;
87 Ok(ret)
88 }
89
90 pub fn open_retryable(
92 &self,
93 id: B256,
94 current_timestamp: u64,
95 ) -> Result<Option<Retryable<D>>, ()> {
96 let sto = self.retryables.open_sub_storage(id.as_slice());
97 let timeout_storage =
98 StorageBackedUint64::new(sto.state_ptr(), sto.base_key(), TIMEOUT_OFFSET);
99 let timeout = timeout_storage.get()?;
100 if timeout == 0 || timeout < current_timestamp {
101 return Ok(None);
102 }
103 Ok(Some(self.internal_open(id)))
104 }
105
106 pub fn retryable_size_bytes(&self, id: B256, current_time: u64) -> Result<u64, ()> {
108 let retryable = self.open_retryable(id, current_time)?;
109 match retryable {
110 None => Ok(0),
111 Some(ret) => {
112 let size = ret.calldata_size()?;
113 let calldata_slots = 32 + 32 * words_for_bytes(size);
114 Ok(6 * 32 + calldata_slots)
115 }
116 }
117 }
118
119 pub fn delete_retryable<F, G>(
122 &self,
123 id: B256,
124 mut transfer_fn: F,
125 mut balance_of: G,
126 ) -> Result<bool, ()>
127 where
128 F: FnMut(Address, Address, U256) -> Result<(), ()>,
129 G: FnMut(Address) -> U256,
130 {
131 let ret_storage = self.retryables.open_sub_storage(id.as_slice());
132 let timeout_val = ret_storage.get_by_uint64(TIMEOUT_OFFSET)?;
133 if timeout_val == B256::ZERO {
134 return Ok(false);
135 }
136
137 let beneficiary_val = ret_storage.get_by_uint64(BENEFICIARY_OFFSET)?;
139 let escrow_address = retryable_escrow_address(id);
140 let beneficiary_address = Address::from_slice(&beneficiary_val[12..]);
141 let amount = balance_of(escrow_address);
142 transfer_fn(escrow_address, beneficiary_address, amount)?;
143
144 let _ = ret_storage.set_by_uint64(NUM_TRIES_OFFSET, B256::ZERO);
146 let _ = ret_storage.set_by_uint64(FROM_OFFSET, B256::ZERO);
147 let _ = ret_storage.set_by_uint64(TO_OFFSET, B256::ZERO);
148 let _ = ret_storage.set_by_uint64(CALLVALUE_OFFSET, B256::ZERO);
149 let _ = ret_storage.set_by_uint64(BENEFICIARY_OFFSET, B256::ZERO);
150 let _ = ret_storage.set_by_uint64(TIMEOUT_OFFSET, B256::ZERO);
151 let _ = ret_storage.set_by_uint64(TIMEOUT_WINDOWS_LEFT_OFFSET, B256::ZERO);
152 let bytes_storage = StorageBackedBytes::new(ret_storage.open_sub_storage(CALLDATA_KEY));
153 bytes_storage.clear()?;
154 Ok(true)
155 }
156
157 pub fn keepalive(
159 &self,
160 ticket_id: B256,
161 current_timestamp: u64,
162 limit_before_add: u64,
163 _time_to_add: u64,
164 ) -> Result<u64, ()> {
165 let retryable = self.open_retryable(ticket_id, current_timestamp)?;
166 let retryable = retryable.ok_or(())?;
167 let timeout = retryable.calculate_timeout()?;
168 if timeout > limit_before_add {
169 return Err(());
170 }
171 self.timeout_queue.put(retryable.id)?;
172 retryable.increment_timeout_windows()?;
173 let new_timeout = timeout + RETRYABLE_LIFETIME_SECONDS;
174 Ok(new_timeout)
176 }
177
178 pub fn try_to_reap_one_retryable<F, G>(
180 &self,
181 current_timestamp: u64,
182 mut transfer_fn: F,
183 mut balance_of: G,
184 ) -> Result<(), ()>
185 where
186 F: FnMut(Address, Address, U256) -> Result<(), ()>,
187 G: FnMut(Address) -> U256,
188 {
189 let id = self.timeout_queue.peek()?;
190 let id = match id {
191 None => return Ok(()),
192 Some(id) => id,
193 };
194
195 let ret_storage = self.retryables.open_sub_storage(id.as_slice());
196 let timeout_storage = StorageBackedUint64::new(
197 ret_storage.state_ptr(),
198 ret_storage.base_key(),
199 TIMEOUT_OFFSET,
200 );
201 let timeout = timeout_storage.get()?;
202
203 if timeout == 0 {
204 let _ = self.timeout_queue.get()?;
206 return Ok(());
207 }
208
209 let windows_left_storage = StorageBackedUint64::new(
210 ret_storage.state_ptr(),
211 ret_storage.base_key(),
212 TIMEOUT_WINDOWS_LEFT_OFFSET,
213 );
214 let windows_left = windows_left_storage.get()?;
215
216 if timeout >= current_timestamp {
217 return Ok(());
218 }
219
220 let _ = self.timeout_queue.get()?;
222
223 if windows_left == 0 {
224 self.delete_retryable(id, &mut transfer_fn, &mut balance_of)?;
226 return Ok(());
227 }
228
229 timeout_storage.set(timeout + RETRYABLE_LIFETIME_SECONDS)?;
231 windows_left_storage.set(windows_left - 1)?;
232 Ok(())
233 }
234
235 fn internal_open(&self, id: B256) -> Retryable<D> {
236 let sto = self.retryables.open_sub_storage(id.as_slice());
237 let state = sto.state_ptr();
238 let base_key = sto.base_key();
239 Retryable {
240 id,
241 num_tries: StorageBackedUint64::new(state, base_key, NUM_TRIES_OFFSET),
242 from: StorageBackedAddress::new(state, base_key, FROM_OFFSET),
243 to: StorageBackedAddressOrNil::new(state, base_key, TO_OFFSET),
244 callvalue: StorageBackedBigUint::new(state, base_key, CALLVALUE_OFFSET),
245 beneficiary: StorageBackedAddress::new(state, base_key, BENEFICIARY_OFFSET),
246 calldata: StorageBackedBytes::new(sto.open_sub_storage(CALLDATA_KEY)),
247 timeout: StorageBackedUint64::new(state, base_key, TIMEOUT_OFFSET),
248 timeout_windows_left: StorageBackedUint64::new(
249 state,
250 base_key,
251 TIMEOUT_WINDOWS_LEFT_OFFSET,
252 ),
253 backing_storage: sto,
254 }
255 }
256}
257
258impl<D: Database> Retryable<D> {
259 pub fn num_tries(&self) -> Result<u64, ()> {
260 self.num_tries.get()
261 }
262
263 pub fn increment_num_tries(&self) -> Result<u64, ()> {
264 let current = self.num_tries.get()?;
265 let new_val = current + 1;
266 self.num_tries.set(new_val)?;
267 Ok(new_val)
268 }
269
270 pub fn beneficiary(&self) -> Result<Address, ()> {
271 self.beneficiary.get()
272 }
273
274 pub fn calculate_timeout(&self) -> Result<u64, ()> {
275 let timeout = self.timeout.get()?;
276 let windows = self.timeout_windows_left.get()?;
277 Ok(timeout + windows * RETRYABLE_LIFETIME_SECONDS)
278 }
279
280 pub fn set_timeout(&self, val: u64) -> Result<(), ()> {
281 self.timeout.set(val)
282 }
283
284 pub fn timeout_windows_left(&self) -> Result<u64, ()> {
285 self.timeout_windows_left.get()
286 }
287
288 fn increment_timeout_windows(&self) -> Result<u64, ()> {
289 let current = self.timeout_windows_left.get()?;
290 let new_val = current + 1;
291 self.timeout_windows_left.set(new_val)?;
292 Ok(new_val)
293 }
294
295 pub fn from(&self) -> Result<Address, ()> {
296 self.from.get()
297 }
298
299 pub fn to(&self) -> Result<Option<Address>, ()> {
300 self.to.get()
301 }
302
303 pub fn callvalue(&self) -> Result<U256, ()> {
304 self.callvalue.get()
305 }
306
307 pub fn calldata(&self) -> Result<Vec<u8>, ()> {
308 self.calldata.get()
309 }
310
311 pub fn calldata_size(&self) -> Result<u64, ()> {
312 self.calldata.size()
313 }
314
315 pub fn make_tx(
318 &self,
319 chain_id: U256,
320 nonce: u64,
321 gas_fee_cap: U256,
322 gas: u64,
323 ticket_id: B256,
324 refund_to: Address,
325 max_refund: U256,
326 submission_fee_refund: U256,
327 ) -> Result<arb_alloy_consensus::tx::ArbRetryTx, ()> {
328 Ok(arb_alloy_consensus::tx::ArbRetryTx {
329 chain_id,
330 nonce,
331 from: self.from()?,
332 gas_fee_cap,
333 gas,
334 to: self.to()?,
335 value: self.callvalue()?,
336 data: self.calldata()?.into(),
337 ticket_id,
338 refund_to,
339 max_refund,
340 submission_fee_refund,
341 })
342 }
343}
344
345pub fn retryable_escrow_address(ticket_id: B256) -> Address {
347 let mut data = Vec::with_capacity(16 + 32);
348 data.extend_from_slice(b"retryable escrow");
349 data.extend_from_slice(ticket_id.as_slice());
350 let hash = keccak256(&data);
351 Address::from_slice(&hash[12..])
352}
353
354pub fn retryable_submission_fee(calldata_length: usize, l1_base_fee: U256) -> U256 {
356 l1_base_fee * U256::from(1400 + 6 * calldata_length as u64)
357}
358
359fn words_for_bytes(bytes: u64) -> u64 {
361 bytes.div_ceil(32)
362}