hypercall_transaction_submitter/
exchange_encoder.rs1use alloy::{
2 primitives::{Address, Bytes, U256},
3 sol,
4 sol_types::SolCall,
5};
6use eyre::Result as EyreResult;
7use hypercall_transaction_submitter_core::ContractCall;
8use std::str::FromStr;
9
10sol! {
12 #[sol(rpc)]
13 contract Exchange {
14 function executeRsmDirective(bytes memory directive, bytes memory signature) external;
15 function executeUserDirective(bytes memory directive, bytes memory signature) external;
16 }
17}
18
19pub fn parse_exchange_address(exchange_contract_address: &str) -> EyreResult<Address> {
20 Address::from_str(exchange_contract_address)
21 .map_err(|e| eyre::eyre!("invalid EXCHANGE_CONTRACT_ADDRESS: {}", e))
22}
23
24pub fn encode_execute_user_directive(
26 exchange_address: Address,
27 directive: Bytes,
28 signature: Bytes,
29 external_id: Option<String>,
30) -> EyreResult<ContractCall> {
31 let call = Exchange::executeUserDirectiveCall {
32 directive,
33 signature,
34 };
35 let encoded_data = call.abi_encode();
36
37 Ok(ContractCall {
38 to: exchange_address,
39 data: Bytes::from(encoded_data),
40 value: U256::ZERO,
41 external_id,
42 })
43}
44
45pub fn encode_execute_rsm_directive(
47 exchange_address: Address,
48 directive: Bytes,
49 signature: Bytes,
50 external_id: Option<String>,
51) -> EyreResult<ContractCall> {
52 let call = Exchange::executeRsmDirectiveCall {
53 directive,
54 signature,
55 };
56 let encoded_data = call.abi_encode();
57
58 Ok(ContractCall {
59 to: exchange_address,
60 data: Bytes::from(encoded_data),
61 value: U256::ZERO,
62 external_id,
63 })
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use alloy::primitives::keccak256;
70
71 fn test_exchange_address() -> Address {
72 parse_exchange_address("0x1d70Ff185F6C25E4d76163F16563D63d5b590Cbc")
73 .expect("test exchange address should parse")
74 }
75
76 #[test]
77 fn transaction_submitter_rsm_encoder_targets_exchange_address() {
78 let request = encode_execute_rsm_directive(
79 test_exchange_address(),
80 Bytes::from(vec![1u8, 2, 3]),
81 Bytes::from(vec![4u8; 65]),
82 Some("req-1".to_string()),
83 )
84 .expect("encoder should build request");
85
86 assert_eq!(request.to, test_exchange_address());
87 assert!(request.value.is_zero(), "call value must be zero");
88 }
89
90 #[test]
91 fn transaction_submitter_rsm_encoder_selector_matches_execute_rsm_directive() {
92 let request = encode_execute_rsm_directive(
93 test_exchange_address(),
94 Bytes::from(vec![0xAA; 8]),
95 Bytes::from(vec![0xBB; 65]),
96 None,
97 )
98 .expect("encoder should build request");
99
100 let calldata = request.data;
101 assert!(
102 calldata.len() >= 4,
103 "calldata must include at least the selector"
104 );
105
106 let expected_hash = keccak256("executeRsmDirective(bytes,bytes)".as_bytes());
107 assert_eq!(
108 &calldata[..4],
109 &expected_hash[..4],
110 "selector must match executeRsmDirective(bytes,bytes)"
111 );
112 }
113}