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