Bläddra i källkod

implement interface

0xfirefist 1 år sedan
förälder
incheckning
d6066ae896
3 ändrade filer med 139 tillägg och 100 borttagningar
  1. 8 2
      apps/fortuna/src/chain/chain.rs
  2. 1 1
      apps/fortuna/src/chain/ethereum.rs
  3. 130 97
      apps/fortuna/src/chain/evm.rs

+ 8 - 2
apps/fortuna/src/chain/chain.rs

@@ -31,9 +31,15 @@ pub trait ChainReader: Send + Sync {
 }
 
 pub enum RevealError {
-    HashChainRevealFailed(Error),
+    GasLimitExceeded,
     ContractError(Error),
     RpcError(Error),
+    Unknown(Error),
+}
+
+pub struct RevealSuccess {
+    pub tx_hash:  String,
+    pub gas_used: f64,
 }
 
 #[async_trait]
@@ -43,5 +49,5 @@ pub trait ChainWriter: Send + Sync + ChainReader {
         &self,
         request_with_callback_data: RequestWithCallbackData,
         provider_revelation: [u8; 32],
-    ) -> Result<(), RevealError>;
+    ) -> Result<RevealSuccess, RevealError>;
 }

+ 1 - 1
apps/fortuna/src/chain/ethereum.rs

