1#![allow(clippy::too_many_arguments)]
6
7use std::str::FromStr;
8
9use SafeContract::SafeContractInstance;
10use alloy::{
11 contract::{Error as ContractError, Result as ContractResult},
12 network::{ReceiptResponse, TransactionBuilder},
13 primitives::{self, Bytes, U256, address, aliases, keccak256},
14 rpc::types::TransactionRequest,
15 signers::{Signer, local::PrivateKeySigner},
16 sol,
17 sol_types::{SolCall, SolValue},
18};
19use hopr_bindings::{
20 hoprchannels::HoprChannels::HoprChannelsInstance,
21 hoprnodemanagementmodule::HoprNodeManagementModule,
22 hoprnodestakefactory::HoprNodeStakeFactory,
23 hoprtoken::HoprToken::{self, HoprTokenInstance},
24};
25use hopr_crypto_types::prelude::*;
26use hopr_primitive_types::primitives::Address;
27use tracing::debug;
28
29use crate::{
30 ContractInstances, constants,
31 errors::{ChainTypesError, Result as ChainTypesResult},
32};
33
34sol!(
36 #![sol(abi)]
37 #![sol(rpc)]
38 contract SafeContract {
40 function nonce() view returns (uint256);
41 function getTransactionHash( address to, uint256 value, bytes calldata data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce) public view returns (bytes32);
42 function execTransaction(address to, uint256 value, bytes calldata data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures) public returns (bool);
43 }
44);
45
46lazy_static::lazy_static! {
47 static ref MINTER_ROLE_VALUE: primitives::FixedBytes<32> = keccak256("MINTER_ROLE");
48}
49
50pub fn create_anvil(block_time: Option<std::time::Duration>) -> alloy::node_bindings::AnvilInstance {
57 let mut anvil = alloy::node_bindings::Anvil::new()
58 .mnemonic("gentle wisdom move brush express similar canal dune emotion series because parrot");
59
60 if let Some(bt) = block_time {
61 anvil = anvil.block_time(bt.as_secs());
62 }
63
64 anvil.spawn()
65}
66
67pub async fn mint_tokens<P, N>(hopr_token: HoprTokenInstance<P, N>, amount: U256) -> ContractResult<Option<u64>>
71where
72 P: alloy::contract::private::Provider<N>,
73 N: alloy::providers::Network,
74{
75 let deployer = hopr_token
76 .provider()
77 .get_accounts()
78 .await
79 .expect("client must have a signer")[0];
80
81 hopr_token
82 .grantRole(*MINTER_ROLE_VALUE, deployer)
83 .send()
84 .await?
85 .watch()
86 .await?;
87
88 let tx_receipt = hopr_token
89 .mint(deployer, amount, Bytes::new(), Bytes::new())
90 .send()
91 .await?
92 .get_receipt()
93 .await?;
94
95 Ok(tx_receipt.block_number())
96}
97
98pub fn create_native_transfer<N>(to: Address, amount: U256) -> N::TransactionRequest
101where
102 N: alloy::providers::Network,
103{
104 N::TransactionRequest::default().with_to(to.into()).with_value(amount)
105}
106
107pub async fn fund_node<P, N>(
110 node: Address,
111 native_token: U256,
112 hopr_token: U256,
113 hopr_token_contract: HoprTokenInstance<P, N>,
114) -> ContractResult<()>
115where
116 P: alloy::contract::private::Provider<N>,
117 N: alloy::providers::Network,
118{
119 let native_transfer_tx = N::TransactionRequest::default()
120 .with_to(node.into())
121 .with_value(native_token);
122
123 let provider = hopr_token_contract.provider();
128
129 provider.send_transaction(native_transfer_tx).await?.watch().await?;
130
131 hopr_token_contract
132 .transfer(node.into(), hopr_token)
133 .send()
134 .await?
135 .watch()
136 .await?;
137 Ok(())
138}
139
140pub async fn fund_channel<P, N>(
143 counterparty: Address,
144 hopr_token: HoprTokenInstance<P, N>,
145 hopr_channels: HoprChannelsInstance<P, N>,
146 amount: U256,
147) -> ContractResult<()>
148where
149 P: alloy::contract::private::Provider<N>,
150 N: alloy::providers::Network,
151{
152 hopr_token
153 .approve(*hopr_channels.address(), amount)
154 .send()
155 .await?
156 .watch()
157 .await?;
158
159 hopr_channels
160 .fundChannel(counterparty.into(), aliases::U96::from(amount))
161 .send()
162 .await?
163 .watch()
164 .await?;
165
166 Ok(())
167}
168
169pub async fn fund_channel_from_different_client<P, N>(
172 counterparty: Address,
173 hopr_token_address: Address,
174 hopr_channels_address: Address,
175 amount: U256,
176 new_client: P,
177) -> ContractResult<()>
178where
179 P: alloy::contract::private::Provider<N> + Clone,
180 N: alloy::providers::Network,
181{
182 let hopr_token_with_new_client: HoprTokenInstance<P, N> =
183 HoprTokenInstance::new(hopr_token_address.into(), new_client.clone());
184 let hopr_channels_with_new_client = HoprChannelsInstance::new(hopr_channels_address.into(), new_client.clone());
185 hopr_token_with_new_client
186 .approve(hopr_channels_address.into(), amount)
187 .send()
188 .await?
189 .watch()
190 .await?;
191
192 hopr_channels_with_new_client
193 .fundChannel(counterparty.into(), aliases::U96::from(amount))
194 .send()
195 .await?
196 .watch()
197 .await?;
198
199 Ok(())
200}
201
202pub async fn get_safe_tx<P, N>(
204 safe_contract: SafeContractInstance<P, N>,
205 target: Address,
206 inner_tx_data: Bytes,
207 wallet: PrivateKeySigner,
208) -> ChainTypesResult<N::TransactionRequest>
209where
210 P: alloy::contract::private::Provider<N>,
211 N: alloy::providers::Network,
212{
213 let nonce = safe_contract.nonce().call().await?;
214
215 let data_hash = safe_contract
216 .getTransactionHash(
217 target.into(),
218 U256::ZERO,
219 inner_tx_data.clone(),
220 0,
221 U256::ZERO,
222 U256::ZERO,
223 U256::ZERO,
224 primitives::Address::default(),
225 wallet.address(),
226 nonce,
227 )
228 .call()
229 .await?;
230
231 let signed_data_hash = wallet.sign_hash(&data_hash).await?;
232
233 let safe_tx_data = SafeContract::execTransactionCall {
234 to: target.into(),
235 value: U256::ZERO,
236 data: inner_tx_data,
237 operation: 0,
238 safeTxGas: U256::ZERO,
239 baseGas: U256::ZERO,
240 gasPrice: U256::ZERO,
241 gasToken: primitives::Address::default(),
242 refundReceiver: wallet.address(),
243 signatures: Bytes::from(signed_data_hash.as_bytes()),
244 }
245 .abi_encode();
246
247 let safe_tx = N::TransactionRequest::default()
249 .with_to(*safe_contract.address())
250 .with_input(safe_tx_data);
251
252 Ok(safe_tx)
253}
254
255pub async fn include_node_to_module_by_safe<P, N>(
257 provider: P,
258 safe_address: Address,
259 module_address: Address,
260 node_address: Address,
261 deployer: &ChainKeypair, ) -> Result<(), ChainTypesError>
263where
264 P: alloy::contract::private::Provider<N> + Clone,
265 N: alloy::providers::Network,
266{
267 let node_target_permission = format!("{node_address:?}010203000000000000000000");
273
274 let inner_tx_data = HoprNodeManagementModule::includeNodeCall {
276 nodeDefaultTarget: U256::from_str(&node_target_permission).unwrap(),
277 }
278 .abi_encode();
279
280 let safe_contract = SafeContract::new(safe_address.into(), provider.clone());
281 let wallet = PrivateKeySigner::from_slice(deployer.secret().as_ref()).expect("failed to construct wallet");
282 let safe_tx = get_safe_tx(safe_contract, module_address, inner_tx_data.into(), wallet).await?;
283
284 provider
285 .send_transaction(safe_tx)
286 .await
287 .map_err(|e| ChainTypesError::ContractError(e.into()))?
288 .watch()
289 .await
290 .map_err(|e| ChainTypesError::ContractError(e.into()))?;
291
292 Ok(())
293}
294
295pub async fn add_announcement_as_target<P, N>(
297 provider: P,
298 safe_address: Address,
299 module_address: Address,
300 announcement_contract_address: Address,
301 deployer: &ChainKeypair, ) -> ContractResult<()>
303where
304 P: alloy::contract::private::Provider<N> + Clone,
305 N: alloy::providers::Network,
306{
307 let announcement_target_permission = format!("{announcement_contract_address:?}010003000000000000000000");
313
314 let inner_tx_data = HoprNodeManagementModule::scopeTargetTokenCall {
316 defaultTarget: U256::from_str(&announcement_target_permission).unwrap(),
317 }
318 .abi_encode();
319
320 let safe_contract = SafeContract::new(safe_address.into(), provider.clone());
321 let wallet = PrivateKeySigner::from_slice(deployer.secret().as_ref()).expect("failed to construct wallet");
322 let safe_tx = get_safe_tx(safe_contract, module_address, inner_tx_data.into(), wallet)
323 .await
324 .unwrap();
325
326 provider.send_transaction(safe_tx).await?.watch().await?;
327
328 Ok(())
329}
330
331pub async fn approve_channel_transfer_from_safe<P, N>(
333 provider: P,
334 safe_address: Address,
335 token_address: Address,
336 channel_address: Address,
337 deployer: &ChainKeypair, ) -> ContractResult<()>
339where
340 P: alloy::contract::private::Provider<N> + Clone,
341 N: alloy::providers::Network,
342{
343 let inner_tx_data = HoprToken::approveCall {
345 spender: channel_address.into(),
346 value: U256::MAX,
347 }
348 .abi_encode();
349
350 let safe_contract = SafeContract::new(safe_address.into(), provider.clone());
351 let wallet = PrivateKeySigner::from_slice(deployer.secret().as_ref()).expect("failed to construct wallet");
352 let safe_tx = get_safe_tx(safe_contract, token_address, inner_tx_data.into(), wallet)
353 .await
354 .unwrap();
355
356 provider.send_transaction(safe_tx).await?.watch().await?;
357
358 Ok(())
359}
360
361pub async fn deploy_one_safe_one_module_and_setup_for_testing<P>(
372 instances: &ContractInstances<P>,
373 provider: P,
374 deployer: &ChainKeypair,
375) -> ContractResult<(Address, Address)>
376where
377 P: alloy::providers::Provider + Clone,
378{
379 let self_address: Address = deployer.public().to_address();
381
382 let code = provider
384 .get_code_at(address!("914d7Fec6aaC8cd542e72Bca78B30650d45643d7"))
385 .await?;
386
387 if code.is_empty() {
389 debug!("deploying safe code");
390 let safe_diamond_proxy_address = {
392 let tx = TransactionRequest::default()
394 .with_to(address!("E1CB04A0fA36DdD16a06ea828007E35e1a3cBC37"))
395 .with_value(U256::from(10000000000000000u128));
396
397 provider.send_transaction(tx).await?.watch().await?;
398
399 let tx_receipt = provider
400 .send_raw_transaction(&constants::SAFE_DIAMOND_PROXY_SINGLETON_DEPLOY_CODE)
401 .await?
402 .get_receipt()
403 .await?;
404 tx_receipt.contract_address().unwrap()
405 };
406 debug!(%safe_diamond_proxy_address, "Safe diamond proxy singleton");
407
408 {
410 let _tx_safe_proxy_factory = TransactionRequest::default()
412 .with_to(safe_diamond_proxy_address)
413 .with_input(constants::SAFE_PROXY_FACTORY_DEPLOY_CODE);
414
415 let _tx_safe_compatibility_fallback_handler = TransactionRequest::default()
417 .with_to(safe_diamond_proxy_address)
418 .with_input(constants::SAFE_COMPATIBILITY_FALLBACK_HANDLER_DEPLOY_CODE);
419 let _tx_safe_multisend_call_only = TransactionRequest::default()
421 .with_to(safe_diamond_proxy_address)
422 .with_input(constants::SAFE_MULTISEND_CALL_ONLY_DEPLOY_CODE);
423 let _tx_safe_singleton = TransactionRequest::default()
425 .with_to(safe_diamond_proxy_address)
426 .with_input(constants::SAFE_SINGLETON_DEPLOY_CODE);
427 provider.send_transaction(_tx_safe_proxy_factory).await?.watch().await?;
430 provider
431 .send_transaction(_tx_safe_compatibility_fallback_handler)
432 .await?
433 .watch()
434 .await?;
435 provider
436 .send_transaction(_tx_safe_multisend_call_only)
437 .await?
438 .watch()
439 .await?;
440 provider.send_transaction(_tx_safe_singleton).await?.watch().await?;
441 }
442 }
443
444 let curr_nonce = provider
446 .get_transaction_count(self_address.into())
447 .pending()
448 .await
450 .unwrap();
451 debug!(%curr_nonce, "curr_nonce");
452
453 let nonce =
455 keccak256((Into::<primitives::Address>::into(self_address), U256::from(curr_nonce)).abi_encode_packed());
456 let default_target = format!("{:?}010103020202020202020202", instances.channels.address());
457
458 debug!(%self_address, "self_address");
459 debug!("nonce {:?}", U256::from_be_bytes(nonce.0).to_string());
460 debug!("default_target in bytes {:?}", default_target.bytes());
461 debug!("default_target in u256 {:?}", U256::from_str(&default_target).unwrap());
462
463 let typed_tx = HoprNodeStakeFactory::cloneCall {
464 moduleSingletonAddress: *instances.module_implementation.address(),
465 admins: vec![self_address.into()],
466 nonce: nonce.into(),
467 defaultTarget: U256::from_str(&default_target).unwrap().into(),
468 }
469 .abi_encode();
470
471 debug!("typed_tx {:?}", typed_tx);
472
473 let instance_deployment_tx_receipt = instances
475 .stake_factory
476 .clone(
477 *instances.module_implementation.address(),
478 vec![self_address.into()],
479 nonce.into(),
480 U256::from_str(&default_target).unwrap().into(),
481 )
482 .send()
483 .await?
484 .get_receipt()
485 .await?;
486
487 let maybe_module_tx_log =
489 instance_deployment_tx_receipt.decoded_log::<HoprNodeStakeFactory::NewHoprNodeStakeModule>();
490 let deployed_module_address: primitives::Address = if let Some(module_tx_log) = maybe_module_tx_log {
491 let HoprNodeStakeFactory::NewHoprNodeStakeModule { instance, .. } = module_tx_log.data;
492 instance
493 } else {
494 return Err(ContractError::ContractNotDeployed);
495 };
496
497 let maybe_safe_tx_log = instance_deployment_tx_receipt.decoded_log::<HoprNodeStakeFactory::NewHoprNodeStakeSafe>();
498 let deployed_safe_address: primitives::Address = if let Some(safe_tx_log) = maybe_safe_tx_log {
499 let HoprNodeStakeFactory::NewHoprNodeStakeSafe { instance } = safe_tx_log.data;
500 instance
501 } else {
502 return Err(ContractError::ContractNotDeployed);
503 };
504
505 debug!("instance_deployment_tx module instance {:?}", deployed_module_address);
506 debug!("instance_deployment_tx safe instance {:?}", deployed_safe_address);
507
508 Ok((deployed_module_address.into(), deployed_safe_address.into()))
509}