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}