use anyhow::{Context, Result};
use std::{ops::Deref, str, sync::Arc};
use tokio::sync::RwLock;
use tracing::{debug, error};
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Router,
};
use borsh::{BorshDeserialize, BorshSerialize};
use sdk::*;
use utoipa::openapi::OpenApi;
pub use axum;
pub use utoipa;
pub use utoipa_axum;
use crate::transaction_builder::TxExecutorHandler;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct ContractStateStore<State> {
pub state: Option<State>,
pub contract_name: ContractName,
}
pub type ContractHandlerStore<T> = Arc<RwLock<ContractStateStore<T>>>;
impl<State> Default for ContractStateStore<State> {
fn default() -> Self {
ContractStateStore {
state: None,
contract_name: Default::default(),
}
}
}
pub trait ContractHandler<Event = ()>
where
Self: Sized + TxExecutorHandler + 'static,
{
fn api(
store: ContractHandlerStore<Self>,
) -> impl std::future::Future<Output = (Router<()>, OpenApi)> + std::marker::Send;
fn handle_transaction_success(
&mut self,
tx: &BlobTransaction,
index: BlobIndex,
tx_context: Arc<TxContext>,
) -> Result<Option<Event>> {
let Blob {
contract_name,
data: _,
} = tx.blobs.get(index.0).context("Failed to get blob")?;
let calldata = Calldata {
identity: tx.identity.clone(),
index,
blobs: tx.blobs.clone().into(),
tx_blob_count: tx.blobs.len(),
tx_hash: tx.hashed(),
tx_ctx: Some(tx_context.deref().clone()),
private_input: vec![],
};
let hyli_output = match self.handle(&calldata) {
Ok(ho) => ho,
Err(e) => {
error!(
"Failed to handle blob {index} for contract {contract_name}: {}",
e
);
return Ok(None);
}
};
let program_outputs = str::from_utf8(&hyli_output.program_outputs).unwrap_or("no output");
info!("🚀 Executed {contract_name}: {}", program_outputs);
debug!(
handler = %contract_name,
"hyli_output: {:?}", hyli_output
);
Ok(None)
}
fn handle_transaction_failed(
&mut self,
_tx: &BlobTransaction,
_index: BlobIndex,
_tx_context: Arc<TxContext>,
) -> Result<Option<Event>> {
Ok(None)
}
fn handle_transaction_timeout(
&mut self,
_tx: &BlobTransaction,
_index: BlobIndex,
_tx_context: Arc<TxContext>,
) -> Result<Option<Event>> {
Ok(None)
}
fn handle_transaction_sequenced(
&mut self,
_tx: &BlobTransaction,
_index: BlobIndex,
_tx_context: Arc<TxContext>,
) -> Result<Option<Event>> {
Ok(None)
}
}
pub struct AppError(pub StatusCode, pub anyhow::Error);
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(self.0, format!("{}", self.1)).into_response()
}
}
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(StatusCode::INTERNAL_SERVER_ERROR, err.into())
}
}