use std::fmt::Debug;
use rs_zephyr_common::{http::AgnosticRequest, wrapping::WrappedMaxBytes, RelayedMessageRequest};
use serde::{Deserialize, Serialize};
use soroban_sdk::{
xdr::{
ContractEvent, DiagnosticEvent, Hash, HostFunction, InvokeContractArgs,
InvokeHostFunctionOp, LedgerEntry, Limits, Operation, OperationBody, ReadXdr,
ScVal, SequenceNumber, SorobanAuthorizationEntry, SorobanTransactionData,
Transaction, TransactionEnvelope, TransactionV1Envelope, Uint256, WriteXdr,
},
Val,
};
use crate::{
database::{Database, DatabaseInteract, TableQueryWrapper},
external::{
self, conclude_host, read_ledger_meta, scval_to_valid_host_val, soroban_simulate_tx,
tx_send_message,
},
logger::EnvLogger,
Condition, MetaReader, SdkError, TableRows,
};
#[derive(Clone)]
pub struct EnvClient {
xdr: Option<soroban_sdk::xdr::LedgerCloseMeta>,
inner_soroban_host: soroban_sdk::Env,
}
impl EnvClient {
pub fn log(&self) -> EnvLogger {
EnvLogger
}
pub fn soroban(&self) -> &soroban_sdk::Env {
&self.inner_soroban_host
}
pub fn from_scval<T: soroban_sdk::TryFromVal<soroban_sdk::Env, soroban_sdk::Val>>(
&self,
scval: &soroban_sdk::xdr::ScVal,
) -> T {
self.scval_to_valid_host_val(scval).unwrap()
}
pub fn try_from_scval<T: soroban_sdk::TryFromVal<soroban_sdk::Env, soroban_sdk::Val>>(
&self,
scval: &soroban_sdk::xdr::ScVal,
) -> Result<T, SdkError> {
self.scval_to_valid_host_val(scval)
}
pub fn to_scval<T: soroban_sdk::TryIntoVal<soroban_sdk::Env, soroban_sdk::Val>>(
&self,
val: T,
) -> soroban_sdk::xdr::ScVal {
let val: soroban_sdk::Val = val.try_into_val(self.soroban()).unwrap();
let val_payload = val.get_payload() as i64;
let (status, offset, size) = unsafe { external::valid_host_val_to_scval(val_payload) };
SdkError::express_from_status(status).unwrap();
let xdr = {
let memory: *const u8 = offset as *const u8;
let slice = unsafe { core::slice::from_raw_parts(memory, size as usize) };
soroban_sdk::xdr::ScVal::from_xdr(slice, Limits::none()).unwrap()
};
xdr
}
pub fn scval_to_valid_host_val<
T: soroban_sdk::TryFromVal<soroban_sdk::Env, soroban_sdk::Val>,
>(
&self,
scval: &soroban_sdk::xdr::ScVal,
) -> Result<T, SdkError> {
let val_bytes = scval.to_xdr(Limits::none()).unwrap();
let (offset, size) = (val_bytes.as_ptr() as i64, val_bytes.len() as i64);
let (status, val) = unsafe { scval_to_valid_host_val(offset, size) };
SdkError::express_from_status(status)?;
let val = soroban_sdk::Val::from_payload(val as u64);
T::try_from_val(&self.soroban(), &val).map_err(|_| SdkError::Conversion)
}
pub(crate) fn message_relay(message: impl Serialize) {
let serialized = bincode::serialize(&message).unwrap();
let res = unsafe { tx_send_message(serialized.as_ptr() as i64, serialized.len() as i64) };
SdkError::express_from_status(res).unwrap()
}
pub fn send_web_request(&self, request: AgnosticRequest) {
let message = RelayedMessageRequest::Http(request);
Self::message_relay(message)
}
pub fn read<T: DatabaseInteract>(&self) -> Vec<T> {
T::read_to_rows(&self, None)
}
pub fn read_filter(&self) -> TableQueryWrapper {
TableQueryWrapper::new(crate::database::Action::Read)
}
pub fn put<T: DatabaseInteract>(&self, row: &T) {
row.put(&self)
}
pub fn update(&self) -> TableQueryWrapper {
TableQueryWrapper::new(crate::database::Action::Update)
}
pub fn update_inner<T: DatabaseInteract>(&self, row: &T, conditions: &[Condition]) {
row.update(&self, conditions)
}
pub fn db_write(
&self,
table_name: &str,
columns: &[&str],
segments: &[&[u8]],
) -> Result<(), SdkError> {
Database::write_table(table_name, columns, segments)
}
pub fn db_update(
&self,
table_name: &str,
columns: &[&str],
segments: &[&[u8]],
conditions: &[Condition],
) -> Result<(), SdkError> {
Database::update_table(table_name, columns, segments, conditions)
}
pub fn db_read(
&self,
table_name: &str,
columns: &[&str],
external: Option<i64>,
conditions: Option<&[Condition]>,
) -> Result<TableRows, SdkError> {
Database::read_table(table_name, columns, external, conditions)
}
pub fn reader(&self) -> MetaReader {
let meta = &self.xdr;
if let Some(meta) = meta {
MetaReader::new(meta)
} else {
panic!("Internal SDK error") }
}
pub fn new() -> Self {
let (offset, size) = unsafe { read_ledger_meta() };
let ledger_meta = {
let memory = 0 as *const u8;
let slice = unsafe {
let start = memory.offset(offset as isize);
core::slice::from_raw_parts(start, size as usize)
};
Some(soroban_sdk::xdr::LedgerCloseMeta::from_xdr(slice, Limits::none()).unwrap())
};
Self {
xdr: ledger_meta,
inner_soroban_host: soroban_sdk::Env::default(),
}
}
pub fn empty() -> Self {
Self {
xdr: None,
inner_soroban_host: soroban_sdk::Env::default(),
}
}
pub fn conclude<T: Serialize>(&self, result: T) {
let v = bincode::serialize(&serde_json::to_string(&result).unwrap()).unwrap();
unsafe { conclude_host(v.as_ptr() as i64, v.len() as i64) }
}
pub fn read_request_body<'a, T: Deserialize<'a>>(&self) -> T {
let (offset, size) = unsafe { read_ledger_meta() };
let request: &'a str = {
let memory = 0 as *const u8;
let slice = unsafe {
let start = memory.offset(offset as isize);
core::slice::from_raw_parts(start, size as usize)
};
bincode::deserialize(slice).unwrap()
};
serde_json::from_str(&request).unwrap()
}
fn get_host_function(
&self,
contract: [u8; 32],
fname: soroban_sdk::Symbol,
args: soroban_sdk::Vec<Val>,
) -> HostFunction {
let contract_address = soroban_sdk::xdr::ScAddress::Contract(Hash(contract));
let ScVal::Symbol(function_name) = self.to_scval(fname) else {
panic!()
};
let args = {
let mut vec = Vec::new();
for arg in args {
let scval = self.to_scval(arg);
vec.push(scval);
}
vec.try_into().unwrap()
};
let function = HostFunction::InvokeContract(InvokeContractArgs {
contract_address,
function_name,
args,
});
function
}
pub fn simulate_contract_call(
&self,
source: String,
contract: [u8; 32],
fname: soroban_sdk::Symbol,
args: soroban_sdk::Vec<Val>,
) -> Result<InvokeHostFunctionSimulationResult, SdkError> {
let source_bytes = stellar_strkey::ed25519::PublicKey::from_string(&source)
.unwrap()
.0;
self.simulate(
source_bytes,
self.get_host_function(contract, fname, args),
)
}
pub fn simulate_contract_call_to_tx(
&self,
source: String,
sequence_number: i64,
contract: [u8; 32],
fname: soroban_sdk::Symbol,
args: soroban_sdk::Vec<Val>,
) -> Result<TransactionResponse, SdkError> {
let source_bytes = stellar_strkey::ed25519::PublicKey::from_string(&source)
.unwrap()
.0;
let hf = self.get_host_function(contract, fname, args);
let simulation = self.simulate(source_bytes, hf.clone())?;
let mut response = TransactionResponse {
tx: None,
error: if let Err(error) = simulation.invoke_result {
Some(
error.to_xdr_base64(Limits::none()).unwrap()
+ (&format!(" Diagnostics: {:?}", simulation.diagnostic_events)),
)
} else {
None
},
};
if response.error.is_some() {
return Ok(response);
}
let tx = Transaction {
source_account: soroban_sdk::xdr::MuxedAccount::Ed25519(Uint256(source_bytes)),
fee: 100 + simulation.transaction_data.as_ref().unwrap().resource_fee as u32,
seq_num: SequenceNumber(sequence_number),
cond: soroban_sdk::xdr::Preconditions::None,
memo: soroban_sdk::xdr::Memo::None,
operations: vec![Operation {
source_account: None,
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
host_function: hf,
auth: simulation.auth.try_into().unwrap(),
}),
}]
.try_into()
.unwrap(),
ext: soroban_sdk::xdr::TransactionExt::V1(SorobanTransactionData {
ext: soroban_sdk::xdr::ExtensionPoint::V0,
resources: simulation
.transaction_data
.as_ref()
.unwrap()
.resources
.clone(),
resource_fee: simulation.transaction_data.as_ref().unwrap().resource_fee,
}),
};
let envelope = TransactionEnvelope::Tx(TransactionV1Envelope {
tx,
signatures: std::vec::Vec::new().try_into().unwrap(),
});
response.tx = Some(envelope.to_xdr_base64(Limits::none()).unwrap());
Ok(response)
}
pub fn simulate(
&self,
source: [u8; 32],
function: HostFunction,
) -> Result<InvokeHostFunctionSimulationResult, SdkError> {
let key_bytes = function.to_xdr(Limits::none()).unwrap();
let (offset, size) = (key_bytes.as_ptr() as i64, key_bytes.len() as i64);
let source_parts = WrappedMaxBytes::array_to_max_parts::<4>(&source);
let (status, inbound_offset, inbound_size) = unsafe {
soroban_simulate_tx(
source_parts[0],
source_parts[1],
source_parts[2],
source_parts[3],
offset,
size,
)
};
SdkError::express_from_status(status)?;
let memory: *const u8 = inbound_offset as *const u8;
let slice = unsafe { core::slice::from_raw_parts(memory, inbound_size as usize) };
let deser = bincode::deserialize::<InvokeHostFunctionSimulationResult>(slice)
.map_err(|_| SdkError::Conversion)?;
Ok(deser)
}
}
#[derive(Eq, PartialEq, Debug, Deserialize, Serialize, Clone)]
pub struct LedgerEntryDiff {
pub state_before: Option<LedgerEntry>,
pub state_after: Option<LedgerEntry>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct InvokeHostFunctionSimulationResult {
pub invoke_result: std::result::Result<ScVal, ScVal>,
pub auth: Vec<SorobanAuthorizationEntry>,
pub contract_events: Vec<ContractEvent>,
pub diagnostic_events: Vec<DiagnosticEvent>,
pub transaction_data: Option<SorobanTransactionData>,
pub simulated_instructions: u32,
pub simulated_memory: u32,
pub modified_entries: Vec<LedgerEntryDiff>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct SimulationResults {
pub xdr: String,
pub auth: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct RestorePreamble {
pub min_resource_fee: String,
pub transaction_data: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct SimulateTransactionResponse {
pub latest_ledger: u32,
pub min_resource_fee: u32,
pub results: Option<[SimulationResults; 1]>,
pub transaction_data: Option<String>,
pub events: Option<Vec<String>>,
pub restore_preamble: Option<RestorePreamble>,
pub state_changes: Option<Vec<LedgerEntryDiff>>,
}
impl InvokeHostFunctionSimulationResult {
pub fn to_rpc_api(&self, env: &EnvClient) -> SimulateTransactionResponse {
SimulateTransactionResponse {
latest_ledger: env.soroban().ledger().sequence(),
min_resource_fee: self
.transaction_data
.as_ref()
.map_or_else(|| 0, |r| r.resource_fee) as u32,
results: Some([SimulationResults {
xdr: if let Ok(res) = &self.invoke_result {
res.to_xdr_base64(Limits::none()).unwrap()
} else {
self.invoke_result
.clone()
.err()
.unwrap()
.to_xdr_base64(Limits::none())
.unwrap()
},
auth: self
.auth
.iter()
.map(|auth| auth.to_xdr_base64(Limits::none()).unwrap())
.collect(),
}]),
transaction_data: if let Some(data) = &self.transaction_data {
Some(data.to_xdr_base64(Limits::none()).unwrap())
} else {
None
},
events: Some(
self.contract_events
.iter()
.map(|event| event.to_xdr_base64(Limits::none()).unwrap())
.collect(),
),
restore_preamble: None,
state_changes: Some(self.modified_entries.clone()),
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct TransactionResponse {
pub tx: Option<String>,
pub error: Option<String>,
}