hopr_chain_rpc/
rpc.rs

1//! General purpose high-level RPC operations implementation (`HoprRpcOperations`).
2//!
3//! The purpose of this module is to give implementation of the [HoprRpcOperations] trait:
4//! [RpcOperations] type, which is the main API exposed by this crate.
5use std::{sync::Arc, time::Duration};
6
7use SafeSingleton::SafeSingletonInstance;
8use alloy::{
9    network::EthereumWallet,
10    providers::{
11        CallItemBuilder, Identity, PendingTransaction, Provider, ProviderBuilder, RootProvider,
12        fillers::{
13            BlobGasFiller, CachedNonceManager, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
14            WalletFiller,
15        },
16    },
17    rpc::{
18        client::RpcClient,
19        types::{Block, TransactionRequest},
20    },
21    signers::local::PrivateKeySigner,
22    sol,
23};
24use async_trait::async_trait;
25use hopr_bindings::hoprnodemanagementmodule::HoprNodeManagementModule::{self, HoprNodeManagementModuleInstance};
26use hopr_chain_types::{ContractAddresses, ContractInstances, NetworkRegistryProxy};
27use hopr_crypto_types::keypairs::{ChainKeypair, Keypair};
28use hopr_internal_types::prelude::{EncodedWinProb, WinningProbability};
29use hopr_primitive_types::prelude::*;
30use primitive_types::U256;
31use serde::{Deserialize, Serialize};
32use tracing::debug;
33use url::Url;
34use validator::Validate;
35
36// use crate::middleware::GnosisScan;
37use crate::{
38    HoprRpcOperations, NodeSafeModuleStatus,
39    client::GasOracleFiller,
40    errors::{Result, RpcError},
41    transport::HttpRequestor,
42};
43
44// define basic safe abi
45sol!(
46    #![sol(abi)]
47    #![sol(rpc)]
48    contract SafeSingleton {
49        function isModuleEnabled(address module) public view returns (bool);
50    }
51);
52
53/// Default gas oracle URL for Gnosis chain.
54pub const DEFAULT_GAS_ORACLE_URL: &str = "https://ggnosis.blockscan.com/gasapi.ashx?apikey=key&method=gasoracle";
55
56/// Configuration of the RPC related parameters.
57#[derive(Clone, Debug, PartialEq, Eq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
58pub struct RpcOperationsConfig {
59    /// Blockchain id
60    ///
61    /// Default is 100.
62    #[default = 100]
63    pub chain_id: u64,
64    /// Addresses of all deployed contracts
65    ///
66    /// Default contains empty (null) addresses.
67    pub contract_addrs: ContractAddresses,
68    /// Address of the node's module.
69    ///
70    /// Defaults to null address.
71    pub module_address: Address,
72    /// Address of the node's safe contract.
73    ///
74    /// Defaults to null address.
75    pub safe_address: Address,
76    /// Expected block time of the blockchain
77    ///
78    /// Defaults to 5 seconds
79    #[default(Duration::from_secs(5))]
80    pub expected_block_time: Duration,
81    /// The largest amount of blocks to fetch at once when fetching a range of blocks.
82    ///
83    /// If the requested block range size is N, then the client will always fetch `min(N, max_block_range_fetch_size)`
84    ///
85    /// Defaults to 2000 blocks
86    #[validate(range(min = 1))]
87    #[default = 2000]
88    pub max_block_range_fetch_size: u64,
89    /// Interval for polling on TX submission
90    ///
91    /// Defaults to 7 seconds.
92    #[default(Duration::from_secs(7))]
93    pub tx_polling_interval: Duration,
94    /// Finalization chain length
95    ///
96    /// The number of blocks including and decreasing from the chain HEAD
97    /// that the logs will be buffered for before being considered
98    /// successfully joined to the chain.
99    ///
100    /// Defaults to 8
101    #[validate(range(min = 1, max = 100))]
102    #[default = 8]
103    pub finality: u32,
104    /// URL to the gas price oracle.
105    ///
106    /// Defaults to [`DEFAULT_GAS_ORACLE_URL`].
107    #[default(Some(DEFAULT_GAS_ORACLE_URL.parse().unwrap()))]
108    pub gas_oracle_url: Option<Url>,
109}
110
111pub(crate) type HoprProvider<R> = FillProvider<
112    JoinFill<
113        JoinFill<
114            JoinFill<
115                JoinFill<
116                    JoinFill<JoinFill<Identity, WalletFiller<EthereumWallet>>, ChainIdFiller>,
117                    NonceFiller<CachedNonceManager>,
118                >,
119                GasFiller,
120            >,
121            GasOracleFiller<R>,
122        >,
123        BlobGasFiller,
124    >,
125    RootProvider,
126>;
127
128/// Implementation of `HoprRpcOperations` and `HoprIndexerRpcOperations` trait via `alloy`
129#[derive(Debug, Clone)]
130pub struct RpcOperations<R: HttpRequestor + 'static + Clone> {
131    pub(crate) provider: Arc<HoprProvider<R>>,
132    pub(crate) cfg: RpcOperationsConfig,
133    contract_instances: Arc<ContractInstances<HoprProvider<R>>>,
134    node_module: HoprNodeManagementModuleInstance<HoprProvider<R>>,
135    node_safe: SafeSingletonInstance<HoprProvider<R>>,
136}
137
138#[cfg_attr(test, mockall::automock)]
139impl<R: HttpRequestor + 'static + Clone> RpcOperations<R> {
140    pub fn new(
141        rpc_client: RpcClient,
142        requestor: R,
143        chain_key: &ChainKeypair,
144        cfg: RpcOperationsConfig,
145        use_dummy_nr: Option<bool>,
146    ) -> Result<Self> {
147        let wallet =
148            PrivateKeySigner::from_slice(chain_key.secret().as_ref()).map_err(|e| RpcError::SignerError(e.into()))?;
149
150        let provider = ProviderBuilder::new()
151            .disable_recommended_fillers()
152            .wallet(wallet)
153            .filler(ChainIdFiller::default())
154            .filler(NonceFiller::new(CachedNonceManager::default()))
155            .filler(GasFiller)
156            .filler(GasOracleFiller::new(requestor.clone(), cfg.gas_oracle_url.clone()))
157            .filler(BlobGasFiller)
158            .connect_client(rpc_client);
159
160        debug!("{:?}", cfg.contract_addrs);
161
162        Ok(Self {
163            contract_instances: Arc::new(ContractInstances::new(
164                &cfg.contract_addrs,
165                provider.clone(),
166                use_dummy_nr.unwrap_or(cfg!(test)),
167            )),
168            node_module: HoprNodeManagementModule::new(cfg.module_address.into(), provider.clone()),
169            node_safe: SafeSingleton::new(cfg.safe_address.into(), provider.clone()),
170            cfg,
171            provider: Arc::new(provider),
172        })
173    }
174
175    pub(crate) async fn get_block_number(&self) -> Result<u64> {
176        Ok(self
177            .provider
178            .get_block_number()
179            .await?
180            .saturating_sub(self.cfg.finality as u64))
181    }
182
183    pub(crate) async fn get_block(&self, block_number: u64) -> Result<Option<Block>> {
184        let sanitized_block_number = block_number.saturating_sub(self.cfg.finality as u64);
185        let result = self.provider.get_block_by_number(sanitized_block_number.into()).await?;
186        Ok(result)
187    }
188
189    pub(crate) async fn get_xdai_balance(&self, address: Address) -> Result<XDaiBalance> {
190        Ok(XDaiBalance::from(U256::from_be_bytes(
191            self.provider.get_balance(address.into()).await?.to_be_bytes::<32>(),
192        )))
193    }
194
195    pub(crate) async fn get_hopr_balance(&self, address: Address) -> Result<HoprBalance> {
196        Ok(HoprBalance::from(U256::from_be_bytes(
197            self.contract_instances
198                .token
199                .balanceOf(address.into())
200                .call()
201                .await?
202                .to_be_bytes::<32>(),
203        )))
204    }
205
206    pub(crate) async fn get_hopr_allowance(&self, owner: Address, spender: Address) -> Result<HoprBalance> {
207        Ok(HoprBalance::from(U256::from_be_bytes(
208            self.contract_instances
209                .token
210                .allowance(owner.into(), spender.into())
211                .call()
212                .await?
213                .to_be_bytes::<32>(),
214        )))
215    }
216}
217
218#[async_trait]
219impl<R: HttpRequestor + 'static + Clone> HoprRpcOperations for RpcOperations<R> {
220    async fn get_timestamp(&self, block_number: u64) -> Result<Option<u64>> {
221        Ok(self.get_block(block_number).await?.map(|b| b.header.timestamp))
222    }
223
224    async fn get_xdai_balance(&self, address: Address) -> Result<XDaiBalance> {
225        self.get_xdai_balance(address).await
226    }
227
228    async fn get_hopr_balance(&self, address: Address) -> Result<HoprBalance> {
229        self.get_hopr_balance(address).await
230    }
231
232    async fn get_hopr_allowance(&self, owner: Address, spender: Address) -> Result<HoprBalance> {
233        self.get_hopr_allowance(owner, spender).await
234    }
235
236    async fn get_minimum_network_winning_probability(&self) -> Result<WinningProbability> {
237        match self.contract_instances.win_prob_oracle.currentWinProb().call().await {
238            Ok(encoded_win_prob) => {
239                let mut encoded: EncodedWinProb = Default::default();
240                encoded.copy_from_slice(&encoded_win_prob.to_be_bytes_vec());
241                Ok(encoded.into())
242            }
243            Err(e) => Err(e.into()),
244        }
245    }
246
247    async fn get_minimum_network_ticket_price(&self) -> Result<HoprBalance> {
248        Ok(self
249            .contract_instances
250            .price_oracle
251            .currentTicketPrice()
252            .call()
253            .await
254            .map(|v| HoprBalance::from(U256::from_be_bytes(v.to_be_bytes::<32>())))?)
255    }
256
257    async fn get_eligibility_status(&self, address: Address) -> Result<bool> {
258        // 2) check if the node is registered to an account. In case the selfRegister is disabled
259        // (i.e., when the staking threshold is zero) this value is used to check if the node is eligible
260        let tx_2 = CallItemBuilder::new(
261            self.contract_instances
262                .network_registry
263                .nodeRegisterdToAccount(address.into()),
264        )
265        .allow_failure(false);
266
267        // 3) check if the node is registered and eligible
268        let tx_3 = CallItemBuilder::new(
269            self.contract_instances
270                .network_registry
271                .isNodeRegisteredAndEligible(address.into()),
272        )
273        .allow_failure(true);
274
275        debug!(address = %self.contract_instances.network_registry_proxy.address(),"building tx_3 for eligibility check");
276        // 1) check if the staking threshold set in the implementation is above zero
277        let (stake_threshold, node_registration, quick_check) = match &self.contract_instances.network_registry_proxy {
278            NetworkRegistryProxy::Dummy(c) => {
279                debug!(proxy_address = %c.address(), "Using dummy network registry proxy for eligibility check");
280                let tx_1_dummy = CallItemBuilder::new(c.maxAllowedRegistrations(address.into())).allow_failure(false);
281                let multicall = self
282                    .provider
283                    .multicall()
284                    .add_call(tx_1_dummy)
285                    .add_call(tx_2)
286                    .add_call(tx_3);
287                let (max_allowed_registration, node_registered_to_account, quick_check) =
288                    multicall.aggregate3().await.map_err(RpcError::MulticallError)?;
289                (
290                    max_allowed_registration
291                        .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
292                    node_registered_to_account
293                        .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
294                    quick_check,
295                )
296            }
297            NetworkRegistryProxy::Safe(c) => {
298                debug!(proxy_address = %c.address(), "Using safe network registry proxy for eligibility check");
299                let tx_1_proxy = CallItemBuilder::new(c.stakeThreshold()).allow_failure(false);
300                let multicall = self
301                    .provider
302                    .multicall()
303                    .add_call(tx_1_proxy)
304                    .add_call(tx_2)
305                    .add_call(tx_3);
306                let (stake_threshold, node_registered_to_account, quick_check) =
307                    multicall.aggregate3().await.map_err(RpcError::MulticallError)?;
308                (
309                    stake_threshold.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
310                    node_registered_to_account
311                        .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
312                    quick_check,
313                )
314            }
315        };
316
317        match &quick_check {
318            Ok(eligibility_quick_check) => return Ok(*eligibility_quick_check),
319            Err(e) => {
320                // check in details what is the failure message
321                // The "division by zero" error is caused by the self-registration,
322                // which is forbidden to the public and thus returns false
323                // therefore the eligibility check should be ignored
324                // In EVM it returns `Panic(0x12)` error, where `0x4e487b71` is the function selector
325                // https://docs.soliditylang.org/en/v0.8.12/control-structures.html#panic-via-assert-and-error-via-require
326                if e.return_data.starts_with(&[0x4e, 0x48, 0x7b, 0x71])
327                    && e.return_data[e.return_data.len() - 1] == 0x12
328                {
329                    // when receiving division by zero error, if the staking threshold is zero
330                    // and registered account is not zero, return true
331                    return Ok(stake_threshold.is_zero() && !node_registration.is_zero());
332                }
333                return Ok(false);
334            }
335        }
336    }
337
338    async fn get_node_management_module_target_info(&self, target: Address) -> Result<Option<U256>> {
339        match self.node_module.tryGetTarget(target.into()).call().await {
340            Ok(returned_result) => Ok(returned_result
341                ._0
342                .then_some(U256::from_big_endian(returned_result._1.to_be_bytes_vec().as_slice()))),
343            Err(e) => Err(e.into()),
344        }
345    }
346
347    async fn get_safe_from_node_safe_registry(&self, node_address: Address) -> Result<Address> {
348        match self
349            .contract_instances
350            .safe_registry
351            .nodeToSafe(node_address.into())
352            .call()
353            .await
354        {
355            Ok(returned_result) => Ok(returned_result.into()),
356            Err(e) => Err(e.into()),
357        }
358    }
359
360    async fn get_module_target_address(&self) -> Result<Address> {
361        match self.node_module.owner().call().await {
362            Ok(returned_result) => Ok(returned_result.into()),
363            Err(e) => Err(e.into()),
364        }
365    }
366
367    async fn get_channel_closure_notice_period(&self) -> Result<Duration> {
368        // TODO: should we cache this value internally ?
369        match self
370            .contract_instances
371            .channels
372            .noticePeriodChannelClosure()
373            .call()
374            .await
375        {
376            Ok(returned_result) => Ok(Duration::from_secs(returned_result.into())),
377            Err(e) => Err(e.into()),
378        }
379    }
380
381    // Check on-chain status of, node, safe, and module
382    async fn check_node_safe_module_status(&self, node_address: Address) -> Result<NodeSafeModuleStatus> {
383        // 1) check if the node is already included into the module
384        let tx_1 = CallItemBuilder::new(self.node_module.isNode(node_address.into())).allow_failure(false);
385        // 2) if the module is enabled in the safe
386        let tx_2 =
387            CallItemBuilder::new(self.node_safe.isModuleEnabled(self.cfg.module_address.into())).allow_failure(false);
388        // 3) if the safe is the owner of the module
389        let tx_3 = CallItemBuilder::new(self.node_module.owner()).allow_failure(false);
390        let multicall = self.provider.multicall().add_call(tx_1).add_call(tx_2).add_call(tx_3);
391
392        let (node_in_module_inclusion, module_safe_enabling, safe_of_module_ownership) =
393            multicall.aggregate3_value().await.map_err(RpcError::MulticallError)?;
394        let is_node_included_in_module =
395            node_in_module_inclusion.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?;
396        let is_module_enabled_in_safe =
397            module_safe_enabling.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?;
398        let is_safe_owner_of_module = self.cfg.safe_address.eq(&safe_of_module_ownership
399            .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?
400            .0
401            .0
402            .into());
403
404        Ok(NodeSafeModuleStatus {
405            is_node_included_in_module,
406            is_module_enabled_in_safe,
407            is_safe_owner_of_module,
408        })
409    }
410
411    async fn send_transaction(&self, tx: TransactionRequest) -> Result<PendingTransaction> {
412        let sent_tx = self.provider.send_transaction(tx).await?;
413
414        let receipt = sent_tx
415            .with_required_confirmations(self.cfg.finality as u64)
416            .register()
417            .await
418            .map_err(RpcError::PendingTransactionError)?;
419
420        Ok(receipt)
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use std::{sync::Arc, time::Duration};
427
428    use alloy::{
429        network::{Ethereum, TransactionBuilder},
430        primitives::{U256, address},
431        providers::Provider,
432        rpc::{client::ClientBuilder, types::TransactionRequest},
433        transports::{http::ReqwestTransport, layers::RetryBackoffLayer},
434    };
435    use hex_literal::hex;
436    use hopr_async_runtime::prelude::sleep;
437    use hopr_chain_types::{ContractAddresses, ContractInstances, NetworkRegistryProxy, utils::create_native_transfer};
438    use hopr_crypto_types::keypairs::{ChainKeypair, Keypair};
439    use hopr_primitive_types::prelude::*;
440    use primitive_types::H160;
441
442    use crate::{
443        HoprRpcOperations, PendingTransaction,
444        client::{AnvilRpcClient, create_rpc_client_to_anvil},
445        errors::Result,
446        rpc::{RpcOperations, RpcOperationsConfig},
447    };
448
449    lazy_static::lazy_static! {
450        static ref RANDY: Address = hex!("762614a5ed652457a2f1cdb8006380530c26ae6a").into();
451        static ref MULTICALL3_DEPLOY_CODE: [u8; 3926] = hex!("f90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086");
452        static ref MULTICALL3_DEPLOYER_ADDRESS: alloy::primitives::Address = address!("05f32b3cc3888453ff71b01135b34ff8e41263f2");
453    }
454
455    pub const ETH_VALUE_FOR_MULTICALL3_DEPLOYER: u128 = 100_000_000_000_000_000;
456    // 0.1 (anvil) ETH
457
458    pub async fn wait_until_tx(pending: PendingTransaction, timeout: Duration) {
459        let tx_hash = *pending.tx_hash();
460        sleep(timeout).await;
461        pending
462            .await
463            .unwrap_or_else(|_| panic!("timeout awaiting tx hash {tx_hash} after {} seconds", timeout.as_secs()));
464    }
465
466    /// Deploy a MULTICALL contract into Anvil local chain for testing
467    pub async fn deploy_multicall3_to_anvil<P: Provider>(provider: &P) -> Result<()> {
468        // Fund Multicall3 deployer and deploy ERC1820Registry
469        let tx = TransactionRequest::default()
470            .with_to(*MULTICALL3_DEPLOYER_ADDRESS)
471            .with_value(U256::from(ETH_VALUE_FOR_MULTICALL3_DEPLOYER));
472
473        provider.send_transaction(tx).await?.watch().await?;
474
475        provider
476            .send_raw_transaction(MULTICALL3_DEPLOY_CODE.as_ref())
477            .await?
478            .watch()
479            .await?;
480        Ok(())
481    }
482
483    #[tokio::test]
484    async fn test_should_estimate_tx() -> anyhow::Result<()> {
485        let _ = env_logger::builder().is_test(true).try_init();
486
487        let expected_block_time = Duration::from_secs(1);
488        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
489        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
490
491        let mut server = mockito::Server::new_async().await;
492        let gas_oracle_mock = server.mock("GET", "/gas_oracle")
493            .with_status(200)
494            .with_body(r#"{"status":"1","message":"OK","result":{"LastBlock":"38791478","SafeGasPrice":"1.1","ProposeGasPrice":"1.1","FastGasPrice":"1.6","UsdPrice":"0.999985432689946"}}"#)
495            .expect(0)
496            .create_async()
497            .await;
498
499        let cfg = RpcOperationsConfig {
500            chain_id: anvil.chain_id(),
501            tx_polling_interval: Duration::from_millis(10),
502            expected_block_time,
503            finality: 2,
504            gas_oracle_url: Some((server.url() + "/gas_oracle").parse()?),
505            ..RpcOperationsConfig::default()
506        };
507
508        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
509
510        let rpc_client = ClientBuilder::default()
511            .layer(RetryBackoffLayer::new(2, 100, 100))
512            .transport(transport_client.clone(), transport_client.guess_local());
513
514        // Wait until contracts deployments are final
515        sleep((1 + cfg.finality) * expected_block_time).await;
516
517        let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
518
519        // call eth_gas_estimate
520        let fees = rpc.provider.estimate_eip1559_fees().await?;
521
522        assert!(
523            fees.max_priority_fee_per_gas.ge(&0_u128),
524            // fees.max_priority_fee_per_gas.ge(&100_000_000_u128),
525            "estimated_max_priority_fee must be equal or greater than 0, 0.1 gwei"
526        );
527
528        let estimated_gas_price = rpc.provider.get_gas_price().await?;
529        assert!(
530            estimated_gas_price.ge(&100_000_000_u128),
531            "estimated_max_fee must be greater than 0.1 gwei"
532        );
533
534        gas_oracle_mock.assert();
535
536        Ok(())
537    }
538
539    #[tokio::test]
540    async fn test_should_send_tx() -> anyhow::Result<()> {
541        let _ = env_logger::builder().is_test(true).try_init();
542
543        let expected_block_time = Duration::from_secs(1);
544        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
545        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
546
547        let cfg = RpcOperationsConfig {
548            chain_id: anvil.chain_id(),
549            tx_polling_interval: Duration::from_millis(10),
550            expected_block_time,
551            finality: 2,
552            gas_oracle_url: None,
553            ..RpcOperationsConfig::default()
554        };
555
556        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
557
558        let rpc_client = ClientBuilder::default()
559            .layer(RetryBackoffLayer::new(2, 100, 100))
560            .transport(transport_client.clone(), transport_client.guess_local());
561
562        // Wait until contracts deployments are final
563        sleep((1 + cfg.finality) * expected_block_time).await;
564
565        let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
566
567        let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
568        assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
569
570        // Send 1 ETH to some random address
571        let tx = create_native_transfer::<Ethereum>(*RANDY, U256::from(1000000_u32));
572        let tx_hash = rpc.send_transaction(tx).await?;
573
574        wait_until_tx(tx_hash, Duration::from_secs(8)).await;
575
576        Ok(())
577    }
578
579    #[tokio::test]
580    async fn test_should_send_consecutive_txs() -> anyhow::Result<()> {
581        let _ = env_logger::builder().is_test(true).try_init();
582
583        let expected_block_time = Duration::from_secs(1);
584        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
585        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
586
587        let cfg = RpcOperationsConfig {
588            chain_id: anvil.chain_id(),
589            tx_polling_interval: Duration::from_millis(10),
590            expected_block_time,
591            finality: 2,
592            gas_oracle_url: None,
593            ..RpcOperationsConfig::default()
594        };
595
596        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
597
598        let rpc_client = ClientBuilder::default()
599            .layer(RetryBackoffLayer::new(2, 100, 100))
600            .transport(transport_client.clone(), transport_client.guess_local());
601
602        // Wait until contracts deployments are final
603        sleep((1 + cfg.finality) * expected_block_time).await;
604
605        let rpc = RpcOperations::new(
606            rpc_client,
607            transport_client.client().clone(),
608            &chain_key_0,
609            cfg.clone(),
610            None,
611        )?;
612
613        let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
614        assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
615
616        let txs_count = 5_u64;
617        let send_amount = 1000000_u64;
618
619        // Send 1 ETH to some random address
620        futures::future::join_all((0..txs_count).map(|_| async {
621            rpc.send_transaction(create_native_transfer::<Ethereum>(*RANDY, U256::from(send_amount)))
622                .await
623                .expect("tx should be sent")
624                .await
625                .expect("tx should resolve")
626        }))
627        .await;
628
629        sleep((1 + cfg.finality) * expected_block_time).await;
630
631        let balance_2: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
632
633        assert!(
634            balance_2.amount() <= balance_1.amount() - txs_count * send_amount,
635            "balance must be less"
636        );
637
638        Ok(())
639    }
640
641    #[tokio::test]
642    async fn test_get_balance_native() -> anyhow::Result<()> {
643        let _ = env_logger::builder().is_test(true).try_init();
644
645        let expected_block_time = Duration::from_secs(1);
646        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
647        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
648
649        let cfg = RpcOperationsConfig {
650            chain_id: anvil.chain_id(),
651            tx_polling_interval: Duration::from_millis(10),
652            expected_block_time,
653            finality: 2,
654            gas_oracle_url: None,
655            ..RpcOperationsConfig::default()
656        };
657
658        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
659
660        let rpc_client = ClientBuilder::default()
661            .layer(RetryBackoffLayer::new(2, 100, 100))
662            .transport(transport_client.clone(), transport_client.guess_local());
663
664        // Wait until contracts deployments are final
665        sleep((1 + cfg.finality) * expected_block_time).await;
666
667        let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
668
669        let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
670        assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
671
672        // Send 1 ETH to some random address
673        let tx_hash = rpc
674            .send_transaction(create_native_transfer::<Ethereum>(*RANDY, U256::from(1_u32)))
675            .await?;
676
677        wait_until_tx(tx_hash, Duration::from_secs(8)).await;
678
679        let balance_2: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
680        assert!(balance_2.lt(&balance_1), "balance must be diminished");
681
682        Ok(())
683    }
684
685    #[tokio::test]
686    async fn test_get_balance_token() -> anyhow::Result<()> {
687        let _ = env_logger::builder().is_test(true).try_init();
688
689        let expected_block_time = Duration::from_secs(1);
690        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
691        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
692
693        // Deploy contracts
694        let contract_instances = {
695            let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
696            ContractInstances::deploy_for_testing(client, &chain_key_0).await?
697        };
698
699        let cfg = RpcOperationsConfig {
700            chain_id: anvil.chain_id(),
701            tx_polling_interval: Duration::from_millis(10),
702            expected_block_time,
703            finality: 2,
704            contract_addrs: ContractAddresses::from(&contract_instances),
705            gas_oracle_url: None,
706            ..RpcOperationsConfig::default()
707        };
708
709        let amount = 1024_u64;
710        let _ = hopr_chain_types::utils::mint_tokens(contract_instances.token, U256::from(amount)).await;
711
712        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
713
714        let rpc_client = ClientBuilder::default()
715            .layer(RetryBackoffLayer::new(2, 100, 100))
716            .transport(transport_client.clone(), transport_client.guess_local());
717
718        // Wait until contracts deployments are final
719        sleep((1 + cfg.finality) * expected_block_time).await;
720
721        let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
722
723        let balance: HoprBalance = rpc.get_hopr_balance((&chain_key_0).into()).await?;
724        assert_eq!(amount, balance.amount().as_u64(), "invalid balance");
725
726        Ok(())
727    }
728
729    #[tokio::test]
730    async fn test_check_node_safe_module_status() -> anyhow::Result<()> {
731        let _ = env_logger::builder().is_test(true).try_init();
732
733        let expected_block_time = Duration::from_secs(1);
734        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
735        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
736
737        // Deploy contracts
738        let (contract_instances, module, safe) = {
739            let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
740            let instances = ContractInstances::deploy_for_testing(client.clone(), &chain_key_0).await?;
741
742            // deploy MULTICALL contract to anvil
743            deploy_multicall3_to_anvil(&client.clone()).await?;
744
745            let (module, safe) = hopr_chain_types::utils::deploy_one_safe_one_module_and_setup_for_testing::<
746                Arc<AnvilRpcClient>,
747            >(&instances, client.clone(), &chain_key_0)
748            .await?;
749
750            // deploy a module and safe instance and add node into the module. The module is enabled by default in the
751            // safe
752            (instances, module, safe)
753        };
754
755        let cfg = RpcOperationsConfig {
756            chain_id: anvil.chain_id(),
757            tx_polling_interval: Duration::from_millis(10),
758            expected_block_time,
759            finality: 2,
760            contract_addrs: ContractAddresses::from(&contract_instances),
761            module_address: module,
762            safe_address: safe,
763            gas_oracle_url: None,
764            ..RpcOperationsConfig::default()
765        };
766
767        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
768
769        let rpc_client = ClientBuilder::default()
770            .layer(RetryBackoffLayer::new(2, 100, 100))
771            .transport(transport_client.clone(), transport_client.guess_local());
772
773        // Wait until contracts deployments are final
774        sleep((1 + cfg.finality) * expected_block_time).await;
775
776        let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
777
778        let result_before_including_node = rpc.check_node_safe_module_status((&chain_key_0).into()).await?;
779        // before including node to the safe and module, only the first chck is false, the others are true
780        assert!(
781            !result_before_including_node.is_node_included_in_module,
782            "node should not be included in a default module"
783        );
784        assert!(
785            result_before_including_node.is_module_enabled_in_safe,
786            "module should be enabled in a default safe"
787        );
788        assert!(
789            result_before_including_node.is_safe_owner_of_module,
790            "safe should not be the owner of a default module"
791        );
792
793        // including node to the module
794        hopr_chain_types::utils::include_node_to_module_by_safe(
795            contract_instances.channels.provider().clone(),
796            safe,
797            module,
798            (&chain_key_0).into(),
799            &chain_key_0,
800        )
801        .await?;
802
803        let result_with_node_included = rpc.check_node_safe_module_status((&chain_key_0).into()).await?;
804        // after the node gets included into the module, all checks should be true
805        assert!(
806            result_with_node_included.is_node_included_in_module,
807            "node should be included in a default module"
808        );
809        assert!(
810            result_with_node_included.is_module_enabled_in_safe,
811            "module should be enabled in a default safe"
812        );
813        assert!(
814            result_with_node_included.is_safe_owner_of_module,
815            "safe should be the owner of a default module"
816        );
817
818        Ok(())
819    }
820
821    #[tokio::test]
822    async fn test_get_eligibility_status() -> anyhow::Result<()> {
823        let _ = env_logger::builder().is_test(true).try_init();
824
825        let expected_block_time = Duration::from_secs(1);
826        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
827        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
828        let node_address: H160 = chain_key_0.public().to_address().into();
829
830        // Deploy contracts
831        let (contract_instances, module, safe) = {
832            let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
833            let instances = ContractInstances::deploy_for_testing(client.clone(), &chain_key_0).await?;
834
835            // deploy MULTICALL contract to anvil
836            deploy_multicall3_to_anvil(&client.clone()).await?;
837
838            let (module, safe) = hopr_chain_types::utils::deploy_one_safe_one_module_and_setup_for_testing::<
839                Arc<AnvilRpcClient>,
840            >(&instances, client.clone(), &chain_key_0)
841            .await?;
842
843            // deploy a module and safe instance and add node into the module. The module is enabled by default in the
844            // safe
845            (instances, module, safe)
846        };
847
848        let cfg = RpcOperationsConfig {
849            chain_id: anvil.chain_id(),
850            tx_polling_interval: Duration::from_millis(10),
851            expected_block_time,
852            finality: 2,
853            contract_addrs: ContractAddresses::from(&contract_instances),
854            module_address: module,
855            safe_address: safe,
856            gas_oracle_url: None,
857            ..RpcOperationsConfig::default()
858        };
859
860        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
861
862        let rpc_client = ClientBuilder::default()
863            .layer(RetryBackoffLayer::new(2, 100, 100))
864            .transport(transport_client.clone(), transport_client.guess_local());
865
866        // Wait until contracts deployments are final
867        sleep((1 + cfg.finality) * expected_block_time).await;
868
869        let rpc = RpcOperations::new(
870            rpc_client,
871            transport_client.client().clone(),
872            &chain_key_0,
873            cfg.clone(),
874            None,
875        )?;
876
877        // check the eligibility status (before registering in the NetworkRegistry contract)
878        let result_before_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
879
880        assert!(
881            !result_before_register_in_the_network_registry,
882            "node should not be eligible"
883        );
884
885        // register the node
886        match &rpc.contract_instances.network_registry_proxy {
887            NetworkRegistryProxy::Dummy(e) => {
888                let _ = e.ownerAddAccount(cfg.safe_address.into()).send().await?.watch().await?;
889            }
890            NetworkRegistryProxy::Safe(_) => {}
891        };
892
893        let _ = rpc
894            .contract_instances
895            .network_registry
896            .managerRegister(
897                vec![cfg.safe_address.into()],
898                vec![alloy::primitives::Address::from_slice(node_address.as_ref())],
899            )
900            .send()
901            .await?
902            .watch()
903            .await?;
904
905        // check the eligibility status (after registering in the NetworkRegistry contract)
906        let result_after_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
907
908        assert!(result_after_register_in_the_network_registry, "node should be eligible");
909        Ok(())
910    }
911
912    #[tokio::test]
913    async fn test_get_eligibility_status_for_staking_proxy() -> anyhow::Result<()> {
914        let _ = env_logger::builder().is_test(true).try_init();
915
916        let expected_block_time = Duration::from_secs(1);
917        let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
918        let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
919        let node_address: H160 = chain_key_0.public().to_address().into();
920
921        // Deploy contracts
922        let (contract_instances, module, safe) = {
923            let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
924            let instances =
925                ContractInstances::deploy_for_testing_with_staking_proxy(client.clone(), &chain_key_0).await?;
926
927            // deploy MULTICALL contract to anvil
928            deploy_multicall3_to_anvil(&client.clone()).await?;
929
930            let (module, safe) = hopr_chain_types::utils::deploy_one_safe_one_module_and_setup_for_testing::<
931                Arc<AnvilRpcClient>,
932            >(&instances, client.clone(), &chain_key_0)
933            .await?;
934
935            // deploy a module and safe instance and add node into the module. The module is enabled by default in the
936            // safe
937            (instances, module, safe)
938        };
939
940        let cfg = RpcOperationsConfig {
941            chain_id: anvil.chain_id(),
942            tx_polling_interval: Duration::from_millis(10),
943            expected_block_time,
944            finality: 2,
945            contract_addrs: ContractAddresses::from(&contract_instances),
946            module_address: module,
947            safe_address: safe,
948            gas_oracle_url: None,
949            ..RpcOperationsConfig::default()
950        };
951
952        let transport_client = ReqwestTransport::new(anvil.endpoint_url());
953
954        let rpc_client = ClientBuilder::default()
955            .layer(RetryBackoffLayer::new(2, 100, 100))
956            .transport(transport_client.clone(), transport_client.guess_local());
957
958        // Wait until contracts deployments are final
959        sleep((1 + cfg.finality) * expected_block_time).await;
960
961        let rpc = RpcOperations::new(
962            rpc_client,
963            transport_client.client().clone(),
964            &chain_key_0,
965            cfg.clone(),
966            Some(false),
967        )?;
968
969        // check the eligibility status (before registering in the NetworkRegistry contract)
970        let result_before_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
971
972        assert!(
973            !result_before_register_in_the_network_registry,
974            "node should not be eligible"
975        );
976
977        // register the node
978        match &rpc.contract_instances.network_registry_proxy {
979            NetworkRegistryProxy::Dummy(p) => {
980                let _ = p.ownerAddAccount(cfg.safe_address.into()).send().await?.watch().await?;
981            }
982            NetworkRegistryProxy::Safe(_) => {}
983        };
984
985        let _ = rpc
986            .contract_instances
987            .network_registry
988            .managerRegister(
989                vec![cfg.safe_address.into()],
990                vec![alloy::primitives::Address::from_slice(node_address.as_ref())],
991            )
992            .send()
993            .await?
994            .watch()
995            .await?;
996
997        // check the eligibility status (after registering in the NetworkRegistry contract)
998        let result_after_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
999
1000        assert!(result_after_register_in_the_network_registry, "node should be eligible");
1001        Ok(())
1002    }
1003}