use super::trace::ExecutionEvent;
use crate::shim::{econ::TokenAmount, fvm_shared_latest::error::ExitCode};
use cid::Cid;
use fil_actors_shared::fvm_ipld_amt::Amtv0;
use fvm2::executor::ApplyRet as ApplyRet_v2;
use fvm3::executor::ApplyRet as ApplyRet_v3;
use fvm4::executor::ApplyRet as ApplyRet_v4;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::RawBytes;
use fvm_shared2::receipt::Receipt as Receipt_v2;
pub use fvm_shared3::receipt::Receipt as Receipt_v3;
use fvm_shared4::receipt::Receipt as Receipt_v4;
use serde::Serialize;
#[derive(Clone, Debug)]
pub enum ApplyRet {
V2(Box<ApplyRet_v2>),
V3(Box<ApplyRet_v3>),
V4(Box<ApplyRet_v4>),
}
impl From<ApplyRet_v2> for ApplyRet {
fn from(other: ApplyRet_v2) -> Self {
ApplyRet::V2(Box::new(other))
}
}
impl From<ApplyRet_v3> for ApplyRet {
fn from(other: ApplyRet_v3) -> Self {
ApplyRet::V3(Box::new(other))
}
}
impl From<ApplyRet_v4> for ApplyRet {
fn from(other: ApplyRet_v4) -> Self {
ApplyRet::V4(Box::new(other))
}
}
impl ApplyRet {
pub fn failure_info(&self) -> Option<String> {
match self {
ApplyRet::V2(v2) => v2.failure_info.as_ref().map(|failure| failure.to_string()),
ApplyRet::V3(v3) => v3.failure_info.as_ref().map(|failure| failure.to_string()),
ApplyRet::V4(v4) => v4.failure_info.as_ref().map(|failure| failure.to_string()),
}
.map(|e| format!("{} (RetCode={})", e, self.msg_receipt().exit_code()))
}
pub fn miner_tip(&self) -> TokenAmount {
match self {
ApplyRet::V2(v2) => (&v2.miner_tip).into(),
ApplyRet::V3(v3) => (&v3.miner_tip).into(),
ApplyRet::V4(v4) => (&v4.miner_tip).into(),
}
}
pub fn penalty(&self) -> TokenAmount {
match self {
ApplyRet::V2(v2) => (&v2.penalty).into(),
ApplyRet::V3(v3) => (&v3.penalty).into(),
ApplyRet::V4(v4) => (&v4.penalty).into(),
}
}
pub fn msg_receipt(&self) -> Receipt {
match self {
ApplyRet::V2(v2) => Receipt::V2(v2.msg_receipt.clone()),
ApplyRet::V3(v3) => Receipt::V3(v3.msg_receipt.clone()),
ApplyRet::V4(v4) => Receipt::V4(v4.msg_receipt.clone()),
}
}
pub fn refund(&self) -> TokenAmount {
match self {
ApplyRet::V2(v2) => (&v2.refund).into(),
ApplyRet::V3(v3) => (&v3.refund).into(),
ApplyRet::V4(v4) => (&v4.refund).into(),
}
}
pub fn base_fee_burn(&self) -> TokenAmount {
match self {
ApplyRet::V2(v2) => (&v2.base_fee_burn).into(),
ApplyRet::V3(v3) => (&v3.base_fee_burn).into(),
ApplyRet::V4(v4) => (&v4.base_fee_burn).into(),
}
}
pub fn over_estimation_burn(&self) -> TokenAmount {
match self {
ApplyRet::V2(v2) => (&v2.over_estimation_burn).into(),
ApplyRet::V3(v3) => (&v3.over_estimation_burn).into(),
ApplyRet::V4(v4) => (&v4.over_estimation_burn).into(),
}
}
pub fn exec_trace(&self) -> Vec<ExecutionEvent> {
match self {
ApplyRet::V2(v2) => v2.exec_trace.iter().cloned().map(Into::into).collect(),
ApplyRet::V3(v3) => v3.exec_trace.iter().cloned().map(Into::into).collect(),
ApplyRet::V4(v4) => v4.exec_trace.iter().cloned().map(Into::into).collect(),
}
}
}
#[derive(PartialEq, Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum Receipt {
V2(Receipt_v2),
V3(Receipt_v3),
V4(Receipt_v4),
}
impl Receipt {
pub fn exit_code(&self) -> ExitCode {
match self {
Receipt::V2(v2) => ExitCode::new(v2.exit_code.value()),
Receipt::V3(v3) => ExitCode::new(v3.exit_code.value()),
Receipt::V4(v4) => v4.exit_code,
}
}
pub fn return_data(&self) -> RawBytes {
match self {
Receipt::V2(v2) => v2.return_data.clone(),
Receipt::V3(v3) => v3.return_data.clone(),
Receipt::V4(v4) => v4.return_data.clone(),
}
}
pub fn gas_used(&self) -> u64 {
match self {
Receipt::V2(v2) => v2.gas_used as u64,
Receipt::V3(v3) => v3.gas_used,
Receipt::V4(v4) => v4.gas_used,
}
}
pub fn events_root(&self) -> Option<Cid> {
match self {
Receipt::V2(_) => None,
Receipt::V3(v3) => v3.events_root,
Receipt::V4(v4) => v4.events_root,
}
}
pub fn get_receipt(
db: &impl Blockstore,
receipts: &Cid,
i: u64,
) -> anyhow::Result<Option<Self>> {
if let Ok(amt) = Amtv0::load(receipts, db) {
if let Ok(receipts) = amt.get(i) {
return Ok(receipts.cloned().map(Receipt::V4));
}
}
let amt = Amtv0::load(receipts, db)?;
let receipts = amt.get(i)?;
Ok(receipts.cloned().map(Receipt::V2))
}
pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result<Vec<Receipt>> {
let mut receipts = Vec::new();
if let Ok(amt) = Amtv0::<fvm_shared4::receipt::Receipt, _>::load(&receipts_cid, db) {
amt.for_each(|_, receipt| {
receipts.push(Receipt::V4(receipt.clone()));
Ok(())
})?;
} else {
let amt = Amtv0::<fvm_shared2::receipt::Receipt, _>::load(&receipts_cid, db)?;
amt.for_each(|_, receipt| {
receipts.push(Receipt::V2(receipt.clone()));
Ok(())
})?;
}
Ok(receipts)
}
}
impl From<Receipt_v3> for Receipt {
fn from(other: Receipt_v3) -> Self {
Receipt::V3(other)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Receipt {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
#[derive(derive_quickcheck_arbitrary::Arbitrary, Clone)]
enum Helper {
V2 {
exit_code: u32,
return_data: Vec<u8>,
gas_used: i64,
},
V3 {
exit_code: u32,
return_data: Vec<u8>,
gas_used: u64,
events_root: Option<::cid::Cid>,
},
V4 {
exit_code: u32,
return_data: Vec<u8>,
gas_used: u64,
events_root: Option<::cid::Cid>,
},
}
match Helper::arbitrary(g) {
Helper::V2 {
exit_code,
return_data,
gas_used,
} => Self::V2(Receipt_v2 {
exit_code: exit_code.into(),
return_data: return_data.into(),
gas_used,
}),
Helper::V3 {
exit_code,
return_data,
gas_used,
events_root,
} => Self::V3(Receipt_v3 {
exit_code: exit_code.into(),
return_data: return_data.into(),
gas_used,
events_root,
}),
Helper::V4 {
exit_code,
return_data,
gas_used,
events_root,
} => Self::V4(Receipt_v4 {
exit_code: exit_code.into(),
return_data: return_data.into(),
gas_used,
events_root,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn receipt_cbor_serde_serialize(receipt: Receipt) {
let encoded = fvm_ipld_encoding::to_vec(&receipt).unwrap();
let encoded2 = match &receipt {
Receipt::V2(v) => fvm_ipld_encoding::to_vec(v),
Receipt::V3(v) => fvm_ipld_encoding::to_vec(v),
Receipt::V4(v) => fvm_ipld_encoding::to_vec(v),
}
.unwrap();
assert_eq!(encoded, encoded2);
}
}