Skip to main content

hypercall_transaction_submitter/
exchange_encoder.rs

1use 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
10// Exchange contract ABI
11sol! {
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
24/// Encode executeUserDirective call into a submitter-owned contract call.
25pub 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
45/// Encode executeRsmDirective call into a submitter-owned contract call.
46pub 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}