novax-executor 0.2.12

Part of the NovaX framework, this crate facilitates the execution of transactions and queries against smart contracts on the blockchain.
use std::time::Duration;

use async_trait::async_trait;
use base64::Engine;
use num_bigint::BigUint;

use novax_data::Address;

use crate::error::transaction::TransactionError;
use crate::ExecutorError;
use crate::network::models::address::info::AddressGatewayInfoAccount;
use crate::network::models::network::config::NetworkGatewayConfig;
use crate::network::transaction::models::send_request::TransactionSendRequest;
use crate::network::transaction::models::transaction_on_network::{FINAL_TRANSACTION_STATUS, TransactionOnNetwork};
use crate::network::utils::address::get_address_info;
use crate::network::utils::network::get_network_config;
use crate::network::utils::transaction::{get_transaction_on_network, send_transaction};
use crate::network::utils::wallet::{SignableTransaction, Wallet};
use crate::utils::date::get_current_timestamp::{get_current_timestamp, get_timestamp_of_next_block};

#[async_trait]
pub trait BlockchainInteractor: Sized + Send + Sync {
    async fn new(gateway_url: String, wallet: Wallet) -> Result<Self, ExecutorError>;

    async fn sc_call(
        &mut self,
        to: String,
        value: BigUint,
        data: String,
        gas_limit: u64
    ) -> Result<TransactionOnNetwork, ExecutorError>;

    fn get_sender_address(&self) -> Address;
}

#[derive(Clone, Debug)]
pub struct Interactor {
    pub gateway_url: String,
    pub wallet: Wallet,
    pub network_config: NetworkGatewayConfig,
    pub refresh_strategy: TransactionRefreshStrategy,
    pub timeout: Duration
}

#[derive(Clone, Debug)]
pub enum TransactionRefreshStrategy {
    EachBlock,
    EachDuration(Duration)
}

impl Interactor {
    async fn get_account_info(&self) -> Result<AddressGatewayInfoAccount, ExecutorError> {
        let address = self.wallet.get_address();

        Ok(get_address_info(&self.gateway_url, address).await?.account)
    }

    async fn wait_for_execution(&self, tx_hash: &str) -> Result<TransactionOnNetwork, ExecutorError> {
        let end_timestamp = get_current_timestamp()? + self.timeout;

        loop {
            let transaction_on_network = get_transaction_on_network(
                &self.gateway_url,
                tx_hash
            ).await?;

            if FINAL_TRANSACTION_STATUS.contains(&transaction_on_network.transaction.status.as_ref()) {
                return Ok(transaction_on_network)
            }

            let current_timestamp = get_current_timestamp()?;

            if current_timestamp >= end_timestamp {
                return Err(TransactionError::TimeoutWhenRetrievingTransactionOnNetwork.into())
            }

            match self.refresh_strategy {
                TransactionRefreshStrategy::EachBlock => {
                    let timestamp_of_next_block = get_timestamp_of_next_block(current_timestamp)?;
                    tokio::time::sleep(timestamp_of_next_block - current_timestamp).await;
                }
                TransactionRefreshStrategy::EachDuration(duration) => {
                    tokio::time::sleep(duration).await;
                }
            }
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn get_sendable_transaction(
        &self,
        nonce: u64,
        value: String,
        receiver: String,
        sender: String,
        gas_price: u64,
        gas_limit: u64,
        data: String,
        chain_id: String,
        version: u32,
        options: u32
    ) -> TransactionSendRequest {
        let base64_encoded_data = base64::engine::general_purpose::STANDARD.encode(data);

        let tx_to_sign = SignableTransaction {
            nonce,
            value,
            receiver,
            sender,
            gas_price,
            gas_limit,
            data: Some(base64_encoded_data),
            chain_id,
            version,
            options,
        };

        tx_to_sign.into_sendable_transaction(&self.wallet)
    }
}

#[async_trait]
impl BlockchainInteractor for Interactor {
    async fn new(
        gateway_url: String,
        wallet: Wallet
    ) -> Result<Self, ExecutorError> {
        let network_config = get_network_config(&gateway_url).await?;

        Ok(
            Self {
                gateway_url,
                wallet,
                network_config,
                refresh_strategy: TransactionRefreshStrategy::EachBlock,
                timeout: Duration::from_secs(10)
            }
        )
    }

    async fn sc_call(
        &mut self,
        to: String,
        value: BigUint,
        data: String,
        gas_limit: u64
    ) -> Result<TransactionOnNetwork, ExecutorError> {
        let sender_info = self.get_account_info().await?;
        let sender_address = sender_info.address;
        let nonce = sender_info.nonce;

        let transaction_request = self.get_sendable_transaction(
            nonce,
            value.to_string(),
            to,
            sender_address,
            self.network_config.config.erd_min_gas_price,
            gas_limit,
            data,
            self.network_config.config.erd_chain_id.clone(),
            1,
            0
        );

        let tx_hash = send_transaction(
            &self.gateway_url,
            &transaction_request
        )
            .await?;


        self.wait_for_execution(&tx_hash).await
    }

    fn get_sender_address(&self) -> Address {
        self.wallet.get_address()
    }
}