use std::fmt::{Debug, Formatter};
use async_trait::async_trait;
use base64::Engine;
use multiversx_sc::codec::TopDecodeMulti;
use num_bigint::BigUint;
use tokio::join;
use novax_data::{Address, NativeConvertible};
use novax_request::gateway::client::GatewayClient;
use crate::{ExecutorError, GatewayError, SimulationError, SimulationGatewayRequest, SimulationGatewayResponse, TransactionExecutor, TransactionOnNetwork, TransactionOnNetworkTransaction, TransactionOnNetworkTransactionSmartContractResult};
use crate::call_result::CallResult;
use crate::network::models::simulate::request::SimulationGatewayRequestBody;
use crate::network::utils::address::{get_address_guardian_data, get_address_info};
use crate::network::utils::network::get_network_config;
use crate::utils::transaction::normalization::NormalizationInOut;
use crate::utils::transaction::results::{find_sc_error, find_smart_contract_result};
use crate::utils::transaction::token_transfer::TokenTransfer;
pub type SimulationNetworkExecutor = BaseSimulationNetworkExecutor<String>;
pub struct BaseSimulationNetworkExecutor<Client: GatewayClient> {
pub client: Client,
pub sender_address: Address,
}
impl<Client: GatewayClient> BaseSimulationNetworkExecutor<Client> {
pub fn new(client: Client, sender_address: Address) -> Self {
Self {
client,
sender_address,
}
}
}
impl<Client: GatewayClient> BaseSimulationNetworkExecutor<Client> {
async fn simulate_transaction(&self, data: SimulationGatewayRequest) -> Result<SimulationGatewayResponse, ExecutorError> {
let sender_address = Address::from_bech32_string(&data.sender)?;
let (
address_info,
address_guardian_data,
network_config
) = join!(
get_address_info(&self.client, sender_address.clone()),
get_address_guardian_data(&self.client, sender_address),
get_network_config(&self.client)
);
let address_info = address_info?.account;
let address_guardian_data = address_guardian_data?;
let network_config = network_config?.config;
let (guardian, guardian_signature, version, options) = if address_guardian_data.guardian_data.is_some() {
let guardian_data = address_guardian_data.guardian_data.unwrap();
if guardian_data.guarded && guardian_data.active_guardian.is_some() {
let active_guardian = guardian_data.active_guardian.unwrap();
(
Some(active_guardian.address),
Some("00".to_string()),
Some(2),
Some(2)
)
} else {
(None, None, None, None)
}
} else {
(None, None, None, None)
};
let body = SimulationGatewayRequestBody {
nonce: address_info.nonce,
value: data.value,
receiver: data.receiver,
sender: data.sender,
gas_price: network_config.erd_min_gas_price,
gas_limit: data.gas_limit,
data: base64::engine::general_purpose::STANDARD.encode(data.data),
chain_id: network_config.erd_chain_id,
version: version.unwrap_or(network_config.erd_min_transaction_version),
guardian,
guardian_signature,
options
};
let Ok((_, Some(text))) = self.client.with_appended_url("/transaction/cost?checkSignature=false").post(&body).await else {
return Err(GatewayError::CannotSimulateTransaction.into())
};
let Ok(results) = serde_json::from_str(&text) else {
return Err(GatewayError::CannotParseSimulationResponse.into())
};
Ok(results)
}
}
impl<Client> Clone for BaseSimulationNetworkExecutor<Client>
where
Client: GatewayClient + Clone
{
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
sender_address: self.sender_address.clone(),
}
}
}
impl<Client> Debug for BaseSimulationNetworkExecutor<Client>
where
Client: GatewayClient
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BaseSimulationNetworkExecutor")
.field("client's url", &self.client.get_gateway_url())
.field("sender address", &self.sender_address)
.finish()
}
}
#[async_trait]
impl<Client: GatewayClient> TransactionExecutor for BaseSimulationNetworkExecutor<Client> {
async fn sc_call<OutputManaged>(
&mut self,
to: &Address,
function: String,
arguments: Vec<Vec<u8>>,
gas_limit: u64,
egld_value: BigUint,
esdt_transfers: Vec<TokenTransfer>
) -> Result<CallResult<OutputManaged::Native>, ExecutorError>
where
OutputManaged: TopDecodeMulti + NativeConvertible + Send + Sync
{
let function_name = if function.is_empty() {
None
} else {
Some(function)
};
let normalized = NormalizationInOut {
sender: self.sender_address.to_bech32_string()?,
receiver: to.to_bech32_string()?,
function_name,
arguments,
egld_value,
esdt_transfers,
}.normalize()?;
let normalized_egld_value = normalized.egld_value.clone();
let normalized_receiver = normalized.receiver.clone();
let normalized_sender = normalized.sender.clone();
let simulation_data = SimulationGatewayRequest {
value: normalized_egld_value.to_string(),
receiver: normalized_receiver,
sender: normalized_sender,
gas_limit,
data: normalized.get_transaction_data(),
};
let response = self.simulate_transaction(simulation_data).await?;
let Some(data) = response.data else {
return Err(SimulationError::ErrorInTx { code: response.code, error: response.error }.into())
};
let scrs = data.smart_contract_results
.into_iter()
.filter_map(|(hash, result)| {
let data = result.data?;
Some(
TransactionOnNetworkTransactionSmartContractResult {
hash,
nonce: result.nonce,
data,
}
)
})
.collect();
let fake_tx_on_network = TransactionOnNetwork {
transaction: TransactionOnNetworkTransaction {
smart_contract_results: Some(scrs),
logs: data.logs.clone(),
..Default::default()
}
};
let opt_smart_contract_results = find_smart_contract_result(
&fake_tx_on_network
)?;
let mut raw_result = match opt_smart_contract_results {
None => {
if let Some(logs) = data.logs.as_ref() {
if let Ok(Some(error_log)) = find_sc_error(logs) {
return Err(SimulationError::SmartContractExecutionError { status: error_log.status,
message: error_log.message
}.into());
}
}
vec![]
}
Some(results) => {
results
}
};
let Ok(output_managed) = OutputManaged::multi_decode(&mut raw_result) else {
return Err(SimulationError::CannotDecodeSmartContractResult.into())
};
let mut response = TransactionOnNetwork::default();
response.transaction.status = "success".to_string();
let call_result = CallResult {
response,
result: Some(output_managed.to_native()),
};
Ok(call_result)
}
}