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