use alloy::primitives::B256;
use alloy::rpc::types::TransactionReceipt;
use alloy::sol_types::SolEventInterface;
use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable};
use bon::bon;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::convert::From;
use crate::eth::{self, ArkivABI};
#[derive(Debug, Clone, Serialize, Deserialize, RlpEncodable, RlpDecodable)]
pub struct Annotation<T> {
pub key: Key,
pub value: T,
}
impl<T> Annotation<T> {
pub fn new<K, V>(key: K, value: V) -> Self
where
K: Into<Key>,
V: Into<T>,
{
Annotation {
key: key.into(),
value: value.into(),
}
}
}
pub type StringAnnotation = Annotation<String>;
pub type NumericAnnotation = Annotation<u64>;
pub type Hash = B256;
pub type Key = String;
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
#[rlp(trailing)]
pub struct Create {
pub btl: u64,
pub data: Bytes,
pub string_annotations: Vec<StringAnnotation>,
pub numeric_annotations: Vec<NumericAnnotation>,
}
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
#[rlp(trailing)]
pub struct Update {
pub entity_key: Hash,
pub btl: u64,
pub data: Bytes,
pub string_annotations: Vec<StringAnnotation>,
pub numeric_annotations: Vec<NumericAnnotation>,
}
pub type ArkivDelete = Hash;
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Deserialize)]
pub struct Extend {
pub entity_key: Hash,
pub number_of_blocks: u64,
}
#[derive(Debug, Clone)]
pub struct ArkivTransaction {
pub encodable: EncodableArkivTransaction,
pub gas_limit: Option<u64>,
pub max_priority_fee_per_gas: Option<u128>,
pub max_fee_per_gas: Option<u128>,
}
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable)]
pub struct EncodableArkivTransaction {
pub creates: Vec<Create>,
pub updates: Vec<Update>,
pub deletes: Vec<ArkivDelete>,
pub extensions: Vec<Extend>,
}
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
pub struct Entity {
pub data: String,
pub btl: u64,
pub string_annotations: Vec<StringAnnotation>,
pub numeric_annotations: Vec<NumericAnnotation>,
}
#[derive(Debug, Clone, Default, RlpEncodable, RlpDecodable, Serialize)]
pub struct EntityResult {
pub entity_key: Hash,
pub expiration_block: u64,
}
#[derive(Debug)]
pub struct ExtendResult {
pub entity_key: Hash,
pub old_expiration_block: u64,
pub new_expiration_block: u64,
}
#[derive(Debug)]
pub struct DeleteResult {
pub entity_key: Hash,
}
#[derive(Debug, Default)]
pub struct TransactionResult {
pub creates: Vec<EntityResult>,
pub updates: Vec<EntityResult>,
pub deletes: Vec<DeleteResult>,
pub extensions: Vec<ExtendResult>,
}
impl TryFrom<TransactionReceipt> for TransactionResult {
type Error = eth::Error;
fn try_from(receipt: TransactionReceipt) -> Result<Self, Self::Error> {
if !receipt.status() {
return Err(Self::Error::TransactionReceiptError(format!(
"Transaction {} failed: {:?}",
receipt.transaction_hash, receipt
)));
}
let mut txres = TransactionResult::default();
receipt.logs().iter().cloned().try_for_each(|log| {
let log: alloy::primitives::Log = log.into();
let parsed = ArkivABI::ArkivABIEvents::decode_log(&log).map_err(|e| {
Self::Error::UnexpectedLogDataError(format!("Error decoding event log: {e}"))
})?;
match parsed.data {
ArkivABI::ArkivABIEvents::GolemBaseStorageEntityCreated(data) => {
txres.creates.push(EntityResult {
entity_key: data.entityKey.into(),
expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
});
Ok(())
}
ArkivABI::ArkivABIEvents::GolemBaseStorageEntityUpdated(data) => {
txres.updates.push(EntityResult {
entity_key: data.entityKey.into(),
expiration_block: data.expirationBlock.try_into().unwrap_or_default(),
});
Ok(())
}
ArkivABI::ArkivABIEvents::GolemBaseStorageEntityDeleted(data) => {
txres.deletes.push(DeleteResult {
entity_key: data.entityKey.into(),
});
Ok(())
}
ArkivABI::ArkivABIEvents::GolemBaseStorageEntityBTLExtended(data) => {
txres.extensions.push(ExtendResult {
entity_key: data.entityKey.into(),
old_expiration_block: data
.oldExpirationBlock
.try_into()
.unwrap_or_default(),
new_expiration_block: data
.newExpirationBlock
.try_into()
.unwrap_or_default(),
});
Ok(())
}
}
})?;
Ok(txres)
}
}
impl Create {
pub fn new(payload: Vec<u8>, btl: u64) -> Self {
Self {
btl,
data: Bytes::from(payload),
string_annotations: Vec::new(),
numeric_annotations: Vec::new(),
}
}
pub fn from_string<T: Into<String>>(payload: T, btl: u64) -> Self {
Self {
btl,
data: Bytes::from(payload.into().into_bytes()),
string_annotations: Vec::new(),
numeric_annotations: Vec::new(),
}
}
pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.string_annotations.push(Annotation {
key: key.into(),
value: value.into(),
});
self
}
pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
self.numeric_annotations.push(Annotation {
key: key.into(),
value,
});
self
}
}
impl Update {
pub fn new(entity_key: B256, payload: Vec<u8>, btl: u64) -> Self {
Self {
entity_key,
btl,
data: Bytes::from(payload),
string_annotations: Vec::new(),
numeric_annotations: Vec::new(),
}
}
pub fn from_string<T: Into<String>>(entity_key: B256, payload: T, btl: u64) -> Self {
Self {
entity_key,
btl,
data: Bytes::from(payload.into().into_bytes()),
string_annotations: Vec::new(),
numeric_annotations: Vec::new(),
}
}
pub fn annotate_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.string_annotations.push(Annotation {
key: key.into(),
value: value.into(),
});
self
}
pub fn annotate_number(mut self, key: impl Into<String>, value: u64) -> Self {
self.numeric_annotations.push(Annotation {
key: key.into(),
value,
});
self
}
}
impl Extend {
pub fn new(entity_key: B256, number_of_blocks: u64) -> Self {
Self {
entity_key,
number_of_blocks,
}
}
}
#[bon]
impl ArkivTransaction {
#[builder]
pub fn builder(
creates: Option<Vec<Create>>,
updates: Option<Vec<Update>>,
deletes: Option<Vec<ArkivDelete>>,
extensions: Option<Vec<Extend>>,
gas_limit: Option<u64>,
max_priority_fee_per_gas: Option<u128>,
max_fee_per_gas: Option<u128>,
) -> Self {
Self {
encodable: EncodableArkivTransaction {
creates: creates.unwrap_or_default(),
updates: updates.unwrap_or_default(),
deletes: deletes.unwrap_or_default(),
extensions: extensions.unwrap_or_default(),
},
gas_limit,
max_priority_fee_per_gas,
max_fee_per_gas,
}
}
}
impl ArkivTransaction {
pub fn encoded(&self) -> Vec<u8> {
let mut encoded = Vec::new();
self.encodable.encode(&mut encoded);
encoded
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::B256;
use hex;
#[test]
fn test_empty_transaction() {
let tx = ArkivTransaction::builder().build();
assert_eq!(hex::encode(tx.encoded()), "c4c0c0c0c0");
}
#[test]
fn test_create_without_annotations() {
let create = Create::new(b"test payload".to_vec(), 1000);
let tx = ArkivTransaction::builder().creates(vec![create]).build();
assert_eq!(
hex::encode(tx.encoded()),
"d7d3d28203e88c74657374207061796c6f6164c0c0c0c0c0"
);
}
#[test]
fn test_create_with_annotations() {
let create = Create::new(b"test payload".to_vec(), 1000)
.annotate_string("foo", "bar")
.annotate_number("baz", 42);
let tx = ArkivTransaction::builder().creates(vec![create]).build();
assert_eq!(
hex::encode(tx.encoded()),
"e6e2e18203e88c74657374207061796c6f6164c9c883666f6f83626172c6c58362617a2ac0c0c0"
);
}
#[test]
fn test_update_with_annotations() {
let update = Update::new(
B256::from_slice(&[1; 32]),
b"updated payload".to_vec(),
2000,
)
.annotate_string("status", "active")
.annotate_number("version", 2);
let tx = ArkivTransaction::builder().updates(vec![update]).build();
assert_eq!(
hex::encode(tx.encoded()),
"f856c0f851f84fa001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164cfce8673746174757386616374697665cac98776657273696f6e02c0c0"
);
}
#[test]
fn test_delete_operation() {
let tx = ArkivTransaction::builder()
.deletes(vec![B256::from_slice(&[2; 32])])
.build();
assert_eq!(
hex::encode(tx.encoded()),
"e5c0c0e1a00202020202020202020202020202020202020202020202020202020202020202c0"
);
}
#[test]
fn test_extend_btl() {
let tx = ArkivTransaction::builder()
.extensions(vec![Extend {
entity_key: B256::from_slice(&[3; 32]),
number_of_blocks: 500,
}])
.build();
assert_eq!(
hex::encode(tx.encoded()),
"e9c0c0c0e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
);
}
#[test]
fn test_mixed_operations() {
let create = Create::new(b"test payload".to_vec(), 1000).annotate_string("type", "test");
let update = Update::new(
B256::from_slice(&[1; 32]),
b"updated payload".to_vec(),
2000,
);
let tx = ArkivTransaction::builder()
.creates(vec![create])
.updates(vec![update])
.deletes(vec![B256::from_slice(&[2; 32])])
.extensions(vec![Extend {
entity_key: B256::from_slice(&[3; 32]),
number_of_blocks: 500,
}])
.build();
assert_eq!(
hex::encode(tx.encoded()),
"f89fdedd8203e88c74657374207061796c6f6164cbca84747970658474657374c0f7f6a001010101010101010101010101010101010101010101010101010101010101018207d08f75706461746564207061796c6f6164c0c0e1a00202020202020202020202020202020202020202020202020202020202020202e5e4a003030303030303030303030303030303030303030303030303030303030303038201f4"
);
}
}