Skip to main content

hypercall/
rsm_credit_directive_producer.rs

1use alloy::sol_types::SolValue;
2use hypercall_types::directives::{
3    CreditToken, SendAsset, WithdrawToken, HL_ACTION_ID_SEND_ASSET, HL_ACTION_VERSION,
4    SYSTEM_ACTION_ID_CREDIT_TOKEN, SYSTEM_ACTION_ID_WITHDRAW_TOKEN, SYSTEM_ACTION_VERSION,
5};
6use hypercall_types::WalletAddress;
7
8use hypercall_signer::encode_action_bytes;
9
10#[cfg(test)]
11use alloy::primitives::U256;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct SystemCreditTokenDirective {
15    pub src_dex: u32,
16    pub dst_dex: u32,
17    pub token: u64,
18    pub amount_wei: u64,
19}
20
21pub use hypercall_runtime_api::{encode_credit_option_action, SystemCreditOptionDirective};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct RsmSendAssetDirective {
25    pub destination: WalletAddress,
26    pub sub_account: WalletAddress,
27    pub src_dex: u32,
28    pub dst_dex: u32,
29    pub token: u64,
30    pub amount_wei: u64,
31}
32
33pub fn encode_credit_token_action(action: SystemCreditTokenDirective) -> anyhow::Result<Vec<u8>> {
34    if action.amount_wei == 0 {
35        anyhow::bail!("refusing to submit SystemCreditToken with amount_wei=0");
36    }
37
38    encode_action_bytes(
39        SYSTEM_ACTION_VERSION,
40        SYSTEM_ACTION_ID_CREDIT_TOKEN,
41        &CreditToken {
42            srcDex: action.src_dex,
43            dstDex: action.dst_dex,
44            token: action.token,
45            amountWei: action.amount_wei,
46        }
47        .abi_encode(),
48    )
49    .map_err(|error| anyhow::anyhow!("{}", error))
50}
51
52pub fn encode_rsm_send_asset_action(action: RsmSendAssetDirective) -> anyhow::Result<Vec<u8>> {
53    if action.destination == WalletAddress::default() {
54        anyhow::bail!("refusing to submit HLSendAsset with zero destination");
55    }
56    if action.amount_wei == 0 {
57        anyhow::bail!("refusing to submit HLSendAsset with amount_wei=0");
58    }
59
60    encode_action_bytes(
61        HL_ACTION_VERSION,
62        HL_ACTION_ID_SEND_ASSET,
63        &SendAsset {
64            destination: action.destination.inner(),
65            subAccount: action.sub_account.inner(),
66            srcDex: action.src_dex,
67            dstDex: action.dst_dex,
68            token: action.token,
69            amountWei: action.amount_wei,
70        }
71        .abi_encode(),
72    )
73    .map_err(|error| anyhow::anyhow!("{}", error))
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct SystemWithdrawTokenDirective {
78    pub destination: WalletAddress,
79    pub sub_account: WalletAddress,
80    pub src_dex: u32,
81    pub dst_dex: u32,
82    pub token: u64,
83    pub amount_wei: u64,
84}
85
86pub fn encode_system_withdraw_token_action(
87    action: SystemWithdrawTokenDirective,
88) -> anyhow::Result<Vec<u8>> {
89    if action.destination == WalletAddress::default() {
90        anyhow::bail!("refusing to submit SystemWithdrawToken with zero destination");
91    }
92    if action.amount_wei == 0 {
93        anyhow::bail!("refusing to submit SystemWithdrawToken with amount_wei=0");
94    }
95
96    encode_action_bytes(
97        SYSTEM_ACTION_VERSION,
98        SYSTEM_ACTION_ID_WITHDRAW_TOKEN,
99        &WithdrawToken {
100            destination: action.destination.inner(),
101            subAccount: action.sub_account.inner(),
102            srcDex: action.src_dex,
103            dstDex: action.dst_dex,
104            token: action.token,
105            amountWei: action.amount_wei,
106        }
107        .abi_encode(),
108    )
109    .map_err(|error| anyhow::anyhow!("{}", error))
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn encode_credit_token_action_rejects_zero_amount() {
118        let err = encode_credit_token_action(SystemCreditTokenDirective {
119            src_dex: 0,
120            dst_dex: 1,
121            token: 1,
122            amount_wei: 0,
123        })
124        .expect_err("zero token credit should be rejected");
125
126        assert!(err.to_string().contains("amount_wei=0"));
127    }
128
129    #[test]
130    fn encode_credit_option_action_rejects_empty_fields() {
131        let err = encode_credit_option_action(SystemCreditOptionDirective {
132            underlying: [0u8; 32],
133            expiry: U256::from(1),
134            strike: U256::from(1),
135            is_call: true,
136            amount_wei: U256::from(1),
137        })
138        .expect_err("zero underlying should be rejected");
139
140        assert!(err.to_string().contains("zero underlying"));
141    }
142
143    #[test]
144    fn encode_rsm_send_asset_rejects_zero_destination() {
145        let err = encode_rsm_send_asset_action(RsmSendAssetDirective {
146            destination: WalletAddress::default(),
147            sub_account: WalletAddress::default(),
148            src_dex: 0,
149            dst_dex: 0,
150            token: 0,
151            amount_wei: 1_000_000,
152        })
153        .expect_err("zero destination should be rejected");
154
155        assert!(err.to_string().contains("zero destination"));
156    }
157
158    #[test]
159    fn encode_rsm_send_asset_rejects_zero_amount() {
160        let destination = WalletAddress::from(alloy::primitives::address!(
161            "1111111111111111111111111111111111111111"
162        ));
163        let err = encode_rsm_send_asset_action(RsmSendAssetDirective {
164            destination,
165            sub_account: WalletAddress::default(),
166            src_dex: 0,
167            dst_dex: 0,
168            token: 0,
169            amount_wei: 0,
170        })
171        .expect_err("zero amount should be rejected");
172
173        assert!(err.to_string().contains("amount_wei=0"));
174    }
175}