@@ -73,7 +73,7 @@ pub type PythContract = PythRandom<Provider<Http>>;
 /// Transformer that converts a transaction into a legacy transaction if use_legacy_tx is true.
 #[derive(Clone, Debug)]
 pub struct LegacyTxTransformer {
-    use_legacy_tx: bool,
+    pub use_legacy_tx: bool,
 }
 
 impl Transformer for LegacyTxTransformer {

+ 130 - 97
apps/fortuna/src/chain/evm.rs

@@ -1,28 +1,35 @@
 use {
-    super::{
+    super::chain::{
+        ChainBlockNumber,
+        ChainReader,
+        ChainWriter,
+        RequestWithCallbackData,
+        RevealError,
+    },
+    crate::{
         chain::{
-            ChainBlockNumber,
-            ChainReader,
-            ChainWriter,
-            RequestWithCallbackData,
-            RevealError,
+            chain::RevealSuccess,
+            ethereum::{
+                LegacyTxTransformer,
+                PythRandom,
+                RequestedWithCallbackFilter,
+            },
         },
-        reader::BlockStatus,
+        config::EthereumConfig,
     },
-    crate::config::EthereumConfig,
-    anyhow::Result,
+    anyhow::{
+        anyhow,
+        Error,
+        Result,
+    },
+    axum::async_trait,
     ethers::{
-        contract::abigen,
         middleware::{
-            transformer::{
-                Transformer,
-                TransformerError,
-                TransformerMiddleware,
-            },
+            transformer::TransformerMiddleware,
             NonceManagerMiddleware,
             SignerMiddleware,
         },
-        prelude::TransactionRequest,
+        prelude::ContractError,
         providers::{
             Http,
             Middleware,
@@ -33,38 +40,14 @@ use {
             Signer,
         },
         types::{
-            transaction::eip2718::TypedTransaction,
             Address,
+            BlockNumber as EthersBlockNumber,
+            U256,
         },
     },
     std::sync::Arc,
 };
 
-// TODO: Programmatically generate this so we don't have to keep committed ABI in sync with the
-// contract in the same repo.
-abigen!(
-    PythRandom,
-    "../../target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json"
-);
-
-/// Transformer that converts a transaction into a legacy transaction if use_legacy_tx is true.
-#[derive(Clone, Debug)]
-pub struct LegacyTxTransformer {
-    use_legacy_tx: bool,
-}
-
-impl Transformer for LegacyTxTransformer {
-    fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
-        if self.use_legacy_tx {
-            let legacy_request: TransactionRequest = (*tx).clone().into();
-            *tx = legacy_request.into();
-            Ok(())
-        } else {
-            Ok(())
-        }
-    }
-}
-
 pub type SignablePythContract = PythRandom<
     TransformerMiddleware<
         NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>,
@@ -100,8 +83,9 @@ impl EthereumConfig {
         );
 
         Ok(Box::new(EvmWriterContract {
+            gas_limit: self.gas_limit,
             provider_addr,
-            confirmed_block_status: self.confirmed_block_status,
+            confirmed_block_status: self.confirmed_block_status.into(),
             reveal_delay_blocks: self.reveal_delay_blocks,
             contract,
         }))
@@ -111,74 +95,123 @@ impl EthereumConfig {
 
 pub struct EvmWriterContract {
     provider_addr:          Address,
-    confirmed_block_status: BlockStatus,
+    confirmed_block_status: EthersBlockNumber,
+    gas_limit:              U256,
     reveal_delay_blocks:    ChainBlockNumber,
     contract:               SignablePythContract,
 }
 
+#[async_trait]
 impl ChainReader for EvmWriterContract {
-    #[doc = " Returns data of all the requests with callback made on chain between"]
-    #[doc = " the given block numbers."]
-    #[must_use]
-    #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
-    fn get_requests_with_callback_data<'life0, 'async_trait>(
-        &'life0 self,
+    async fn get_requests_with_callback_data(
+        &self,
         from_block: ChainBlockNumber,
         to_block: ChainBlockNumber,
-    ) -> ::core::pin::Pin<
-        Box<
-            dyn ::core::future::Future<Output = Result<Vec<RequestWithCallbackData>>>
-                + ::core::marker::Send
-                + 'async_trait,
-        >,
-    >
-    where
-        'life0: 'async_trait,
-        Self: 'async_trait,
-    {
-        todo!()
+    ) -> Result<Vec<RequestWithCallbackData>> {
+        let mut event = self.contract.requested_with_callback_filter();
+        event.filter = event.filter.from_block(from_block).to_block(to_block);
+
+        let res: Vec<RequestedWithCallbackFilter> = event.query().await?;
+
+        // Filter for provider_address
+        let filtered_res = res
+            .into_iter()
+            .filter(|r| r.provider == self.provider_addr)
+            .map(|r| RequestWithCallbackData {
+                sequence_number:    r.sequence_number,
+                user_random_number: r.user_random_number,
+            })
+            .collect();
+
+        Ok(filtered_res)
     }
 
-    #[doc = " Returns the latest block which we consider to be included into the chain and"]
-    #[doc = " is safe from reorgs."]
-    #[must_use]
-    #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
-    fn get_latest_safe_block<'life0, 'async_trait>(
-        &'life0 self,
-    ) -> ::core::pin::Pin<
-        Box<
-            dyn ::core::future::Future<Output = Result<ChainBlockNumber>>
-                + ::core::marker::Send
-                + 'async_trait,
-        >,
-    >
-    where
-        'life0: 'async_trait,
-        Self: 'async_trait,
-    {
-        todo!()
+    async fn get_latest_safe_block(&self) -> Result<ChainBlockNumber> {
+        let block_number: EthersBlockNumber = self.confirmed_block_status.into();
+        let block = self
+            .contract
+            .client()
+            .get_block(block_number)
+            .await?
+            .ok_or_else(|| Error::msg("pending block confirmation"))?;
+
+        match block.number {
+            Some(n) => Ok(n.as_u64().checked_sub(self.reveal_delay_blocks).unwrap()),
+            None => Err(Error::msg("pending confirmation")),
+        }
     }
 }
 
+#[async_trait]
 impl ChainWriter for EvmWriterContract {
-    #[doc = " Fulfill the given request on chain with the given provider revelation."]
-    #[must_use]
-    #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
-    fn reveal_with_callback<'life0, 'async_trait>(
-        &'life0 self,
+    async fn reveal_with_callback(
+        &self,
         request_with_callback_data: RequestWithCallbackData,
         provider_revelation: [u8; 32],
-    ) -> ::core::pin::Pin<
-        Box<
-            dyn ::core::future::Future<Output = Result<(), RevealError>>
-                + ::core::marker::Send
-                + 'async_trait,
-        >,
-    >
-    where
-        'life0: 'async_trait,
-        Self: 'async_trait,
-    {
-        todo!()
+    ) -> Result<RevealSuccess, RevealError> {
+        let gas_estimate = self
+            .contract
+            .reveal_with_callback(
+                self.provider_addr,
+                request_with_callback_data.sequence_number,
+                request_with_callback_data.user_random_number,
+                provider_revelation,
+            )
+            .estimate_gas()
+            .await
+            .map_err(|e| match e {
+                ContractError::ProviderError { e } => RevealError::RpcError(e.into()),
+                ContractError::Revert(reason) => RevealError::ContractError(anyhow!(reason)),
+                _ => RevealError::Unknown(anyhow!(e)),
+            })?;
+
+        let gas_estimate = EvmWriterContract::gas_multiplier(gas_estimate);
+        if gas_estimate > self.gas_limit {
+            return Err(RevealError::GasLimitExceeded);
+        }
+
+        let pending_tx = self
+            .contract
+            .reveal_with_callback(
+                self.provider_addr,
+                request_with_callback_data.sequence_number,
+                request_with_callback_data.user_random_number,
+                provider_revelation,
+            )
+            .send()
+            .await
+            .map_err(|e| match e {
+                ContractError::ProviderError { e } => RevealError::RpcError(e.into()),
+                ContractError::Revert(reason) => RevealError::ContractError(anyhow!(reason)),
+                _ => RevealError::Unknown(anyhow!(e)),
+            })?;
+
+        let res = pending_tx
+            .await
+            .map_err(|e| RevealError::RpcError(e.into()))?;
+
+        let res = res.ok_or_else(|| {
+            RevealError::RpcError(anyhow!("unable to verify transaction success"))
+        })?;
+
+        let gas_used = Self::wei_to_eth(res.gas_used.unwrap_or(U256::from(0)));
+        Ok(RevealSuccess {
+            tx_hash: res.transaction_hash.to_string(),
+            gas_used,
+        })
+    }
+}
+
+impl EvmWriterContract {
+    fn gas_multiplier(gas_estimate: U256) -> U256 {
+        let (gas_estimate, _) = gas_estimate
+            .saturating_mul(U256::from(4))
+            .div_mod(U256::from(3));
+
+        gas_estimate
+    }
+
+    fn wei_to_eth(value: U256) -> f64 {
+        value.as_u128() as f64 / 1e18
     }
 }