1use 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::{
28 keypairs::{ChainKeypair, Keypair},
29 prelude::Hash,
30};
31use hopr_internal_types::prelude::{EncodedWinProb, WinningProbability};
32use hopr_primitive_types::prelude::*;
33use primitive_types::U256;
34use serde::{Deserialize, Serialize};
35use tracing::debug;
36use url::Url;
37use validator::Validate;
38
39use crate::{
41 HoprRpcOperations, NodeSafeModuleStatus,
42 client::GasOracleFiller,
43 errors::{Result, RpcError},
44 transport::HttpRequestor,
45};
46
47sol!(
49 #![sol(abi)]
50 #![sol(rpc)]
51 contract SafeSingleton {
52 function isModuleEnabled(address module) public view returns (bool);
53 }
54);
55
56pub const DEFAULT_GAS_ORACLE_URL: &str = "https://ggnosis.blockscan.com/gasapi.ashx?apikey=key&method=gasoracle";
58
59#[derive(Clone, Debug, PartialEq, Eq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
61pub struct RpcOperationsConfig {
62 #[default = 100]
66 pub chain_id: u64,
67 pub contract_addrs: ContractAddresses,
71 pub module_address: Address,
75 pub safe_address: Address,
79 #[default(Duration::from_secs(5))]
83 pub expected_block_time: Duration,
84 #[validate(range(min = 1))]
90 #[default = 2000]
91 pub max_block_range_fetch_size: u64,
92 #[default(Duration::from_secs(7))]
96 pub tx_polling_interval: Duration,
97 #[validate(range(min = 1, max = 100))]
105 #[default = 8]
106 pub finality: u32,
107 #[default(Some(DEFAULT_GAS_ORACLE_URL.parse().unwrap()))]
111 pub gas_oracle_url: Option<Url>,
112}
113
114pub(crate) type HoprProvider<R> = FillProvider<
115 JoinFill<
116 JoinFill<
117 JoinFill<
118 JoinFill<
119 JoinFill<JoinFill<Identity, WalletFiller<EthereumWallet>>, ChainIdFiller>,
120 NonceFiller<CachedNonceManager>,
121 >,
122 GasFiller,
123 >,
124 GasOracleFiller<R>,
125 >,
126 BlobGasFiller,
127 >,
128 RootProvider,
129>;
130
131#[derive(Debug, Clone)]
133pub struct RpcOperations<R: HttpRequestor + 'static + Clone> {
134 pub(crate) provider: Arc<HoprProvider<R>>,
135 pub(crate) cfg: RpcOperationsConfig,
136 contract_instances: Arc<ContractInstances<HoprProvider<R>>>,
137 node_module: HoprNodeManagementModuleInstance<HoprProvider<R>>,
138 node_safe: SafeSingletonInstance<HoprProvider<R>>,
139}
140
141#[cfg_attr(test, mockall::automock)]
142impl<R: HttpRequestor + 'static + Clone> RpcOperations<R> {
143 pub fn new(
144 rpc_client: RpcClient,
145 requestor: R,
146 chain_key: &ChainKeypair,
147 cfg: RpcOperationsConfig,
148 use_dummy_nr: Option<bool>,
149 ) -> Result<Self> {
150 let wallet =
151 PrivateKeySigner::from_slice(chain_key.secret().as_ref()).map_err(|e| RpcError::SignerError(e.into()))?;
152
153 let provider = ProviderBuilder::new()
154 .disable_recommended_fillers()
155 .wallet(wallet)
156 .filler(ChainIdFiller::default())
157 .filler(NonceFiller::new(CachedNonceManager::default()))
158 .filler(GasFiller)
159 .filler(GasOracleFiller::new(requestor.clone(), cfg.gas_oracle_url.clone()))
160 .filler(BlobGasFiller)
161 .connect_client(rpc_client);
162
163 debug!("{:?}", cfg.contract_addrs);
164
165 Ok(Self {
166 contract_instances: Arc::new(ContractInstances::new(
167 &cfg.contract_addrs,
168 provider.clone(),
169 use_dummy_nr.unwrap_or(cfg!(test)),
170 )),
171 node_module: HoprNodeManagementModule::new(cfg.module_address.into(), provider.clone()),
172 node_safe: SafeSingleton::new(cfg.safe_address.into(), provider.clone()),
173 cfg,
174 provider: Arc::new(provider),
175 })
176 }
177
178 pub(crate) async fn get_block_number(&self) -> Result<u64> {
179 Ok(self
180 .provider
181 .get_block_number()
182 .await?
183 .saturating_sub(self.cfg.finality as u64))
184 }
185
186 pub(crate) async fn get_block(&self, block_number: u64) -> Result<Option<Block>> {
187 let sanitized_block_number = block_number.saturating_sub(self.cfg.finality as u64);
188 let result = self.provider.get_block_by_number(sanitized_block_number.into()).await?;
189 Ok(result)
190 }
191
192 pub(crate) async fn get_xdai_balance(&self, address: Address) -> Result<XDaiBalance> {
193 Ok(XDaiBalance::from(U256::from_be_bytes(
194 self.provider.get_balance(address.into()).await?.to_be_bytes::<32>(),
195 )))
196 }
197
198 pub(crate) async fn get_hopr_balance(&self, address: Address) -> Result<HoprBalance> {
199 Ok(HoprBalance::from(U256::from_be_bytes(
200 self.contract_instances
201 .token
202 .balanceOf(address.into())
203 .call()
204 .await?
205 .to_be_bytes::<32>(),
206 )))
207 }
208
209 pub(crate) async fn get_hopr_allowance(&self, owner: Address, spender: Address) -> Result<HoprBalance> {
210 Ok(HoprBalance::from(U256::from_be_bytes(
211 self.contract_instances
212 .token
213 .allowance(owner.into(), spender.into())
214 .call()
215 .await?
216 .to_be_bytes::<32>(),
217 )))
218 }
219}
220
221#[async_trait]
222impl<R: HttpRequestor + 'static + Clone> HoprRpcOperations for RpcOperations<R> {
223 async fn get_timestamp(&self, block_number: u64) -> Result<Option<u64>> {
224 Ok(self.get_block(block_number).await?.map(|b| b.header.timestamp))
225 }
226
227 async fn get_xdai_balance(&self, address: Address) -> Result<XDaiBalance> {
228 self.get_xdai_balance(address).await
229 }
230
231 async fn get_hopr_balance(&self, address: Address) -> Result<HoprBalance> {
232 self.get_hopr_balance(address).await
233 }
234
235 async fn get_hopr_allowance(&self, owner: Address, spender: Address) -> Result<HoprBalance> {
236 self.get_hopr_allowance(owner, spender).await
237 }
238
239 async fn get_minimum_network_winning_probability(&self) -> Result<WinningProbability> {
240 match self.contract_instances.win_prob_oracle.currentWinProb().call().await {
241 Ok(encoded_win_prob) => {
242 let mut encoded: EncodedWinProb = Default::default();
243 encoded.copy_from_slice(&encoded_win_prob.to_be_bytes_vec());
244 Ok(encoded.into())
245 }
246 Err(e) => Err(e.into()),
247 }
248 }
249
250 async fn get_minimum_network_ticket_price(&self) -> Result<HoprBalance> {
251 Ok(self
252 .contract_instances
253 .price_oracle
254 .currentTicketPrice()
255 .call()
256 .await
257 .map(|v| HoprBalance::from(U256::from_be_bytes(v.to_be_bytes::<32>())))?)
258 }
259
260 async fn get_eligibility_status(&self, address: Address) -> Result<bool> {
261 let tx_2 = CallItemBuilder::new(
264 self.contract_instances
265 .network_registry
266 .nodeRegisterdToAccount(address.into()),
267 )
268 .allow_failure(false);
269
270 let tx_3 = CallItemBuilder::new(
272 self.contract_instances
273 .network_registry
274 .isNodeRegisteredAndEligible(address.into()),
275 )
276 .allow_failure(true);
277
278 debug!(address = %self.contract_instances.network_registry_proxy.address(),"building tx_3 for eligibility check");
279 let (stake_threshold, node_registration, quick_check) = match &self.contract_instances.network_registry_proxy {
281 NetworkRegistryProxy::Dummy(c) => {
282 debug!(proxy_address = %c.address(), "Using dummy network registry proxy for eligibility check");
283 let tx_1_dummy = CallItemBuilder::new(c.maxAllowedRegistrations(address.into())).allow_failure(false);
284 let multicall = self
285 .provider
286 .multicall()
287 .add_call(tx_1_dummy)
288 .add_call(tx_2)
289 .add_call(tx_3);
290 let (max_allowed_registration, node_registered_to_account, quick_check) =
291 multicall.aggregate3().await.map_err(RpcError::MulticallError)?;
292 (
293 max_allowed_registration
294 .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
295 node_registered_to_account
296 .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
297 quick_check,
298 )
299 }
300 NetworkRegistryProxy::Safe(c) => {
301 debug!(proxy_address = %c.address(), "Using safe network registry proxy for eligibility check");
302 let tx_1_proxy = CallItemBuilder::new(c.stakeThreshold()).allow_failure(false);
303 let multicall = self
304 .provider
305 .multicall()
306 .add_call(tx_1_proxy)
307 .add_call(tx_2)
308 .add_call(tx_3);
309 let (stake_threshold, node_registered_to_account, quick_check) =
310 multicall.aggregate3().await.map_err(RpcError::MulticallError)?;
311 (
312 stake_threshold.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
313 node_registered_to_account
314 .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?,
315 quick_check,
316 )
317 }
318 };
319
320 match &quick_check {
321 Ok(eligibility_quick_check) => return Ok(*eligibility_quick_check),
322 Err(e) => {
323 if e.return_data.starts_with(&[0x4e, 0x48, 0x7b, 0x71])
330 && e.return_data[e.return_data.len() - 1] == 0x12
331 {
332 return Ok(stake_threshold.is_zero() && !node_registration.is_zero());
335 }
336 return Ok(false);
337 }
338 }
339 }
340
341 async fn get_node_management_module_target_info(&self, target: Address) -> Result<Option<U256>> {
342 match self.node_module.tryGetTarget(target.into()).call().await {
343 Ok(returned_result) => Ok(returned_result
344 ._0
345 .then_some(U256::from_big_endian(returned_result._1.to_be_bytes_vec().as_slice()))),
346 Err(e) => Err(e.into()),
347 }
348 }
349
350 async fn get_safe_from_node_safe_registry(&self, node_address: Address) -> Result<Address> {
351 match self
352 .contract_instances
353 .safe_registry
354 .nodeToSafe(node_address.into())
355 .call()
356 .await
357 {
358 Ok(returned_result) => Ok(returned_result.into()),
359 Err(e) => Err(e.into()),
360 }
361 }
362
363 async fn get_module_target_address(&self) -> Result<Address> {
364 match self.node_module.owner().call().await {
365 Ok(returned_result) => Ok(returned_result.into()),
366 Err(e) => Err(e.into()),
367 }
368 }
369
370 async fn get_channel_closure_notice_period(&self) -> Result<Duration> {
371 match self
373 .contract_instances
374 .channels
375 .noticePeriodChannelClosure()
376 .call()
377 .await
378 {
379 Ok(returned_result) => Ok(Duration::from_secs(returned_result.into())),
380 Err(e) => Err(e.into()),
381 }
382 }
383
384 async fn check_node_safe_module_status(&self, node_address: Address) -> Result<NodeSafeModuleStatus> {
386 let tx_1 = CallItemBuilder::new(self.node_module.isNode(node_address.into())).allow_failure(false);
388 let tx_2 =
390 CallItemBuilder::new(self.node_safe.isModuleEnabled(self.cfg.module_address.into())).allow_failure(false);
391 let tx_3 = CallItemBuilder::new(self.node_module.owner()).allow_failure(false);
393 let multicall = self.provider.multicall().add_call(tx_1).add_call(tx_2).add_call(tx_3);
394
395 let (node_in_module_inclusion, module_safe_enabling, safe_of_module_ownership) =
396 multicall.aggregate3_value().await.map_err(RpcError::MulticallError)?;
397 let is_node_included_in_module =
398 node_in_module_inclusion.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?;
399 let is_module_enabled_in_safe =
400 module_safe_enabling.map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?;
401 let is_safe_owner_of_module = self.cfg.safe_address.eq(&safe_of_module_ownership
402 .map_err(|e| RpcError::MulticallFailure(e.idx, e.return_data.to_string()))?
403 .0
404 .0
405 .into());
406
407 Ok(NodeSafeModuleStatus {
408 is_node_included_in_module,
409 is_module_enabled_in_safe,
410 is_safe_owner_of_module,
411 })
412 }
413
414 async fn send_transaction(&self, tx: TransactionRequest) -> Result<PendingTransaction> {
415 let sent_tx = self.provider.send_transaction(tx).await?;
416
417 let pending_tx = sent_tx
418 .with_required_confirmations(self.cfg.finality as u64)
419 .register()
420 .await
421 .map_err(RpcError::PendingTransactionError)?;
422
423 Ok(pending_tx)
424 }
425
426 async fn send_transaction_with_confirm(&self, tx: TransactionRequest) -> Result<Hash> {
427 let sent_tx = self.provider.send_transaction(tx).await?;
428
429 let receipt = sent_tx
430 .with_required_confirmations(self.cfg.finality as u64)
431 .get_receipt()
432 .await
433 .map_err(RpcError::PendingTransactionError)?;
434
435 let tx_hash = Hash::from(receipt.transaction_hash.0);
436
437 if receipt.status() {
440 Ok(tx_hash)
441 } else {
442 Err(RpcError::TransactionFailed(tx_hash))
444 }
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use std::{sync::Arc, time::Duration};
451
452 use alloy::{
453 network::{Ethereum, TransactionBuilder},
454 primitives::{U256, address},
455 providers::Provider,
456 rpc::{client::ClientBuilder, types::TransactionRequest},
457 transports::{http::ReqwestTransport, layers::RetryBackoffLayer},
458 };
459 use hex_literal::hex;
460 use hopr_async_runtime::prelude::sleep;
461 use hopr_chain_types::{ContractAddresses, ContractInstances, NetworkRegistryProxy, utils::create_native_transfer};
462 use hopr_crypto_types::keypairs::{ChainKeypair, Keypair};
463 use hopr_primitive_types::prelude::*;
464 use primitive_types::H160;
465
466 use crate::{
467 HoprRpcOperations, PendingTransaction,
468 client::{AnvilRpcClient, create_rpc_client_to_anvil},
469 errors::Result,
470 rpc::{RpcOperations, RpcOperationsConfig},
471 };
472
473 lazy_static::lazy_static! {
474 static ref RANDY: Address = hex!("762614a5ed652457a2f1cdb8006380530c26ae6a").into();
475 static ref MULTICALL3_DEPLOY_CODE: [u8; 3926] = hex!("f90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086");
476 static ref MULTICALL3_DEPLOYER_ADDRESS: alloy::primitives::Address = address!("05f32b3cc3888453ff71b01135b34ff8e41263f2");
477 }
478
479 pub const ETH_VALUE_FOR_MULTICALL3_DEPLOYER: u128 = 100_000_000_000_000_000;
480 pub async fn wait_until_tx(pending: PendingTransaction, timeout: Duration) {
483 let tx_hash = *pending.tx_hash();
484 sleep(timeout).await;
485 pending
486 .await
487 .unwrap_or_else(|_| panic!("timeout awaiting tx hash {tx_hash} after {} seconds", timeout.as_secs()));
488 }
489
490 pub async fn deploy_multicall3_to_anvil<P: Provider>(provider: &P) -> Result<()> {
492 let tx = TransactionRequest::default()
494 .with_to(*MULTICALL3_DEPLOYER_ADDRESS)
495 .with_value(U256::from(ETH_VALUE_FOR_MULTICALL3_DEPLOYER));
496
497 provider.send_transaction(tx).await?.watch().await?;
498
499 provider
500 .send_raw_transaction(MULTICALL3_DEPLOY_CODE.as_ref())
501 .await?
502 .watch()
503 .await?;
504 Ok(())
505 }
506
507 #[tokio::test]
508 async fn test_should_estimate_tx() -> anyhow::Result<()> {
509 let _ = env_logger::builder().is_test(true).try_init();
510
511 let expected_block_time = Duration::from_secs(1);
512 let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
513 let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
514
515 let mut server = mockito::Server::new_async().await;
516 let gas_oracle_mock = server.mock("GET", "/gas_oracle")
517 .with_status(200)
518 .with_body(r#"{"status":"1","message":"OK","result":{"LastBlock":"38791478","SafeGasPrice":"1.1","ProposeGasPrice":"1.1","FastGasPrice":"1.6","UsdPrice":"0.999985432689946"}}"#)
519 .expect(0)
520 .create_async()
521 .await;
522
523 let cfg = RpcOperationsConfig {
524 chain_id: anvil.chain_id(),
525 tx_polling_interval: Duration::from_millis(10),
526 expected_block_time,
527 finality: 2,
528 gas_oracle_url: Some((server.url() + "/gas_oracle").parse()?),
529 ..RpcOperationsConfig::default()
530 };
531
532 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
533
534 let rpc_client = ClientBuilder::default()
535 .layer(RetryBackoffLayer::new(2, 100, 100))
536 .transport(transport_client.clone(), transport_client.guess_local());
537
538 sleep((1 + cfg.finality) * expected_block_time).await;
540
541 let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
542
543 let fees = rpc.provider.estimate_eip1559_fees().await?;
545
546 assert!(
547 fees.max_priority_fee_per_gas.ge(&0_u128),
548 "estimated_max_priority_fee must be equal or greater than 0, 0.1 gwei"
550 );
551
552 let estimated_gas_price = rpc.provider.get_gas_price().await?;
553 assert!(
554 estimated_gas_price.ge(&100_000_000_u128),
555 "estimated_max_fee must be greater than 0.1 gwei"
556 );
557
558 gas_oracle_mock.assert();
559
560 Ok(())
561 }
562
563 #[tokio::test]
564 async fn test_should_send_tx() -> anyhow::Result<()> {
565 let _ = env_logger::builder().is_test(true).try_init();
566
567 let expected_block_time = Duration::from_secs(1);
568 let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
569 let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
570
571 let cfg = RpcOperationsConfig {
572 chain_id: anvil.chain_id(),
573 tx_polling_interval: Duration::from_millis(10),
574 expected_block_time,
575 finality: 2,
576 gas_oracle_url: None,
577 ..RpcOperationsConfig::default()
578 };
579
580 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
581
582 let rpc_client = ClientBuilder::default()
583 .layer(RetryBackoffLayer::new(2, 100, 100))
584 .transport(transport_client.clone(), transport_client.guess_local());
585
586 sleep((1 + cfg.finality) * expected_block_time).await;
588
589 let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
590
591 let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
592 assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
593
594 let tx_1 = create_native_transfer::<Ethereum>(*RANDY, U256::from(1000000_u32));
596 let tx_hash = rpc.send_transaction(tx_1).await?;
597
598 wait_until_tx(tx_hash, Duration::from_secs(8)).await;
599
600 let tx_2 = create_native_transfer::<Ethereum>(*RANDY, U256::from(1000000_u32));
602 let _receipt = rpc.send_transaction_with_confirm(tx_2).await?;
603
604 Ok(())
605 }
606
607 #[tokio::test]
608 async fn test_should_send_consecutive_txs() -> anyhow::Result<()> {
609 let _ = env_logger::builder().is_test(true).try_init();
610
611 let expected_block_time = Duration::from_secs(1);
612 let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
613 let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
614
615 let cfg = RpcOperationsConfig {
616 chain_id: anvil.chain_id(),
617 tx_polling_interval: Duration::from_millis(10),
618 expected_block_time,
619 finality: 2,
620 gas_oracle_url: None,
621 ..RpcOperationsConfig::default()
622 };
623
624 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
625
626 let rpc_client = ClientBuilder::default()
627 .layer(RetryBackoffLayer::new(2, 100, 100))
628 .transport(transport_client.clone(), transport_client.guess_local());
629
630 sleep((1 + cfg.finality) * expected_block_time).await;
632
633 let rpc = RpcOperations::new(
634 rpc_client,
635 transport_client.client().clone(),
636 &chain_key_0,
637 cfg.clone(),
638 None,
639 )?;
640
641 let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
642 assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
643
644 let txs_count = 5_u64;
645 let send_amount = 1000000_u64;
646
647 futures::future::join_all((0..txs_count).map(|_| async {
649 rpc.send_transaction(create_native_transfer::<Ethereum>(*RANDY, U256::from(send_amount)))
650 .await
651 .expect("tx should be sent")
652 .await
653 .expect("tx should resolve")
654 }))
655 .await;
656
657 sleep((1 + cfg.finality) * expected_block_time).await;
658
659 let balance_2: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).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 #[tokio::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 transport_client = ReqwestTransport::new(anvil.endpoint_url());
687
688 let rpc_client = ClientBuilder::default()
689 .layer(RetryBackoffLayer::new(2, 100, 100))
690 .transport(transport_client.clone(), transport_client.guess_local());
691
692 sleep((1 + cfg.finality) * expected_block_time).await;
694
695 let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
696
697 let balance_1: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
698 assert!(balance_1.amount().gt(&0.into()), "balance must be greater than 0");
699
700 let tx_hash = rpc
702 .send_transaction(create_native_transfer::<Ethereum>(*RANDY, U256::from(1_u32)))
703 .await?;
704
705 wait_until_tx(tx_hash, Duration::from_secs(8)).await;
706
707 let balance_2: XDaiBalance = rpc.get_xdai_balance((&chain_key_0).into()).await?;
708 assert!(balance_2.lt(&balance_1), "balance must be diminished");
709
710 Ok(())
711 }
712
713 #[tokio::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 let contract_instances = {
723 let client = create_rpc_client_to_anvil(&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 let _ = hopr_chain_types::utils::mint_tokens(contract_instances.token, U256::from(amount)).await;
739
740 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
741
742 let rpc_client = ClientBuilder::default()
743 .layer(RetryBackoffLayer::new(2, 100, 100))
744 .transport(transport_client.clone(), transport_client.guess_local());
745
746 sleep((1 + cfg.finality) * expected_block_time).await;
748
749 let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
750
751 let balance: HoprBalance = rpc.get_hopr_balance((&chain_key_0).into()).await?;
752 assert_eq!(amount, balance.amount().as_u64(), "invalid balance");
753
754 Ok(())
755 }
756
757 #[tokio::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 let (contract_instances, module, safe) = {
767 let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
768 let instances = ContractInstances::deploy_for_testing(client.clone(), &chain_key_0).await?;
769
770 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 Arc<AnvilRpcClient>,
775 >(&instances, client.clone(), &chain_key_0)
776 .await?;
777
778 (instances, module, safe)
781 };
782
783 let cfg = RpcOperationsConfig {
784 chain_id: anvil.chain_id(),
785 tx_polling_interval: Duration::from_millis(10),
786 expected_block_time,
787 finality: 2,
788 contract_addrs: ContractAddresses::from(&contract_instances),
789 module_address: module,
790 safe_address: safe,
791 gas_oracle_url: None,
792 ..RpcOperationsConfig::default()
793 };
794
795 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
796
797 let rpc_client = ClientBuilder::default()
798 .layer(RetryBackoffLayer::new(2, 100, 100))
799 .transport(transport_client.clone(), transport_client.guess_local());
800
801 sleep((1 + cfg.finality) * expected_block_time).await;
803
804 let rpc = RpcOperations::new(rpc_client, transport_client.client().clone(), &chain_key_0, cfg, None)?;
805
806 let result_before_including_node = rpc.check_node_safe_module_status((&chain_key_0).into()).await?;
807 assert!(
809 !result_before_including_node.is_node_included_in_module,
810 "node should not be included in a default module"
811 );
812 assert!(
813 result_before_including_node.is_module_enabled_in_safe,
814 "module should be enabled in a default safe"
815 );
816 assert!(
817 result_before_including_node.is_safe_owner_of_module,
818 "safe should not be the owner of a default module"
819 );
820
821 hopr_chain_types::utils::include_node_to_module_by_safe(
823 contract_instances.channels.provider().clone(),
824 safe,
825 module,
826 (&chain_key_0).into(),
827 &chain_key_0,
828 )
829 .await?;
830
831 let result_with_node_included = rpc.check_node_safe_module_status((&chain_key_0).into()).await?;
832 assert!(
834 result_with_node_included.is_node_included_in_module,
835 "node should be included in a default module"
836 );
837 assert!(
838 result_with_node_included.is_module_enabled_in_safe,
839 "module should be enabled in a default safe"
840 );
841 assert!(
842 result_with_node_included.is_safe_owner_of_module,
843 "safe should be the owner of a default module"
844 );
845
846 Ok(())
847 }
848
849 #[tokio::test]
850 async fn test_get_eligibility_status() -> anyhow::Result<()> {
851 let _ = env_logger::builder().is_test(true).try_init();
852
853 let expected_block_time = Duration::from_secs(1);
854 let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
855 let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
856 let node_address: H160 = chain_key_0.public().to_address().into();
857
858 let (contract_instances, module, safe) = {
860 let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
861 let instances = ContractInstances::deploy_for_testing(client.clone(), &chain_key_0).await?;
862
863 deploy_multicall3_to_anvil(&client.clone()).await?;
865
866 let (module, safe) = hopr_chain_types::utils::deploy_one_safe_one_module_and_setup_for_testing::<
867 Arc<AnvilRpcClient>,
868 >(&instances, client.clone(), &chain_key_0)
869 .await?;
870
871 (instances, module, safe)
874 };
875
876 let cfg = RpcOperationsConfig {
877 chain_id: anvil.chain_id(),
878 tx_polling_interval: Duration::from_millis(10),
879 expected_block_time,
880 finality: 2,
881 contract_addrs: ContractAddresses::from(&contract_instances),
882 module_address: module,
883 safe_address: safe,
884 gas_oracle_url: None,
885 ..RpcOperationsConfig::default()
886 };
887
888 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
889
890 let rpc_client = ClientBuilder::default()
891 .layer(RetryBackoffLayer::new(2, 100, 100))
892 .transport(transport_client.clone(), transport_client.guess_local());
893
894 sleep((1 + cfg.finality) * expected_block_time).await;
896
897 let rpc = RpcOperations::new(
898 rpc_client,
899 transport_client.client().clone(),
900 &chain_key_0,
901 cfg.clone(),
902 None,
903 )?;
904
905 let result_before_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
907
908 assert!(
909 !result_before_register_in_the_network_registry,
910 "node should not be eligible"
911 );
912
913 match &rpc.contract_instances.network_registry_proxy {
915 NetworkRegistryProxy::Dummy(e) => {
916 let _ = e.ownerAddAccount(cfg.safe_address.into()).send().await?.watch().await?;
917 }
918 NetworkRegistryProxy::Safe(_) => {}
919 };
920
921 let _ = rpc
922 .contract_instances
923 .network_registry
924 .managerRegister(
925 vec![cfg.safe_address.into()],
926 vec![alloy::primitives::Address::from_slice(node_address.as_ref())],
927 )
928 .send()
929 .await?
930 .watch()
931 .await?;
932
933 let result_after_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
935
936 assert!(result_after_register_in_the_network_registry, "node should be eligible");
937 Ok(())
938 }
939
940 #[tokio::test]
941 async fn test_get_eligibility_status_for_staking_proxy() -> anyhow::Result<()> {
942 let _ = env_logger::builder().is_test(true).try_init();
943
944 let expected_block_time = Duration::from_secs(1);
945 let anvil = hopr_chain_types::utils::create_anvil(Some(expected_block_time));
946 let chain_key_0 = ChainKeypair::from_secret(anvil.keys()[0].to_bytes().as_ref())?;
947 let node_address: H160 = chain_key_0.public().to_address().into();
948
949 let (contract_instances, module, safe) = {
951 let client = create_rpc_client_to_anvil(&anvil, &chain_key_0);
952 let instances =
953 ContractInstances::deploy_for_testing_with_staking_proxy(client.clone(), &chain_key_0).await?;
954
955 deploy_multicall3_to_anvil(&client.clone()).await?;
957
958 let (module, safe) = hopr_chain_types::utils::deploy_one_safe_one_module_and_setup_for_testing::<
959 Arc<AnvilRpcClient>,
960 >(&instances, client.clone(), &chain_key_0)
961 .await?;
962
963 (instances, module, safe)
966 };
967
968 let cfg = RpcOperationsConfig {
969 chain_id: anvil.chain_id(),
970 tx_polling_interval: Duration::from_millis(10),
971 expected_block_time,
972 finality: 2,
973 contract_addrs: ContractAddresses::from(&contract_instances),
974 module_address: module,
975 safe_address: safe,
976 gas_oracle_url: None,
977 ..RpcOperationsConfig::default()
978 };
979
980 let transport_client = ReqwestTransport::new(anvil.endpoint_url());
981
982 let rpc_client = ClientBuilder::default()
983 .layer(RetryBackoffLayer::new(2, 100, 100))
984 .transport(transport_client.clone(), transport_client.guess_local());
985
986 sleep((1 + cfg.finality) * expected_block_time).await;
988
989 let rpc = RpcOperations::new(
990 rpc_client,
991 transport_client.client().clone(),
992 &chain_key_0,
993 cfg.clone(),
994 Some(false),
995 )?;
996
997 let result_before_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
999
1000 assert!(
1001 !result_before_register_in_the_network_registry,
1002 "node should not be eligible"
1003 );
1004
1005 match &rpc.contract_instances.network_registry_proxy {
1007 NetworkRegistryProxy::Dummy(p) => {
1008 let _ = p.ownerAddAccount(cfg.safe_address.into()).send().await?.watch().await?;
1009 }
1010 NetworkRegistryProxy::Safe(_) => {}
1011 };
1012
1013 let _ = rpc
1014 .contract_instances
1015 .network_registry
1016 .managerRegister(
1017 vec![cfg.safe_address.into()],
1018 vec![alloy::primitives::Address::from_slice(node_address.as_ref())],
1019 )
1020 .send()
1021 .await?
1022 .watch()
1023 .await?;
1024
1025 let result_after_register_in_the_network_registry = rpc.get_eligibility_status(node_address.into()).await?;
1027
1028 assert!(result_after_register_in_the_network_registry, "node should be eligible");
1029 Ok(())
1030 }
1031}