1use alloy::primitives::B256;
2use alloy::rpc::types::TransactionReceipt;
3use alloy::sol_types::SolEventInterface;
4use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable};
5use bon::bon;
6use bytes::Bytes;
7use serde::{Deserialize, Serialize};
8use std::convert::From;
9
10use crate::eth::{self, ArkivABI};
11
12#[derive(Debug, Clone, Serialize, Deserialize, RlpEncodable, RlpDecodable)]
15pub struct Annotation<T> {
16 pub key: Key,
18 pub value: T,
20}
21
22impl<T> Annotation<T> {
23 pub fn new<K, V>(key: K, value: V) -> Self
26 where
27 K: Into<Key>,
28 V: Into<T>,
29 {
30 Annotation {
31 key: key.into(),
32 value: value.into(),
33 }
34 }
35}
36
37pub type StringAnnotation = Annotation<String>;
39
40pub type NumericAnnotation = Annotation<u64>;
42
43pub type Hash = B256;
45
46pub type Key = String;
48
49#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
55#[rlp(trailing)]
56pub struct Create {
57 pub btl: u64,
59 pub data: Bytes,
61 pub string_annotations: Vec<StringAnnotation>,
63 pub numeric_annotations: Vec<NumericAnnotation>,
65}
66
67#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
73#[rlp(trailing)]
74pub struct Update {
75 pub entity_key: Hash,
77 pub btl: u64,
79 pub data: Bytes,
81 pub string_annotations: Vec<StringAnnotation>,
83 pub numeric_annotations: Vec<NumericAnnotation>,
85}
86
87pub type ArkivDelete = Hash;
89
90#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
96pub struct Extend {
97 pub entity_key: Hash,
99 pub number_of_blocks: u64,
101}
102
103#[derive(Debug, Clone)]
106pub struct ArkivTransaction {
107 pub encodable: EncodableArkivTransaction,
108 pub gas_limit: Option<u64>,
109 pub max_priority_fee_per_gas: Option<u128>,
110 pub max_fee_per_gas: Option<u128>,
111}
112
113#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable)]
115pub struct EncodableArkivTransaction {
116 pub creates: Vec<Create>,
118 pub updates: Vec<Update>,
120 pub deletes: Vec<ArkivDelete>,
122 pub extensions: Vec<Extend>,
124}
125
126#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
132pub struct Entity {
133 pub data: String,
135 pub btl: u64,
137 pub string_annotations: Vec<StringAnnotation>,
139 pub numeric_annotations: Vec<NumericAnnotation>,
141}
142
143#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize)]
146pub struct EntityResult {
147 pub entity_key: Hash,
149 pub expiration_block: u64,
151}
152
153#[derive(Debug)]
156pub struct ExtendResult {
157 pub entity_key: Hash,
159 pub old_expiration_block: u64,
161 pub new_expiration_block: u64,
163}
164
165#[derive(Debug)]
168pub struct DeleteResult {
169 pub entity_key: Hash,
171}
172
173#[derive(Debug, Default)]
174pub struct TransactionResult {
175 pub creates: Vec<EntityResult>,
176 pub updates: Vec<EntityResult>,
177 pub deletes: Vec<DeleteResult>,
178 pub extensions: Vec<ExtendResult>,
179}
180
181impl TryFrom<TransactionReceipt> for TransactionResult {
182 type Error = eth::Error;
183
184 fn try_from(receipt: TransactionReceipt) -> Result<Self, Self::Error> {
185 if !receipt.status() {
186 return Err(Self::Error::TransactionReceiptError(format!(
187 "Transaction {} failed: {:?}",
188 receipt.transaction_hash, receipt
189 )));
190 }
191
192 let mut txres = TransactionResult::default();
193 receipt.logs().iter().cloned().try_for_each(|log| {
194 let log: alloy::primitives::Log = log.into();
195 let parsed = ArkivABI::ArkivABIEvents::decode_log(&log).map_err(|e| {
196 Self::Error::UnexpectedLogDataError(format!("Error decoding event log: {e}"))
197 })?;
198 match parsed.data {
199 ArkivABI::ArkivABIEvents::GolemBaseStorageEntityCreated(data) => {
200 txres.creates.push(EntityResult {
201 entity_key: data.entityKey.into(),
202 expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
203 });
204 Ok(())
205 }
206 ArkivABI::ArkivABIEvents::GolemBaseStorageEntityUpdated(data) => {
207 txres.updates.push(EntityResult {
208 entity_key: data.entityKey.into(),
209 expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
210 });
211 Ok(())
212 }
213 ArkivABI::ArkivABIEvents::GolemBaseStorageEntityDeleted(data) => {
214 txres.deletes.push(DeleteResult {
215 entity_key: data.entityKey.into(),
216 });
217 Ok(())
218 }
219 ArkivABI::ArkivABIEvents::GolemBaseStorageEntityBTLExtended(data) => {
220 txres.extensions.push(ExtendResult {
221 entity_key: data.entityKey.into(),
222 old_expiration_block: data
223 .oldExpirationBlock
224 .try_into()
225 .unwrap_or_default(),
226 new_expiration_block: data
227 .newExpirationBlock
228 .try_into()
229 .unwrap_or_default(),
230 });
231 Ok(())
232 }
233 }
234 })?;
235
236 Ok(txres)
237 }
238}
239
240impl Create {
241 pub fn new(payload: Vec<u8>, btl: u64) -> Self {
244 Self {
245 btl,
246 data: Bytes::from(payload),
247 string_annotations: Vec::new(),
248 numeric_annotations: Vec::new(),
249 }
250 }
251
252 pub fn from_string<T: Into<String>>(payload: T, btl: u64) -> Self {
254 Self {
255 btl,
256 data: Bytes::from(payload.into().into_bytes()),
257 string_annotations: Vec::new(),
258 numeric_annotations: Vec::new(),
259 }
260 }
261
262 pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
265 self.string_annotations.push(Annotation {
266 key: key.into(),
267 value: value.into(),
268 });
269 self
270 }
271
272 pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
275 self.numeric_annotations.push(Annotation {
276 key: key.into(),
277 value,
278 });
279 self
280 }
281}
282
283impl Update {
284 pub fn new(entity_key: B256, payload: Vec<u8>, btl: u64) -> Self {
287 Self {
288 entity_key,
289 btl,
290 data: Bytes::from(payload),
291 string_annotations: Vec::new(),
292 numeric_annotations: Vec::new(),
293 }
294 }
295
296 pub fn from_string<T: Into<String>>(entity_key: B256, payload: T, btl: u64) -> Self {
298 Self {
299 entity_key,
300 btl,
301 data: Bytes::from(payload.into().into_bytes()),
302 string_annotations: Vec::new(),
303 numeric_annotations: Vec::new(),
304 }
305 }
306
307 pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
310 self.string_annotations.push(Annotation {
311 key: key.into(),
312 value: value.into(),
313 });
314 self
315 }
316
317 pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
320 self.numeric_annotations.push(Annotation {
321 key: key.into(),
322 value,
323 });
324 self
325 }
326}
327
328impl Extend {
329 pub fn new(entity_key: B256, number_of_blocks: u64) -> Self {
332 Self {
333 entity_key,
334 number_of_blocks,
335 }
336 }
337}
338
339#[bon]
340impl ArkivTransaction {
341 #[builder]
342 pub fn builder(
343 creates: Option<Vec<Create>>,
344 updates: Option<Vec<Update>>,
345 deletes: Option<Vec<ArkivDelete>>,
346 extensions: Option<Vec<Extend>>,
347 gas_limit: Option<u64>,
348 max_priority_fee_per_gas: Option<u128>,
349 max_fee_per_gas: Option<u128>,
350 ) -> Self {
351 Self {
352 encodable: EncodableArkivTransaction {
353 creates: creates.unwrap_or_default(),
354 updates: updates.unwrap_or_default(),
355 deletes: deletes.unwrap_or_default(),
356 extensions: extensions.unwrap_or_default(),
357 },
358 gas_limit,
359 max_priority_fee_per_gas,
360 max_fee_per_gas,
361 }
362 }
363}
364
365impl ArkivTransaction {
366 pub fn encoded(&self) -> Vec<u8> {
369 let mut encoded = Vec::new();
370 self.encodable.encode(&mut encoded);
371 encoded
372 }
373}
374
375#[cfg(test)]
377mod tests {
378 use super::*;
379 use alloy::primitives::B256;
380 use hex;
381
382 #[test]
383 fn test_empty_transaction() {
384 let tx = ArkivTransaction::builder().build();
385 assert_eq!(hex::encode(tx.encoded()), "c4c0c0c0c0");
386 }
387
388 #[test]
389 fn test_create_without_annotations() {
390 let create = Create::new(b"test payload".to_vec(), 1000);
391
392 let tx = ArkivTransaction::builder().creates(vec![create]).build();
393
394 assert_eq!(
395 hex::encode(tx.encoded()),
396 "d7d3d28203e88c74657374207061796c6f6164c0c0c0c0c0"
397 );
398 }
399
400 #[test]
401 fn test_create_with_annotations() {
402 let create = Create::new(b"test payload".to_vec(), 1000)
403 .annotate_string("foo", "bar")
404 .annotate_number("baz", 42);
405
406 let tx = ArkivTransaction::builder().creates(vec![create]).build();
407
408 assert_eq!(
409 hex::encode(tx.encoded()),
410 "e6e2e18203e88c74657374207061796c6f6164c9c883666f6f83626172c6c58362617a2ac0c0c0"
411 );
412 }
413
414 #[test]
415 fn test_update_with_annotations() {
416 let update = Update::new(
417 B256::from_slice(&[1; 32]),
418 b"updated payload".to_vec(),
419 2000,
420 )
421 .annotate_string("status", "active")
422 .annotate_number("version", 2);
423
424 let tx = ArkivTransaction::builder().updates(vec![update]).build();
425
426 assert_eq!(
427 hex::encode(tx.encoded()),
428 "f856c0f851f84fa001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164cfce8673746174757386616374697665cac98776657273696f6e02c0c0"
429 );
430 }
431
432 #[test]
433 fn test_delete_operation() {
434 let tx = ArkivTransaction::builder()
435 .deletes(vec![B256::from_slice(&[2; 32])])
436 .build();
437
438 assert_eq!(
439 hex::encode(tx.encoded()),
440 "e5c0c0e1a00202020202020202020202020202020202020202020202020202020202020202c0"
441 );
442 }
443
444 #[test]
445 fn test_extend_btl() {
446 let tx = ArkivTransaction::builder()
447 .extensions(vec![Extend {
448 entity_key: B256::from_slice(&[3; 32]),
449 number_of_blocks: 500,
450 }])
451 .build();
452
453 assert_eq!(
454 hex::encode(tx.encoded()),
455 "e9c0c0c0e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
456 );
457 }
458
459 #[test]
460 fn test_mixed_operations() {
461 let create = Create::new(b"test payload".to_vec(), 1000).annotate_string("type", "test");
462 let update = Update::new(
463 B256::from_slice(&[1; 32]),
464 b"updated payload".to_vec(),
465 2000,
466 );
467 let tx = ArkivTransaction::builder()
468 .creates(vec![create])
469 .updates(vec![update])
470 .deletes(vec![B256::from_slice(&[2; 32])])
471 .extensions(vec![Extend {
472 entity_key: B256::from_slice(&[3; 32]),
473 number_of_blocks: 500,
474 }])
475 .build();
476
477 assert_eq!(
478 hex::encode(tx.encoded()),
479 "f89fdedd8203e88c74657374207061796c6f6164cbca84747970658474657374c0f7f6a001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164c0c0e1a00202020202020202020202020202020202020202020202020202020202020202e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
480 );
481 }
482}