1use 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
34abigen!(
36 SafeSingleton,
37 r#"[
38 function isModuleEnabled(address module) public view returns (bool)
39 ]"#,
40);
41
42pub const DEFAULT_GAS_ORACLE_URL: &str = "https://ggnosis.blockscan.com/gasapi.ashx?apikey=key&method=gasoracle";
44
45#[derive(Clone, Debug, PartialEq, Eq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
47pub struct RpcOperationsConfig {
48 #[default = 100]
52 pub chain_id: u64,
53 pub contract_addrs: ContractAddresses,
57 pub module_address: Address,
61 pub safe_address: Address,
65 #[default(Duration::from_secs(5))]
69 pub expected_block_time: Duration,
70 #[validate(range(min = 1))]
76 #[default = 2000]
77 pub max_block_range_fetch_size: u64,
78 #[default(Duration::from_secs(7))]
82 pub tx_polling_interval: Duration,
83 #[validate(range(min = 1, max = 100))]
91 #[default = 8]
92 pub finality: u32,
93 #[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#[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
113impl<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 .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 .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 .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(explicit_result) => Ok(explicit_result.clone().into_bool().unwrap_or(false)),
287 Err(e) => {
288 if e.to_string().contains(DIV_BY_ZERO) {
294 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 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 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 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 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 .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 .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 .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 let _ = self.provider.initialize_nonce(None).await;
443 debug!("send outgoing tx: {:?}", tx);
444
445 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; 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 pub async fn deploy_multicall3_to_anvil<M: Middleware>(provider: Arc<M>) -> Result<(), ContractError<M>> {
496 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 sleep((1 + cfg.finality) * expected_block_time).await;
547
548 let rpc = RpcOperations::new(client, SurfRequestor::default(), &chain_key_0, cfg)?;
549
550 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 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 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 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 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 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 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 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 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 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_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 (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 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 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 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 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 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_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 (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 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 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 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 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}