use std::mem;
use super::ethereum_data::EthereumData;
use crate::{
Client,
EthereumTransaction,
FileAppendTransaction,
FileCreateTransaction,
FileId,
Hbar,
TransactionResponse,
};
#[derive(Default, Debug)]
pub struct EthereumFlow {
ethereum_data: Option<EthereumData>,
max_gas_allowance: Option<Hbar>,
}
impl EthereumFlow {
const MAX_ETHEREUM_DATA_SIZE: usize = 5120;
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn get_ethereum_data(&self) -> Option<&EthereumData> {
self.ethereum_data.as_ref()
}
pub fn ethereum_data(&mut self, data: &[u8]) -> crate::Result<&mut Self> {
self.ethereum_data = Some(EthereumData::from_bytes(data)?);
Ok(self)
}
#[must_use]
pub fn get_max_gas_allowance(&self) -> Option<Hbar> {
self.max_gas_allowance
}
pub fn max_gas_allowance(&mut self, hbar: Hbar) -> &mut Self {
self.max_gas_allowance = Some(hbar);
self
}
pub async fn execute(&self, client: &Client) -> crate::Result<TransactionResponse> {
self.execute_with_optional_timeout(client, None).await
}
pub async fn execute_with_timeout(
&self,
client: &Client,
timeout_per_transaction: std::time::Duration,
) -> crate::Result<TransactionResponse> {
self.execute_with_optional_timeout(client, Some(timeout_per_transaction)).await
}
async fn execute_with_optional_timeout(
&self,
client: &Client,
timeout_per_transaction: Option<std::time::Duration>,
) -> crate::Result<TransactionResponse> {
let mut ethereum_data = self
.ethereum_data
.clone()
.expect("Must set ethereum data before calling execute on ethereum flow");
let ethereum_data_bytes = ethereum_data.to_bytes();
let mut ethereum_transaction = EthereumTransaction::new();
if let Some(allowance) = self.max_gas_allowance {
ethereum_transaction.max_gas_allowance_hbar(allowance);
}
if ethereum_data_bytes.len() <= Self::MAX_ETHEREUM_DATA_SIZE {
return ethereum_transaction
.ethereum_data(ethereum_data_bytes)
.execute_with_optional_timeout(client, timeout_per_transaction)
.await;
}
let call_data = mem::take(ethereum_data.call_data_mut());
let file_id = create_file(client, call_data, timeout_per_transaction).await?;
let ethereum_data_bytes = ethereum_data.to_bytes();
ethereum_transaction.call_data_file_id(file_id).ethereum_data(ethereum_data_bytes);
ethereum_transaction.execute_with_optional_timeout(client, timeout_per_transaction).await
}
}
fn split_call_data(call_data: Vec<u8>) -> (Vec<u8>, Option<Vec<u8>>) {
const FILE_APPEND_DEFAULT_CHUNK_SIZE: usize = 4096;
if call_data.len() <= FILE_APPEND_DEFAULT_CHUNK_SIZE {
return (call_data, None);
}
let mut file_create_call_data = call_data;
let file_append_call_data = file_create_call_data.split_off(FILE_APPEND_DEFAULT_CHUNK_SIZE);
(file_create_call_data, Some(file_append_call_data))
}
async fn create_file(
client: &Client,
call_data: Vec<u8>,
timeout_per_transaction: Option<std::time::Duration>,
) -> crate::Result<FileId> {
let (file_create_data, file_append_data) = split_call_data(call_data);
let file_id = FileCreateTransaction::new()
.contents(file_create_data)
.execute_with_optional_timeout(client, timeout_per_transaction)
.await?
.get_receipt_query()
.execute_with_optional_timeout(client, timeout_per_transaction)
.await?
.file_id
.expect("Creating a file means there's a file ID");
if let Some(file_append_data) = file_append_data {
FileAppendTransaction::new()
.file_id(file_id)
.contents(file_append_data)
.execute_all_with_optional_timeout(client, timeout_per_transaction)
.await?;
}
Ok(file_id)
}