use ethers_core::types::{
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, NameOrAddress, Signature,
TxHash,
};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_signers::Signer;
use crate::{
types::{
DestinationTransferPeerPath, ExtraParameters, OneTimeAddress, PeerType,
TransactionArguments, TransactionOperation, TransferPeerPath,
},
FireblocksError, FireblocksSigner,
};
use async_trait::async_trait;
use rustc_hex::ToHex;
use thiserror::Error;
#[derive(Debug)]
pub struct FireblocksMiddleware<M> {
fireblocks: FireblocksSigner,
inner: M,
}
impl<M: Middleware> FireblocksMiddleware<M> {
pub fn new(inner: M, fireblocks: FireblocksSigner) -> Self {
Self { inner, fireblocks }
}
}
#[derive(Error, Debug)]
pub enum FireblocksMiddlewareError<M: Middleware> {
#[error(transparent)]
FireblocksError(#[from] FireblocksError),
#[error("{0}")]
MiddlewareError(M::Error),
}
impl<M: Middleware> FromErr<M::Error> for FireblocksMiddlewareError<M> {
fn from(err: M::Error) -> FireblocksMiddlewareError<M> {
FireblocksMiddlewareError::MiddlewareError(err)
}
}
#[async_trait]
impl<M: Middleware> Middleware for FireblocksMiddleware<M> {
type Provider = M::Provider;
type Inner = M;
type Error = FireblocksMiddlewareError<M>;
fn inner(&self) -> &Self::Inner {
&self.inner
}
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
_: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let tx_hash = self
.fireblocks
.submit_transaction(tx, "".to_owned())
.await?;
Ok(PendingTransaction::new(tx_hash, self.provider()))
}
async fn sign<T: Into<Bytes> + Send + Sync>(
&self,
data: T,
_: &Address,
) -> Result<Signature, Self::Error> {
Ok(self.fireblocks.sign_message(data.into()).await?)
}
}
impl FireblocksSigner {
pub async fn submit_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
note: String,
) -> Result<TxHash, FireblocksError> {
let tx = tx.into();
let gas_price = match tx {
TypedTransaction::Eip2930(ref inner) => inner.tx.gas_price,
TypedTransaction::Legacy(ref tx) => tx.gas_price,
TypedTransaction::Eip1559(ref tx) => tx.max_fee_per_gas,
};
let args = TransactionArguments {
operation: TransactionOperation::CONTRACT_CALL,
source: TransferPeerPath {
peer_type: Some(PeerType::VAULT_ACCOUNT),
id: Some(self.account_id.clone()),
},
destination: self.to_destination(tx.to()),
extra_parameters: tx
.data()
.map(|data| ExtraParameters::ContractCallData(data.0.to_hex::<String>())),
asset_id: self.asset_id.clone(),
amount: tx.value().cloned().unwrap_or_default().to_string(),
gas_price: gas_price.map(|x| x.to_string()),
gas_limit: tx.gas().map(|x| x.to_string()),
note,
};
self.handle_action(args, |details| {
details.tx_hash[2..]
.parse::<TxHash>()
.map_err(|err| FireblocksError::ParseError(err.to_string()))
})
.await
}
fn to_destination(&self, to: Option<&NameOrAddress>) -> Option<DestinationTransferPeerPath> {
match to {
Some(NameOrAddress::Address(addr)) => {
let ota = OneTimeAddress {
address: format!("{:?}", addr),
tag: None,
};
Some(if let Some(id) = self.account_ids.get(addr) {
DestinationTransferPeerPath {
peer_type: PeerType::EXTERNAL_WALLET,
id: Some(id.clone()),
one_time_address: Some(ota),
}
} else {
DestinationTransferPeerPath {
peer_type: PeerType::ONE_TIME_ADDRESS,
id: None,
one_time_address: Some(ota),
}
})
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_signer;
use ethers_core::types::TransactionRequest;
use ethers_providers::Provider;
use rustc_hex::FromHex;
use std::convert::TryFrom;
#[tokio::test]
async fn broadcasts_tx() {
let fireblocks = test_signer().await;
let inner =
Provider::try_from("https://ropsten.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778")
.unwrap();
let provider = FireblocksMiddleware::new(inner, fireblocks);
let tx = TransactionRequest::new()
.to("cbe74e21b070a979b9d6426b11e876d4cb618daf".parse::<Address>().unwrap())
.data("ead710c40000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000".from_hex::<Vec<u8>>().unwrap());
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
let tx_hash = *pending_tx;
let receipt = pending_tx.await.unwrap().unwrap();
assert_eq!(receipt.transaction_hash, tx_hash);
}
}