1use alloy::primitives::B256;
2use alloy::rpc::types::TransactionReceipt;
3use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable};
4use alloy_sol_types::SolEventInterface;
5use bon::bon;
6use bytes::Bytes;
7use serde::{Deserialize, Serialize};
8use std::convert::From;
9
10use crate::eth::{self, GolemBaseABI};
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)]
52#[rlp(trailing)]
53pub struct Create {
54 pub btl: u64,
56 pub data: Bytes,
58 pub string_annotations: Vec<StringAnnotation>,
60 pub numeric_annotations: Vec<NumericAnnotation>,
62}
63
64#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
67#[rlp(trailing)]
68pub struct Update {
69 pub entity_key: Hash,
71 pub btl: u64,
73 pub data: Bytes,
75 pub string_annotations: Vec<StringAnnotation>,
77 pub numeric_annotations: Vec<NumericAnnotation>,
79}
80
81pub type GolemBaseDelete = Hash;
83
84#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
87pub struct Extend {
88 pub entity_key: Hash,
90 pub number_of_blocks: u64,
92}
93
94#[derive(Debug, Clone)]
97pub struct GolemBaseTransaction {
98 pub encodable: EncodableGolemBaseTransaction,
99
100 pub gas_limit: Option<u64>,
101 pub max_priority_fee_per_gas: Option<u128>,
102 pub max_fee_per_gas: Option<u128>,
103}
104
105#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable)]
107pub struct EncodableGolemBaseTransaction {
108 pub creates: Vec<Create>,
110 pub updates: Vec<Update>,
112 pub deletes: Vec<GolemBaseDelete>,
114 pub extensions: Vec<Extend>,
116}
117
118#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
121pub struct Entity {
122 pub data: String,
124 pub btl: u64,
126 pub string_annotations: Vec<StringAnnotation>,
128 pub numeric_annotations: Vec<NumericAnnotation>,
130}
131
132#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize)]
135pub struct EntityResult {
136 pub entity_key: Hash,
138 pub expiration_block: u64,
140}
141
142#[derive(Debug)]
145pub struct ExtendResult {
146 pub entity_key: Hash,
148 pub old_expiration_block: u64,
150 pub new_expiration_block: u64,
152}
153
154#[derive(Debug)]
157pub struct DeleteResult {
158 pub entity_key: Hash,
160}
161
162#[derive(Debug, Default)]
163pub struct TransactionResult {
164 pub creates: Vec<EntityResult>,
165 pub updates: Vec<EntityResult>,
166 pub deletes: Vec<DeleteResult>,
167 pub extensions: Vec<ExtendResult>,
168}
169
170impl TryFrom<TransactionReceipt> for TransactionResult {
171 type Error = eth::Error;
172
173 fn try_from(receipt: TransactionReceipt) -> Result<Self, Self::Error> {
174 if !receipt.status() {
175 return Err(Self::Error::TransactionReceiptError(format!(
176 "Transaction {} failed: {:?}",
177 receipt.transaction_hash, receipt
178 )));
179 }
180
181 let mut txres = TransactionResult::default();
182 receipt.logs().iter().cloned().try_for_each(|log| {
183 let log: alloy::primitives::Log = log.into();
184 let parsed = GolemBaseABI::GolemBaseABIEvents::decode_log(&log).map_err(|e| {
185 Self::Error::UnexpectedLogDataError(format!("Error decoding event log: {}", e))
186 })?;
187 match parsed.data {
188 GolemBaseABI::GolemBaseABIEvents::GolemBaseStorageEntityCreated(data) => {
189 txres.creates.push(EntityResult {
190 entity_key: data.entityKey.into(),
191 expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
192 });
193 Ok(())
194 }
195 GolemBaseABI::GolemBaseABIEvents::GolemBaseStorageEntityUpdated(data) => {
196 txres.updates.push(EntityResult {
197 entity_key: data.entityKey.into(),
198 expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
199 });
200 Ok(())
201 }
202 GolemBaseABI::GolemBaseABIEvents::GolemBaseStorageEntityDeleted(data) => {
203 txres.deletes.push(DeleteResult {
204 entity_key: data.entityKey.into(),
205 });
206 Ok(())
207 }
208 GolemBaseABI::GolemBaseABIEvents::GolemBaseStorageEntityBTLExtended(data) => {
209 txres.extensions.push(ExtendResult {
210 entity_key: data.entityKey.into(),
211 old_expiration_block: data
212 .oldExpirationBlock
213 .try_into()
214 .unwrap_or_default(),
215 new_expiration_block: data
216 .newExpirationBlock
217 .try_into()
218 .unwrap_or_default(),
219 });
220 Ok(())
221 }
222 }
223 })?;
224
225 Ok(txres)
226 }
227}
228
229impl Create {
230 pub fn new(payload: Vec<u8>, btl: u64) -> Self {
233 Self {
234 btl,
235 data: Bytes::from(payload),
236 string_annotations: Vec::new(),
237 numeric_annotations: Vec::new(),
238 }
239 }
240
241 pub fn from_string<T: Into<String>>(payload: T, btl: u64) -> Self {
243 Self {
244 btl,
245 data: Bytes::from(payload.into().into_bytes()),
246 string_annotations: Vec::new(),
247 numeric_annotations: Vec::new(),
248 }
249 }
250
251 pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
254 self.string_annotations.push(Annotation {
255 key: key.into(),
256 value: value.into(),
257 });
258 self
259 }
260
261 pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
264 self.numeric_annotations.push(Annotation {
265 key: key.into(),
266 value,
267 });
268 self
269 }
270}
271
272impl Update {
273 pub fn new(entity_key: B256, payload: Vec<u8>, btl: u64) -> Self {
276 Self {
277 entity_key,
278 btl,
279 data: Bytes::from(payload),
280 string_annotations: Vec::new(),
281 numeric_annotations: Vec::new(),
282 }
283 }
284
285 pub fn from_string<T: Into<String>>(entity_key: B256, payload: T, btl: u64) -> Self {
287 Self {
288 entity_key,
289 btl,
290 data: Bytes::from(payload.into().into_bytes()),
291 string_annotations: Vec::new(),
292 numeric_annotations: Vec::new(),
293 }
294 }
295
296 pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
299 self.string_annotations.push(Annotation {
300 key: key.into(),
301 value: value.into(),
302 });
303 self
304 }
305
306 pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
309 self.numeric_annotations.push(Annotation {
310 key: key.into(),
311 value,
312 });
313 self
314 }
315}
316
317impl Extend {
318 pub fn new(entity_key: B256, number_of_blocks: u64) -> Self {
321 Self {
322 entity_key,
323 number_of_blocks,
324 }
325 }
326}
327
328#[bon]
329impl GolemBaseTransaction {
330 #[builder]
331 pub fn builder(
332 creates: Option<Vec<Create>>,
333 updates: Option<Vec<Update>>,
334 deletes: Option<Vec<GolemBaseDelete>>,
335 extensions: Option<Vec<Extend>>,
336 gas_limit: Option<u64>,
337 max_priority_fee_per_gas: Option<u128>,
338 max_fee_per_gas: Option<u128>,
339 ) -> Self {
340 Self {
341 encodable: EncodableGolemBaseTransaction {
342 creates: creates.unwrap_or_default(),
343 updates: updates.unwrap_or_default(),
344 deletes: deletes.unwrap_or_default(),
345 extensions: extensions.unwrap_or_default(),
346 },
347 gas_limit,
348 max_priority_fee_per_gas,
349 max_fee_per_gas,
350 }
351 }
352}
353
354impl GolemBaseTransaction {
355 pub fn encoded(&self) -> Vec<u8> {
358 let mut encoded = Vec::new();
359 self.encodable.encode(&mut encoded);
360 encoded
361 }
362}
363
364#[cfg(test)]
366mod tests {
367 use super::*;
368 use alloy::primitives::B256;
369 use hex;
370
371 #[test]
372 fn test_empty_transaction() {
373 let tx = GolemBaseTransaction::builder().build();
374 assert_eq!(hex::encode(tx.encoded()), "c4c0c0c0c0");
375 }
376
377 #[test]
378 fn test_create_without_annotations() {
379 let create = Create::new(b"test payload".to_vec(), 1000);
380
381 let tx = GolemBaseTransaction::builder()
382 .creates(vec![create])
383 .build();
384
385 assert_eq!(
386 hex::encode(tx.encoded()),
387 "d7d3d28203e88c74657374207061796c6f6164c0c0c0c0c0"
388 );
389 }
390
391 #[test]
392 fn test_create_with_annotations() {
393 let create = Create::new(b"test payload".to_vec(), 1000)
394 .annotate_string("foo", "bar")
395 .annotate_number("baz", 42);
396
397 let tx = GolemBaseTransaction::builder()
398 .creates(vec![create])
399 .build();
400
401 assert_eq!(
402 hex::encode(tx.encoded()),
403 "e6e2e18203e88c74657374207061796c6f6164c9c883666f6f83626172c6c58362617a2ac0c0c0"
404 );
405 }
406
407 #[test]
408 fn test_update_with_annotations() {
409 let update = Update::new(
410 B256::from_slice(&[1; 32]),
411 b"updated payload".to_vec(),
412 2000,
413 )
414 .annotate_string("status", "active")
415 .annotate_number("version", 2);
416
417 let tx = GolemBaseTransaction::builder()
418 .updates(vec![update])
419 .build();
420
421 assert_eq!(
422 hex::encode(tx.encoded()),
423 "f856c0f851f84fa001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164cfce8673746174757386616374697665cac98776657273696f6e02c0c0"
424 );
425 }
426
427 #[test]
428 fn test_delete_operation() {
429 let tx = GolemBaseTransaction::builder()
430 .deletes(vec![B256::from_slice(&[2; 32])])
431 .build();
432
433 assert_eq!(
434 hex::encode(tx.encoded()),
435 "e5c0c0e1a00202020202020202020202020202020202020202020202020202020202020202c0"
436 );
437 }
438
439 #[test]
440 fn test_extend_btl() {
441 let tx = GolemBaseTransaction::builder()
442 .extensions(vec![Extend {
443 entity_key: B256::from_slice(&[3; 32]),
444 number_of_blocks: 500,
445 }])
446 .build();
447
448 assert_eq!(
449 hex::encode(tx.encoded()),
450 "e9c0c0c0e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
451 );
452 }
453
454 #[test]
455 fn test_mixed_operations() {
456 let create = Create::new(b"test payload".to_vec(), 1000).annotate_string("type", "test");
457 let update = Update::new(
458 B256::from_slice(&[1; 32]),
459 b"updated payload".to_vec(),
460 2000,
461 );
462 let tx = GolemBaseTransaction::builder()
463 .creates(vec![create])
464 .updates(vec![update])
465 .deletes(vec![B256::from_slice(&[2; 32])])
466 .extensions(vec![Extend {
467 entity_key: B256::from_slice(&[3; 32]),
468 number_of_blocks: 500,
469 }])
470 .build();
471
472 assert_eq!(
473 hex::encode(tx.encoded()),
474 "f89fdedd8203e88c74657374207061796c6f6164cbca84747970658474657374c0f7f6a001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164c0c0e1a00202020202020202020202020202020202020202020202020202020202020202e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
475 );
476 }
477}