1use std::str::FromStr;
2
3pub use hopr_bindings::exports::alloy::rpc::types::TransactionRequest;
4use hopr_bindings::{
5 exports::alloy::{
6 self,
7 consensus::TxEnvelope,
8 eips::Encodable2718,
9 network::{EthereumWallet, TransactionBuilder},
10 primitives::{
11 B256, U256,
12 aliases::{U24, U48, U56, U96},
13 b256,
14 },
15 signers::local::PrivateKeySigner,
16 sol,
17 sol_types::{SolCall, SolValue},
18 },
19 hopr_channels::{
20 HoprChannels::{
21 RedeemableTicket as OnChainRedeemableTicket, TicketData, closeIncomingChannelCall,
22 closeIncomingChannelSafeCall, finalizeOutgoingChannelClosureCall, finalizeOutgoingChannelClosureSafeCall,
23 fundChannelCall, fundChannelSafeCall, initiateOutgoingChannelClosureCall,
24 initiateOutgoingChannelClosureSafeCall, redeemTicketCall, redeemTicketSafeCall,
25 },
26 HoprCrypto::{CompactSignature, VRFParameters},
27 },
28 hopr_node_management_module::HoprNodeManagementModule::execTransactionFromModuleCall,
29 hopr_node_safe_registry::HoprNodeSafeRegistry::{deregisterNodeBySafeCall, registerSafeByNodeCall},
30 hopr_token::HoprToken::{approveCall, sendCall, transferCall},
31};
32use hopr_crypto_types::prelude::*;
33use hopr_internal_types::prelude::*;
34use hopr_primitive_types::prelude::*;
35
36use crate::{
37 ContractAddresses, a2al,
38 errors::{
39 ChainTypesError,
40 ChainTypesError::{InvalidArguments, InvalidState, SigningError},
41 },
42 payload,
43 payload::{GasEstimation, PayloadGenerator, SignableTransaction},
44};
45
46const DEFAULT_TX_GAS: u64 = 400_000;
47
48sol! {
49 struct KeyBindAndAnnouncePayload {
51 address callerNode;
52 bytes32 ed25519_sig_0;
53 bytes32 ed25519_sig_1;
54 bytes32 ed25519_pub_key;
55 string multiaddress;
56 }
57}
58
59sol! (
60 #![sol(all_derives)]
61 struct UserData {
62 bytes32 functionIdentifier;
63 uint256 nonce;
64 bytes32 defaultTarget;
65 address[] memory admins;
66 }
67);
68
69#[repr(u8)]
70#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71enum Operation {
72 Call = 0,
73 }
75
76#[async_trait::async_trait]
77impl SignableTransaction for TransactionRequest {
78 async fn sign_and_encode_to_eip2718(
79 self,
80 nonce: u64,
81 chain_id: u64,
82 max_gas: Option<GasEstimation>,
83 chain_keypair: &ChainKeypair,
84 ) -> payload::Result<Box<[u8]>> {
85 let max_gas = max_gas.unwrap_or_default();
86 let signer: EthereumWallet = PrivateKeySigner::from_slice(chain_keypair.secret().as_ref())
87 .map_err(|e| SigningError(e.into()))?
88 .into();
89 let signed: TxEnvelope = self
90 .nonce(nonce)
91 .with_chain_id(chain_id)
92 .gas_limit(max_gas.gas_limit)
93 .max_fee_per_gas(max_gas.max_fee_per_gas)
94 .max_priority_fee_per_gas(max_gas.max_priority_fee_per_gas)
95 .build(&signer)
96 .await
97 .map_err(|e| SigningError(e.into()))?;
98
99 Ok(signed.encoded_2718().into_boxed_slice())
100 }
101}
102
103fn channels_payload(hopr_channels: alloy::primitives::Address, call_data: Vec<u8>) -> Vec<u8> {
104 execTransactionFromModuleCall {
105 to: hopr_channels,
106 value: U256::ZERO,
107 data: call_data.into(),
108 operation: Operation::Call as u8,
109 }
110 .abi_encode()
111}
112
113fn approve_tx(spender: alloy::primitives::Address, amount: HoprBalance) -> TransactionRequest {
114 TransactionRequest::default().with_input(
115 approveCall {
116 spender,
117 value: U256::from_be_bytes(amount.amount().to_be_bytes()),
118 }
119 .abi_encode(),
120 )
121}
122
123fn transfer_tx<C: Currency>(destination: Address, amount: Balance<C>) -> TransactionRequest {
124 let amount_u256 = U256::from_be_bytes(amount.amount().to_be_bytes());
125 let tx = TransactionRequest::default();
126 if WxHOPR::is::<C>() {
127 tx.with_input(
128 transferCall {
129 recipient: a2al(destination),
130 amount: amount_u256,
131 }
132 .abi_encode(),
133 )
134 } else if XDai::is::<C>() {
135 tx.with_value(amount_u256)
136 } else {
137 unimplemented!("other currencies are currently not supported")
138 }
139}
140
141fn register_safe_tx(safe_addr: Address) -> TransactionRequest {
142 TransactionRequest::default().with_input(
143 registerSafeByNodeCall {
144 safeAddr: a2al(safe_addr),
145 }
146 .abi_encode(),
147 )
148}
149
150#[derive(Debug, Clone, Copy)]
152pub struct BasicPayloadGenerator {
153 me: Address,
154 contract_addrs: ContractAddresses,
155}
156
157impl BasicPayloadGenerator {
158 pub fn new(me: Address, contract_addrs: ContractAddresses) -> Self {
159 Self { me, contract_addrs }
160 }
161}
162
163impl PayloadGenerator for BasicPayloadGenerator {
164 type TxRequest = TransactionRequest;
165
166 fn approve(&self, spender: Address, amount: HoprBalance) -> payload::Result<Self::TxRequest> {
167 let tx = approve_tx(a2al(spender), amount).with_to(self.contract_addrs.token);
168 Ok(tx)
169 }
170
171 fn transfer<C: Currency>(&self, destination: Address, amount: Balance<C>) -> payload::Result<Self::TxRequest> {
172 let to = if XDai::is::<C>() {
173 a2al(destination)
174 } else if WxHOPR::is::<C>() {
175 self.contract_addrs.token
176 } else {
177 return Err(InvalidArguments("invalid currency"));
178 };
179 let tx = transfer_tx(destination, amount).with_to(to);
180 Ok(tx)
181 }
182
183 fn announce(
184 &self,
185 announcement: AnnouncementData,
186 key_binding_fee: HoprBalance,
187 ) -> payload::Result<Self::TxRequest> {
188 let serialized_signature = announcement.key_binding().signature.as_ref();
191
192 let inner_payload = KeyBindAndAnnouncePayload {
193 callerNode: a2al(self.me),
194 ed25519_sig_0: B256::from_slice(&serialized_signature[0..32]),
195 ed25519_sig_1: B256::from_slice(&serialized_signature[32..64]),
196 ed25519_pub_key: B256::from_slice(announcement.key_binding().packet_key.as_ref()),
197 multiaddress: announcement
198 .multiaddress()
199 .as_ref()
200 .map(ToString::to_string)
201 .unwrap_or_default(), }
203 .abi_encode();
204
205 let call_data = sendCall {
206 recipient: self.contract_addrs.announcements,
207 amount: alloy::primitives::U256::from_be_slice(&key_binding_fee.amount().to_be_bytes()),
208 data: inner_payload[32..].to_vec().into(),
209 }
210 .abi_encode();
211
212 Ok(TransactionRequest::default()
213 .with_input(call_data)
214 .with_to(self.contract_addrs.token))
215 }
216
217 fn fund_channel(&self, dest: Address, amount: HoprBalance) -> payload::Result<Self::TxRequest> {
218 if dest.eq(&self.me) {
219 return Err(InvalidArguments("Cannot fund channel to self"));
220 }
221
222 let tx = TransactionRequest::default()
223 .with_input(
224 fundChannelCall {
225 account: a2al(dest),
226 amount: U96::from_be_slice(&amount.amount().to_be_bytes()[32 - 12..]),
227 }
228 .abi_encode(),
229 )
230 .with_to(self.contract_addrs.channels);
231 Ok(tx)
232 }
233
234 fn close_incoming_channel(&self, source: Address) -> payload::Result<Self::TxRequest> {
235 if source.eq(&self.me) {
236 return Err(InvalidArguments("Cannot close incoming channel from self"));
237 }
238
239 let tx = TransactionRequest::default()
240 .with_input(closeIncomingChannelCall { source: a2al(source) }.abi_encode())
241 .with_to(self.contract_addrs.channels);
242 Ok(tx)
243 }
244
245 fn initiate_outgoing_channel_closure(&self, destination: Address) -> payload::Result<Self::TxRequest> {
246 if destination.eq(&self.me) {
247 return Err(InvalidArguments("Cannot initiate closure of incoming channel to self"));
248 }
249
250 let tx = TransactionRequest::default()
251 .with_input(
252 initiateOutgoingChannelClosureCall {
253 destination: a2al(destination),
254 }
255 .abi_encode(),
256 )
257 .with_to(self.contract_addrs.channels);
258 Ok(tx)
259 }
260
261 fn finalize_outgoing_channel_closure(&self, destination: Address) -> payload::Result<Self::TxRequest> {
262 if destination.eq(&self.me) {
263 return Err(InvalidArguments("Cannot initiate closure of incoming channel to self"));
264 }
265
266 let tx = TransactionRequest::default()
267 .with_input(
268 finalizeOutgoingChannelClosureCall {
269 destination: a2al(destination),
270 }
271 .abi_encode(),
272 )
273 .with_to(self.contract_addrs.channels);
274 Ok(tx)
275 }
276
277 fn redeem_ticket(&self, acked_ticket: RedeemableTicket) -> payload::Result<Self::TxRequest> {
278 let redeemable = convert_acknowledged_ticket(&acked_ticket)?;
279
280 let params = convert_vrf_parameters(
281 &acked_ticket.vrf_params,
282 &self.me,
283 acked_ticket.ticket.verified_hash(),
284 &acked_ticket.channel_dst,
285 );
286
287 let tx = TransactionRequest::default()
288 .with_input(redeemTicketCall { redeemable, params }.abi_encode())
289 .with_to(self.contract_addrs.channels);
290 Ok(tx)
291 }
292
293 fn register_safe_by_node(&self, safe_addr: Address) -> payload::Result<Self::TxRequest> {
294 let tx = register_safe_tx(safe_addr).with_to(self.contract_addrs.node_safe_registry);
295 Ok(tx)
296 }
297
298 fn deregister_node_by_safe(&self) -> payload::Result<Self::TxRequest> {
299 Err(InvalidState("Can only deregister an address if Safe is activated"))
300 }
301
302 fn deploy_safe(
303 &self,
304 balance: HoprBalance,
305 admins: &[Address],
306 include_node: bool,
307 nonce: [u8; 64],
308 ) -> crate::payload::Result<Self::TxRequest> {
309 const DEFAULT_CAPABILITY_PERMISSIONS: &str = "010103030303030303030303";
310 let nonce = U256::from_be_slice(&nonce);
311 let admins = admins
312 .iter()
313 .map(|a| alloy::primitives::Address::new((*a).into()))
314 .collect::<Vec<_>>();
315 let default_target = alloy::primitives::U256::from_str(&format!(
316 "{:?}{DEFAULT_CAPABILITY_PERMISSIONS}",
317 self.contract_addrs.channels
318 ))
319 .map_err(|e| ChainTypesError::ParseError(e.into()))?;
320
321 let user_data = if include_node {
322 UserData {
323 functionIdentifier: b256!("0105b97dcdf19d454ebe36f91ed516c2b90ee79f4a46af96a0138c1f5403c1cc"),
324 nonce,
325 defaultTarget: default_target.into(),
326 admins,
327 }
328 .abi_encode()[32..]
329 .to_vec()
330 } else {
331 UserData {
332 functionIdentifier: b256!("dd24c144db91d1bc600aac99393baf8f8c664ba461188f057e37f2c37b962b45"),
333 nonce,
334 defaultTarget: default_target.into(),
335 admins,
336 }
337 .abi_encode()[32..]
338 .to_vec()
339 };
340
341 let tx_payload = sendCall {
342 recipient: self.contract_addrs.node_stake_factory,
343 amount: alloy::primitives::U256::from_be_slice(&balance.amount().to_be_bytes()),
344 data: user_data.into(),
345 }
346 .abi_encode();
347
348 let tx = TransactionRequest::default()
349 .with_to(self.contract_addrs.token)
350 .with_input(tx_payload);
351 Ok(tx)
352 }
353}
354
355#[derive(Debug, Clone, Copy)]
357pub struct SafePayloadGenerator {
358 me: Address,
359 contract_addrs: ContractAddresses,
360 module: Address,
361}
362
363impl SafePayloadGenerator {
364 pub fn new(chain_keypair: &ChainKeypair, contract_addrs: ContractAddresses, module: Address) -> Self {
365 Self {
366 me: chain_keypair.into(),
367 contract_addrs,
368 module,
369 }
370 }
371}
372
373impl PayloadGenerator for SafePayloadGenerator {
374 type TxRequest = TransactionRequest;
375
376 fn approve(&self, spender: Address, amount: HoprBalance) -> payload::Result<Self::TxRequest> {
377 let tx = approve_tx(a2al(spender), amount)
378 .with_to(self.contract_addrs.token)
379 .with_gas_limit(DEFAULT_TX_GAS);
380
381 Ok(tx)
382 }
383
384 fn transfer<C: Currency>(&self, destination: Address, amount: Balance<C>) -> payload::Result<Self::TxRequest> {
385 let to = if XDai::is::<C>() {
386 a2al(destination)
387 } else if WxHOPR::is::<C>() {
388 self.contract_addrs.token
389 } else {
390 return Err(InvalidArguments("invalid currency"));
391 };
392 let tx = transfer_tx(destination, amount)
393 .with_to(to)
394 .with_gas_limit(DEFAULT_TX_GAS);
395
396 Ok(tx)
397 }
398
399 fn announce(
400 &self,
401 announcement: AnnouncementData,
402 key_binding_fee: HoprBalance,
403 ) -> payload::Result<Self::TxRequest> {
404 let serialized_signature = announcement.key_binding().signature.as_ref();
407
408 let inner_payload = KeyBindAndAnnouncePayload {
409 callerNode: a2al(self.me),
410 ed25519_sig_0: B256::from_slice(&serialized_signature[0..32]),
411 ed25519_sig_1: B256::from_slice(&serialized_signature[32..64]),
412 ed25519_pub_key: B256::from_slice(announcement.key_binding().packet_key.as_ref()),
413 multiaddress: announcement
414 .multiaddress()
415 .as_ref()
416 .map(ToString::to_string)
417 .unwrap_or_default(),
418 }
419 .abi_encode();
420
421 let call_data = sendCall {
422 recipient: self.contract_addrs.announcements,
423 amount: alloy::primitives::U256::from_be_slice(&key_binding_fee.amount().to_be_bytes()),
424 data: inner_payload[32..].to_vec().into(),
425 }
426 .abi_encode();
427
428 Ok(TransactionRequest::default()
429 .with_input(
430 execTransactionFromModuleCall {
431 to: self.contract_addrs.token,
432 value: U256::ZERO,
433 data: call_data.into(),
434 operation: Operation::Call as u8,
435 }
436 .abi_encode(),
437 )
438 .with_to(a2al(self.module))
439 .with_gas_limit(DEFAULT_TX_GAS))
440 }
441
442 fn fund_channel(&self, dest: Address, amount: HoprBalance) -> payload::Result<Self::TxRequest> {
443 if dest.eq(&self.me) {
444 return Err(InvalidArguments("Cannot fund channel to self"));
445 }
446
447 if amount.amount() > hopr_primitive_types::prelude::U256::from(ChannelEntry::MAX_CHANNEL_BALANCE) {
448 return Err(InvalidArguments("Cannot fund channel with amount larger than 96 bits"));
449 }
450
451 let call_data = fundChannelSafeCall {
452 selfAddress: a2al(self.me),
453 account: a2al(dest),
454 amount: U96::from_be_slice(&amount.amount().to_be_bytes()[32 - 12..]),
455 }
456 .abi_encode();
457
458 let tx = TransactionRequest::default()
459 .with_input(channels_payload(self.contract_addrs.channels, call_data))
460 .with_to(a2al(self.module))
461 .with_gas_limit(DEFAULT_TX_GAS);
462
463 Ok(tx)
464 }
465
466 fn close_incoming_channel(&self, source: Address) -> payload::Result<Self::TxRequest> {
467 if source.eq(&self.me) {
468 return Err(InvalidArguments("Cannot close incoming channel from self"));
469 }
470
471 let call_data = closeIncomingChannelSafeCall {
472 selfAddress: a2al(self.me),
473 source: a2al(source),
474 }
475 .abi_encode();
476
477 let tx = TransactionRequest::default()
478 .with_input(channels_payload(self.contract_addrs.channels, call_data))
479 .with_to(a2al(self.module))
480 .with_gas_limit(DEFAULT_TX_GAS);
481
482 Ok(tx)
483 }
484
485 fn initiate_outgoing_channel_closure(&self, destination: Address) -> payload::Result<Self::TxRequest> {
486 if destination.eq(&self.me) {
487 return Err(InvalidArguments("Cannot initiate closure of incoming channel to self"));
488 }
489
490 let call_data = initiateOutgoingChannelClosureSafeCall {
491 selfAddress: a2al(self.me),
492 destination: a2al(destination),
493 }
494 .abi_encode();
495
496 let tx = TransactionRequest::default()
497 .with_input(channels_payload(self.contract_addrs.channels, call_data))
498 .with_to(a2al(self.module))
499 .with_gas_limit(DEFAULT_TX_GAS);
500
501 Ok(tx)
502 }
503
504 fn finalize_outgoing_channel_closure(&self, destination: Address) -> payload::Result<Self::TxRequest> {
505 if destination.eq(&self.me) {
506 return Err(InvalidArguments("Cannot initiate closure of incoming channel to self"));
507 }
508
509 let call_data = finalizeOutgoingChannelClosureSafeCall {
510 selfAddress: a2al(self.me),
511 destination: a2al(destination),
512 }
513 .abi_encode();
514
515 let tx = TransactionRequest::default()
516 .with_input(channels_payload(self.contract_addrs.channels, call_data))
517 .with_to(a2al(self.module))
518 .with_gas_limit(DEFAULT_TX_GAS);
519
520 Ok(tx)
521 }
522
523 fn redeem_ticket(&self, acked_ticket: RedeemableTicket) -> payload::Result<Self::TxRequest> {
524 let redeemable = convert_acknowledged_ticket(&acked_ticket)?;
525
526 let params = convert_vrf_parameters(
527 &acked_ticket.vrf_params,
528 &self.me,
529 acked_ticket.ticket.verified_hash(),
530 &acked_ticket.channel_dst,
531 );
532
533 let call_data = redeemTicketSafeCall {
534 selfAddress: a2al(self.me),
535 redeemable,
536 params,
537 }
538 .abi_encode();
539
540 let tx = TransactionRequest::default()
541 .with_input(channels_payload(self.contract_addrs.channels, call_data))
542 .with_to(a2al(self.module))
543 .with_gas_limit(DEFAULT_TX_GAS);
544
545 Ok(tx)
546 }
547
548 fn register_safe_by_node(&self, safe_addr: Address) -> payload::Result<Self::TxRequest> {
549 let tx = register_safe_tx(safe_addr)
550 .with_to(self.contract_addrs.node_safe_registry)
551 .with_gas_limit(DEFAULT_TX_GAS);
552
553 Ok(tx)
554 }
555
556 fn deregister_node_by_safe(&self) -> payload::Result<Self::TxRequest> {
557 let tx = TransactionRequest::default()
558 .with_input(
559 deregisterNodeBySafeCall {
560 nodeAddr: a2al(self.me),
561 }
562 .abi_encode(),
563 )
564 .with_to(a2al(self.module))
565 .with_gas_limit(DEFAULT_TX_GAS);
566
567 Ok(tx)
568 }
569
570 fn deploy_safe(
571 &self,
572 _: HoprBalance,
573 _: &[Address],
574 _: bool,
575 _: [u8; 64],
576 ) -> crate::payload::Result<Self::TxRequest> {
577 Err(InvalidState("cannot deploy Safe from SafePayloadGenerator"))
578 }
579}
580
581fn convert_vrf_parameters(
586 off_chain: &VrfParameters,
587 signer: &Address,
588 ticket_hash: &Hash,
589 domain_separator: &Hash,
590) -> VRFParameters {
591 let v = off_chain.get_v_encoded_point();
593 let s_b = off_chain
594 .get_s_b_witness(signer, &ticket_hash.into(), domain_separator.as_ref())
595 .expect("ticket hash exceeded hash2field boundaries or encoding to unsupported curve");
600
601 let h_v = off_chain.get_h_v_witness();
602
603 VRFParameters {
604 vx: U256::from_be_slice(&v.as_bytes()[1..33]),
605 vy: U256::from_be_slice(&v.as_bytes()[33..65]),
606 s: U256::from_be_slice(&off_chain.s.to_bytes()),
607 h: U256::from_be_slice(&off_chain.h.to_bytes()),
608 sBx: U256::from_be_slice(&s_b.as_bytes()[1..33]),
609 sBy: U256::from_be_slice(&s_b.as_bytes()[33..65]),
610 hVx: U256::from_be_slice(&h_v.as_bytes()[1..33]),
611 hVy: U256::from_be_slice(&h_v.as_bytes()[33..65]),
612 }
613}
614
615fn convert_acknowledged_ticket(off_chain: &RedeemableTicket) -> payload::Result<OnChainRedeemableTicket> {
620 if let Some(ref signature) = off_chain.verified_ticket().signature {
621 let serialized_signature = signature.as_ref();
622
623 Ok(OnChainRedeemableTicket {
624 data: TicketData {
625 channelId: B256::from_slice(off_chain.ticket.channel_id().as_ref()),
626 amount: U96::from_be_slice(&off_chain.verified_ticket().amount.amount().to_be_bytes()[32 - 12..]), ticketIndex: U48::from_be_slice(&off_chain.verified_ticket().index.to_be_bytes()[8 - 6..]),
628 epoch: U24::from_be_slice(&off_chain.verified_ticket().channel_epoch.to_be_bytes()[4 - 3..]),
629 winProb: U56::from_be_slice(&off_chain.verified_ticket().encoded_win_prob),
630 },
631 signature: CompactSignature {
632 r: B256::from_slice(&serialized_signature[0..32]),
633 vs: B256::from_slice(&serialized_signature[32..64]),
634 },
635 porSecret: U256::from_be_slice(off_chain.response.as_ref()),
636 })
637 } else {
638 Err(InvalidArguments("Acknowledged ticket must be signed"))
639 }
640}
641
642#[cfg(test)]
643pub(crate) mod tests {
644 use std::str::FromStr;
645
646 use hex_literal::hex;
647 use hopr_crypto_types::prelude::*;
648 use hopr_internal_types::prelude::*;
649 use hopr_primitive_types::prelude::*;
650 use multiaddr::Multiaddr;
651
652 use crate::payload::{
653 BasicPayloadGenerator, PayloadGenerator, SafePayloadGenerator, SignableTransaction, tests::CONTRACT_ADDRS,
654 };
655
656 const PRIVATE_KEY_1: [u8; 32] = hex!("c14b8faa0a9b8a5fa4453664996f23a7e7de606d42297d723fc4a794f375e260");
657 const PRIVATE_KEY_2: [u8; 32] = hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775");
658
659 lazy_static::lazy_static! {
660 static ref REDEEMABLE_TICKET: RedeemableTicket = postcard::from_bytes(&hex!(
661 "bea83ba0fcee21da44a30c893f466e6bf0c29bbb0530783365387bffffffffffffff010000000000000000000000000000000000000000014038536c412ff92c3b070d98724a2ac167b7a914aa2151cf71eea3d192b0df195d0184aa92c73bccb27aded5f27fcd1cdcf65889f78cf2e62d2f630f659aa2fba220cba79e6dc2ea1205cb76833c9223cd912f056f3406d73d0d689602afe5e88abc668430def9eacd2b5064acf85d73fb0b351a1c8c20d7f3fa28f0caa757e81226e1ee86a9efdbe7991442286183797296ebaa4d292a2005a089ed04b7dbb28ad1c9074f13d10115b0002ca88f4d68ce14549099773c192103d14016cbfa555574e8a5a8fbcb52677dfb7e9267e99c05ebe29603e41b33327705ddecfc569b0125d1ae9a3d3cb637a3c8c9eaafe90e6a1877292227065fbdcc897e95962ce1604fb644782e9029a046650ed84c4f1043b753959d7819f53cec200000000000000000000000000000000000000000000000000000000000000000"
662 )).unwrap();
663 }
664
665 #[tokio::test]
688 async fn test_announce() -> anyhow::Result<()> {
689 let test_multiaddr = Multiaddr::from_str("/ip4/1.2.3.4/tcp/56")?;
690
691 let chain_key_0 = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
692
693 let generator = BasicPayloadGenerator::new((&chain_key_0).into(), *CONTRACT_ADDRS);
694
695 let kb = KeyBinding::new((&chain_key_0).into(), &OffchainKeypair::from_secret(&PRIVATE_KEY_1)?);
696
697 let ad = AnnouncementData::new(kb, Some(test_multiaddr))?;
698
699 let signed_tx = generator
700 .announce(ad, 100_u32.into())?
701 .sign_and_encode_to_eip2718(2, 1, None, &chain_key_0)
702 .await?;
703 insta::assert_snapshot!("announce_basic", hex::encode(signed_tx));
704
705 let test_multiaddr_reannounce = Multiaddr::from_str("/ip4/5.6.7.8/tcp/99")?;
706 let ad_reannounce = AnnouncementData::new(kb, Some(test_multiaddr_reannounce))?;
707
708 let signed_tx = generator
709 .announce(ad_reannounce, 0_u32.into())?
710 .sign_and_encode_to_eip2718(1, 1, None, &chain_key_0)
711 .await?;
712 insta::assert_snapshot!("announce_safe", hex::encode(signed_tx.clone()));
713
714 Ok(())
715 }
716
717 #[tokio::test]
718 async fn redeem_ticket_basic() -> anyhow::Result<()> {
719 let chain_key_bob = ChainKeypair::from_secret(&PRIVATE_KEY_2)?;
720
721 let acked_ticket = REDEEMABLE_TICKET.clone();
722
723 let generator = BasicPayloadGenerator::new((&chain_key_bob).into(), *CONTRACT_ADDRS);
725 let redeem_ticket_tx = generator.redeem_ticket(acked_ticket.clone())?;
726 let signed_tx = redeem_ticket_tx
727 .sign_and_encode_to_eip2718(1, 1, None, &chain_key_bob)
728 .await?;
729
730 insta::assert_snapshot!("redeem_ticket_basic", hex::encode(signed_tx));
731
732 Ok(())
733 }
734
735 #[tokio::test]
736 async fn redeem_ticket_safe() -> anyhow::Result<()> {
737 let chain_key_bob = ChainKeypair::from_secret(&PRIVATE_KEY_2)?;
738
739 let acked_ticket = REDEEMABLE_TICKET.clone();
740
741 let generator =
743 SafePayloadGenerator::new((&chain_key_bob).into(), *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
744 let redeem_ticket_tx = generator.redeem_ticket(acked_ticket)?;
745 let signed_tx = redeem_ticket_tx
746 .sign_and_encode_to_eip2718(2, 1, None, &chain_key_bob)
747 .await?;
748
749 insta::assert_snapshot!("redeem_ticket_safe", hex::encode(signed_tx));
750
751 Ok(())
752 }
753
754 #[tokio::test]
755 async fn withdraw_token() -> anyhow::Result<()> {
756 let chain_key_alice = ChainKeypair::from_secret(&PRIVATE_KEY_1)?;
757 let chain_key_bob = ChainKeypair::from_secret(&PRIVATE_KEY_2)?;
758
759 let generator = BasicPayloadGenerator::new((&chain_key_alice).into(), *CONTRACT_ADDRS);
760 let tx = generator.transfer((&chain_key_bob).into(), HoprBalance::from(100))?;
761
762 let signed_tx = tx.sign_and_encode_to_eip2718(1, 1, None, &chain_key_bob).await?;
763
764 insta::assert_snapshot!("withdraw_basic", hex::encode(signed_tx));
765
766 let generator =
767 SafePayloadGenerator::new((&chain_key_alice).into(), *CONTRACT_ADDRS, [1u8; Address::SIZE].into());
768 let tx = generator.transfer((&chain_key_bob).into(), HoprBalance::from(100))?;
769
770 let signed_tx = tx.sign_and_encode_to_eip2718(2, 1, None, &chain_key_bob).await?;
771
772 insta::assert_snapshot!("withdraw_safe", hex::encode(signed_tx));
773
774 Ok(())
775 }
776}