use std::ops::Deref;
use std::sync::Arc;
use async_trait::async_trait;
use multiversx_sc::codec::TopDecodeMulti;
use multiversx_sc::imports::{CodeMetadata, ReturnsNewAddress};
use multiversx_sc::types::ReturnsRawResult;
use multiversx_sc_scenario::imports::{Bech32Address, BytesValue};
use multiversx_sc_scenario::ScenarioTxRun;
use num_bigint::BigUint;
use tokio::sync::Mutex;
use novax_data::Address;
use novax_data::NativeConvertible;
use crate::base::deploy::DeployExecutor;
use crate::base::query::QueryExecutor;
use crate::base::transaction::TransactionExecutor;
use crate::call_result::CallResult;
use crate::error::executor::ExecutorError;
use crate::error::mock_deploy::MockDeployError;
use crate::error::mock_transaction::MockTransactionError;
use crate::error::transaction::TransactionError;
use crate::{ScenarioWorld, TransactionOnNetwork};
use crate::utils::transaction::token_transfer::TokenTransfer;
use crate::utils::transaction::transfers::get_egld_or_esdt_transfers;
pub type StandardMockExecutor = MockExecutor<String>;
#[derive(Clone)]
pub struct MockExecutor<A>
where
A: Deref + Send + Sync,
Address: for<'a> From<&'a A::Target>
{
world: Arc<Mutex<ScenarioWorld>>,
opt_caller: Option<A>,
}
impl<A> MockExecutor<A>
where
A: Deref + Send + Sync,
Address: for<'a> From<&'a A::Target>
{
pub fn new(world: Arc<Mutex<ScenarioWorld>>, opt_caller: Option<A>) -> MockExecutor<A> {
MockExecutor {
world,
opt_caller,
}
}
}
#[async_trait]
impl<A> TransactionExecutor for MockExecutor<A>
where
A: Deref + Send + Sync,
Address: for<'a> From<&'a A::Target>
{
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 mut world = self.world.lock().await;
let transfers = get_egld_or_esdt_transfers(
egld_value,
esdt_transfers
)?;
let Some(caller) = self.opt_caller.as_ref() else {
return Err(MockTransactionError::CallerAddressNotPresent.into())
};
let mut tx = world.tx()
.from(Bech32Address::from_bech32_string(Address::from(caller).to_bech32_string()?))
.to(Bech32Address::from_bech32_string(to.to_bech32_string()?))
.raw_call(function)
.with_gas_limit(gas_limit)
.egld_or_multi_esdt(transfers)
.returns(ReturnsRawResult);
for argument in arguments {
tx = tx.argument(&argument);
}
let raw_result_managed = tx.run();
let mut raw_result: Vec<Vec<u8>> = raw_result_managed
.into_vec()
.iter()
.map(|buffer| buffer.to_boxed_bytes().into_vec())
.collect();
let Ok(output_managed) = OutputManaged::multi_decode(&mut raw_result) else {
return Err(TransactionError::CannotDecodeSmartContractResult.into())
};
let mut response = TransactionOnNetwork::default();
response.transaction.status = "successful".to_string();
let call_result = CallResult {
response,
result: Some(output_managed.to_native()),
};
Ok(call_result)
}
}
#[async_trait]
impl<A> DeployExecutor for MockExecutor<A>
where
A: Deref + Send + Sync,
Address: for<'a> From<&'a A::Target>
{
async fn sc_deploy<
OutputManaged
>(
&mut self,
bytes: Vec<u8>,
code_metadata: CodeMetadata,
egld_value: BigUint,
arguments: Vec<Vec<u8>>,
gas_limit: u64
) -> Result<(Address, CallResult<OutputManaged::Native>), ExecutorError>
where
OutputManaged: TopDecodeMulti + NativeConvertible + Send + Sync
{
let mut world = self.world.lock().await;
let Some(caller) = self.opt_caller.as_ref() else {
return Err(MockDeployError::CallerAddressNotPresent.into())
};
let mut tx = world.tx()
.from(Bech32Address::from_bech32_string(Address::from(caller).to_bech32_string()?))
.raw_deploy()
.code_metadata(code_metadata)
.code(BytesValue::from(bytes))
.with_gas_limit(gas_limit)
.egld(multiversx_sc::types::BigUint::from(egld_value))
.returns(ReturnsNewAddress)
.returns(ReturnsRawResult);
for argument in arguments {
tx = tx.argument(&argument);
}
let (new_address, raw_result_managed) = tx.run();
let mut raw_result: Vec<Vec<u8>> = raw_result_managed
.into_vec()
.iter()
.map(|buffer| buffer.to_boxed_bytes().into_vec())
.collect();
let Ok(output_managed) = OutputManaged::multi_decode(&mut raw_result) else {
return Err(TransactionError::CannotDecodeSmartContractResult.into())
};
let call_result = CallResult {
response: Default::default(),
result: Some(output_managed.to_native()),
};
Ok((Address::from_bytes(*new_address.as_array()), call_result))
}
}
#[async_trait]
impl<A> QueryExecutor for MockExecutor<A>
where
A: Clone + Deref + Send + Sync,
Address: for<'a> From<&'a A::Target>
{
async fn execute<OutputManaged>(
&self,
to: &Address,
function: String,
arguments: Vec<Vec<u8>>,
_egld_value: BigUint,
_esdt_transfers: Vec<TokenTransfer>
) -> Result<OutputManaged::Native, ExecutorError>
where
OutputManaged: TopDecodeMulti + NativeConvertible + Send + Sync
{
let mut world = self.world.lock().await;
let mut tx = world.query()
.to(Bech32Address::from_bech32_string(to.to_bech32_string()?))
.raw_call(function)
.returns(ReturnsRawResult);
for argument in arguments {
tx = tx.argument(&argument);
}
let raw_result_managed = tx.run();
let mut raw_result: Vec<Vec<u8>> = raw_result_managed
.into_vec()
.iter()
.map(|buffer| buffer.to_boxed_bytes().into_vec())
.collect();
let Ok(output_managed) = OutputManaged::multi_decode(&mut raw_result) else {
return Err(TransactionError::CannotDecodeSmartContractResult.into())
};
Ok(output_managed.to_native())
}
}