use super::input::Input;
use super::output::Output;
use super::receipt::Receipt;
use crate::model::fuel_block::FuelBlockDb;
use crate::schema::contract::Contract;
use crate::schema::scalars::{AssetId, Bytes32, HexString, Salt, TransactionId, U64};
use crate::tx_pool::TransactionStatus as TxStatus;
use crate::{database::Database, schema::block::Block};
use async_graphql::{Context, Enum, Object, Union};
use chrono::{DateTime, Utc};
use fuel_core_interfaces::db::KvStoreError;
use fuel_storage::Storage;
use fuel_types::bytes::SerializableVec;
use fuel_vm::prelude::ProgramState as VmProgramState;
pub struct ProgramState {
return_type: ReturnType,
data: Vec<u8>,
}
#[Object]
impl ProgramState {
async fn return_type(&self) -> ReturnType {
self.return_type
}
async fn data(&self) -> HexString {
self.data.clone().into()
}
}
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum ReturnType {
Return,
ReturnData,
Revert,
}
impl From<VmProgramState> for ProgramState {
fn from(state: VmProgramState) -> Self {
match state {
VmProgramState::Return(d) => ProgramState {
return_type: ReturnType::Return,
data: d.to_be_bytes().to_vec(),
},
VmProgramState::ReturnData(d) => ProgramState {
return_type: ReturnType::ReturnData,
data: d.as_ref().to_vec(),
},
VmProgramState::Revert(d) => ProgramState {
return_type: ReturnType::Revert,
data: d.to_be_bytes().to_vec(),
},
}
}
}
#[derive(Union)]
pub enum TransactionStatus {
Submitted(SubmittedStatus),
Success(SuccessStatus),
Failed(FailureStatus),
}
pub struct SubmittedStatus(DateTime<Utc>);
#[Object]
impl SubmittedStatus {
async fn time(&self) -> DateTime<Utc> {
self.0
}
}
pub struct SuccessStatus {
block_id: fuel_types::Bytes32,
time: DateTime<Utc>,
result: VmProgramState,
}
#[Object]
impl SuccessStatus {
async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result<Block> {
let db = ctx.data_unchecked::<Database>();
let block = Storage::<fuel_types::Bytes32, FuelBlockDb>::get(db, &self.block_id)?
.ok_or(KvStoreError::NotFound)?
.into_owned();
let block = Block(block);
Ok(block)
}
async fn time(&self) -> DateTime<Utc> {
self.time
}
async fn program_state(&self) -> ProgramState {
self.result.into()
}
}
pub struct FailureStatus {
block_id: fuel_types::Bytes32,
time: DateTime<Utc>,
reason: String,
state: Option<VmProgramState>,
}
#[Object]
impl FailureStatus {
async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result<Block> {
let db = ctx.data_unchecked::<Database>();
let block = Storage::<fuel_types::Bytes32, FuelBlockDb>::get(db, &self.block_id)?
.ok_or(KvStoreError::NotFound)?
.into_owned();
let block = Block(block);
Ok(block)
}
async fn time(&self) -> DateTime<Utc> {
self.time
}
async fn reason(&self) -> String {
self.reason.clone()
}
async fn program_state(&self) -> Option<ProgramState> {
self.state.map(Into::into)
}
}
impl From<TxStatus> for TransactionStatus {
fn from(s: TxStatus) -> Self {
match s {
TxStatus::Submitted { time } => TransactionStatus::Submitted(SubmittedStatus(time)),
TxStatus::Success {
block_id,
result,
time,
} => TransactionStatus::Success(SuccessStatus {
block_id,
result,
time,
}),
TxStatus::Failed {
block_id,
reason,
time,
result,
} => TransactionStatus::Failed(FailureStatus {
block_id,
reason,
time,
state: result,
}),
}
}
}
pub struct Transaction(pub(crate) fuel_tx::Transaction);
#[Object]
impl Transaction {
async fn id(&self) -> TransactionId {
TransactionId(self.0.id())
}
async fn input_asset_ids(&self) -> Vec<AssetId> {
self.0.input_asset_ids().map(|c| AssetId(*c)).collect()
}
async fn input_contracts(&self) -> Vec<Contract> {
self.0.input_contracts().map(|v| Contract(*v)).collect()
}
async fn gas_price(&self) -> U64 {
self.0.gas_price().into()
}
async fn gas_limit(&self) -> U64 {
self.0.gas_limit().into()
}
async fn byte_price(&self) -> U64 {
self.0.byte_price().into()
}
async fn maturity(&self) -> U64 {
self.0.maturity().into()
}
async fn is_script(&self) -> bool {
self.0.is_script()
}
async fn inputs(&self) -> Vec<Input> {
self.0.inputs().iter().map(Into::into).collect()
}
async fn outputs(&self) -> Vec<Output> {
self.0.outputs().iter().map(Into::into).collect()
}
async fn witnesses(&self) -> Vec<HexString> {
self.0
.witnesses()
.iter()
.map(|w| HexString(w.clone().into_inner()))
.collect()
}
async fn receipts_root(&self) -> Option<Bytes32> {
self.0.receipts_root().cloned().map(Bytes32)
}
async fn status(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<TransactionStatus>> {
let db = ctx.data_unchecked::<Database>();
let status = db.get_tx_status(&self.0.id())?;
Ok(status.map(Into::into))
}
async fn receipts(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<Vec<Receipt>>> {
let db = ctx.data_unchecked::<Database>();
let receipts =
Storage::<fuel_types::Bytes32, Vec<fuel_tx::Receipt>>::get(db, &self.0.id())?;
Ok(receipts.map(|receipts| receipts.iter().cloned().map(Receipt).collect()))
}
async fn script(&self) -> Option<HexString> {
match &self.0 {
fuel_tx::Transaction::Script { script, .. } => Some(HexString(script.clone())),
fuel_tx::Transaction::Create { .. } => None,
}
}
async fn script_data(&self) -> Option<HexString> {
match &self.0 {
fuel_tx::Transaction::Script { script_data, .. } => {
Some(HexString(script_data.clone()))
}
fuel_tx::Transaction::Create { .. } => None,
}
}
async fn bytecode_witness_index(&self) -> Option<u8> {
match self.0 {
fuel_tx::Transaction::Script { .. } => None,
fuel_tx::Transaction::Create {
bytecode_witness_index,
..
} => Some(bytecode_witness_index),
}
}
async fn salt(&self) -> Option<Salt> {
match self.0 {
fuel_tx::Transaction::Script { .. } => None,
fuel_tx::Transaction::Create { salt, .. } => Some(salt.into()),
}
}
async fn static_contracts(&self) -> Option<Vec<Contract>> {
match &self.0 {
fuel_tx::Transaction::Script { .. } => None,
fuel_tx::Transaction::Create {
static_contracts, ..
} => Some(static_contracts.iter().cloned().map(Into::into).collect()),
}
}
async fn storage_slots(&self) -> Option<Vec<HexString>> {
match &self.0 {
fuel_tx::Transaction::Script { .. } => None,
fuel_tx::Transaction::Create { storage_slots, .. } => Some(
storage_slots
.iter()
.map(|slot| {
HexString(
slot.key()
.as_slice()
.iter()
.chain(slot.value().as_slice())
.copied()
.collect(),
)
})
.collect(),
),
}
}
async fn raw_payload(&self) -> HexString {
HexString(self.0.clone().to_bytes())
}
}