use alloy_primitives::{Address, Bloom, Bytes, TxHash, B256, U256};
use alloy_rpc_types_eth::{Block, Header, Log, Transaction, TransactionReceipt, Withdrawals};
use serde::{
de::{self, Unexpected},
Deserialize, Deserializer, Serialize, Serializer,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperationType {
OpTransfer = 0,
OpSelfDestruct = 1,
OpCreate = 2,
OpCreate2 = 3,
OpEofCreate = 4,
}
impl Serialize for OperationType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
impl<'de> Deserialize<'de> for OperationType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(Self::OpTransfer),
1 => Ok(Self::OpSelfDestruct),
2 => Ok(Self::OpCreate),
3 => Ok(Self::OpCreate2),
4 => Ok(Self::OpEofCreate),
other => Err(de::Error::invalid_value(
Unexpected::Unsigned(other as u64),
&"a valid OperationType",
)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct InternalOperation {
pub r#type: OperationType,
pub from: Address,
pub to: Address,
pub value: U256,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TraceEntry {
pub r#type: String,
pub depth: u32,
pub from: Address,
pub to: Address,
pub value: U256,
pub input: Bytes,
pub output: Bytes,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_copy_implementations)]
#[serde(rename_all = "camelCase")]
pub struct InternalIssuance {
pub block_reward: U256,
pub uncle_reward: U256,
pub issuance: U256,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OtsBlock<T = Transaction, H = Header> {
#[serde(flatten)]
pub block: Block<T, H>,
#[doc(alias = "tx_count")]
pub transaction_count: usize,
}
impl<T, H> From<Block<T, H>> for OtsBlock<T, H> {
fn from(block: Block<T, H>) -> Self {
Self { transaction_count: block.transactions.len(), block }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OtsSlimBlock<H = Header> {
#[serde(flatten)]
pub header: H,
#[serde(default)]
pub uncles: Vec<B256>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawals: Option<Withdrawals>,
#[doc(alias = "tx_count")]
pub transaction_count: usize,
}
impl<T, H> From<Block<T, H>> for OtsSlimBlock<H> {
fn from(block: Block<T, H>) -> Self {
Self {
header: block.header,
uncles: block.uncles,
withdrawals: block.withdrawals,
transaction_count: block.transactions.len(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockDetails<H = Header> {
pub block: OtsSlimBlock<H>,
pub issuance: InternalIssuance,
pub total_fees: U256,
}
impl<T, H> From<Block<T, H>> for BlockDetails<H> {
fn from(block: Block<T, H>) -> Self {
Self { block: block.into(), issuance: Default::default(), total_fees: U256::default() }
}
}
impl<H> BlockDetails<H> {
pub fn new<T>(block: Block<T, H>, issuance: InternalIssuance, total_fees: U256) -> Self {
Self { block: block.into(), issuance, total_fees }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OtsTransactionReceipt {
#[serde(flatten)]
pub receipt: TransactionReceipt<OtsReceipt>,
#[serde(default, with = "alloy_serde::quantity::opt")]
pub timestamp: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OtsReceipt {
#[serde(with = "alloy_serde::quantity")]
pub status: bool,
#[serde(with = "alloy_serde::quantity")]
pub cumulative_gas_used: u64,
pub logs: Option<Vec<Log>>,
pub logs_bloom: Option<Bloom>,
#[serde(with = "alloy_serde::quantity")]
pub r#type: u8,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OtsBlockTransactions<T = Transaction, H = Header> {
pub fullblock: OtsBlock<T, H>,
pub receipts: Vec<OtsTransactionReceipt>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[doc(alias = "TxWithReceipts")]
pub struct TransactionsWithReceipts<T = Transaction> {
#[doc(alias = "transactions")]
pub txs: Vec<T>,
pub receipts: Vec<OtsTransactionReceipt>,
pub first_page: bool,
pub last_page: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractCreator {
pub hash: TxHash,
pub creator: Address,
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
#[test]
fn test_otterscan_receipt() {
let s = r#"{
"blockHash": "0xf05aa8b73b005314684595adcff8e6149917b3239b6316247ce5e88eba9fd3f5",
"blockNumber": "0x1106fe7",
"contractAddress": null,
"cumulativeGasUsed": "0x95fac3",
"effectiveGasPrice": "0x2e9f0055d",
"from": "0x793abeea78d94c14b884a56788f549836a35db65",
"gasUsed": "0x14427",
"logs": null,
"logsBloom": null,
"status": "0x1",
"to": "0x06450dee7fd2fb8e39061434babcfc05599a6fb8",
"transactionHash": "0xd3cead022cbb5d6d18091f8b375e3a3896ec139e986144b9448290d55837275a",
"transactionIndex": "0x90",
"type": "0x2"
}"#;
let _receipt: OtsTransactionReceipt = serde_json::from_str(s).unwrap();
}
#[test]
fn test_otterscan_internal_operation() {
let s = r#"{
"type": 0,
"from": "0xea593b730d745fb5fe01b6d20e6603915252c6bf",
"to": "0xcc3d455481967dc97346ef1771a112d7a14c8f12",
"value": "0xee846f9305c00"
}"#;
let _op: InternalOperation = serde_json::from_str(s).unwrap();
}
#[test]
fn test_serialize_operation_type() {
assert_eq!(serde_json::to_string(&OperationType::OpTransfer).unwrap(), "0");
assert_eq!(serde_json::to_string(&OperationType::OpSelfDestruct).unwrap(), "1");
assert_eq!(serde_json::to_string(&OperationType::OpCreate).unwrap(), "2");
assert_eq!(serde_json::to_string(&OperationType::OpCreate2).unwrap(), "3");
}
#[test]
fn test_deserialize_operation_type() {
assert_eq!(serde_json::from_str::<OperationType>("0").unwrap(), OperationType::OpTransfer);
assert_eq!(
serde_json::from_str::<OperationType>("1").unwrap(),
OperationType::OpSelfDestruct
);
assert_eq!(serde_json::from_str::<OperationType>("2").unwrap(), OperationType::OpCreate);
assert_eq!(serde_json::from_str::<OperationType>("3").unwrap(), OperationType::OpCreate2);
}
}