interchain_token_transfer_gmp/
lib.rs1use std::borrow::Cow;
2
3pub use alloy_primitives;
4use alloy_primitives::U256;
5use alloy_sol_types::{sol, SolValue};
6
7#[derive(Clone, Debug, PartialEq)]
14pub enum GMPPayload {
15 InterchainTransfer(InterchainTransfer),
16 DeployInterchainToken(DeployInterchainToken),
17 SendToHub(SendToHub),
18 ReceiveFromHub(ReceiveFromHub),
19 LinkToken(LinkToken),
20 RegisterTokenMetadata(RegisterTokenMetadata),
21}
22
23sol! {
24 #[derive(Debug, PartialEq)]
29 #[repr(C)]
30 struct InterchainTransfer {
31 uint256 selector;
33 bytes32 token_id;
35 bytes source_address;
38 bytes destination_address;
40 uint256 amount;
42 bytes data;
45 }
46
47 #[derive(Debug, PartialEq)]
51 #[repr(C)]
52 struct DeployInterchainToken {
53 uint256 selector;
54 bytes32 token_id;
56 string name;
58 string symbol;
60 uint8 decimals;
62 bytes minter;
65 }
66
67 #[derive(Debug, PartialEq)]
71 #[repr(C)]
72 struct SendToHub {
73 uint256 selector;
75
76 string destination_chain;
78
79 bytes payload;
81 }
82
83 #[derive(Debug, PartialEq)]
87 #[repr(C)]
88 struct ReceiveFromHub {
89 uint256 selector;
91
92 string source_chain;
94
95 bytes payload;
97 }
98
99 #[derive(Debug, PartialEq)]
100 #[repr(C)]
101 struct LinkToken {
102 uint256 selector;
104 bytes32 token_id;
106 uint256 token_manager_type;
108 bytes source_token_address;
110 bytes destination_token_address;
112 bytes link_params;
115 }
116
117 #[derive(Debug, PartialEq)]
118 #[repr(C)]
119 struct RegisterTokenMetadata {
120 uint256 selector;
122 bytes token_address;
124 uint8 decimals;
126 }
127
128
129}
130
131const fn u8_to_u256(x: u8) -> U256 {
135 U256::from_limbs([x as u64, 0, 0, 0])
136}
137
138impl InterchainTransfer {
139 pub const MESSAGE_TYPE_ID: u8 = 0;
140 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
141}
142
143impl DeployInterchainToken {
144 pub const MESSAGE_TYPE_ID: u8 = 1;
145 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
146}
147
148impl SendToHub {
152 pub const MESSAGE_TYPE_ID: u8 = 3;
153 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
154}
155
156impl ReceiveFromHub {
157 pub const MESSAGE_TYPE_ID: u8 = 4;
158 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
159}
160
161impl LinkToken {
162 pub const MESSAGE_TYPE_ID: u8 = 5;
163 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
164}
165
166impl RegisterTokenMetadata {
167 pub const MESSAGE_TYPE_ID: u8 = 6;
168 pub const MESSAGE_TYPE_ID_UINT: U256 = u8_to_u256(Self::MESSAGE_TYPE_ID);
169}
170
171impl GMPPayload {
172 pub fn decode(bytes: &[u8]) -> Result<Self, alloy_sol_types::Error> {
173 if bytes.len() < 32 {
174 return Err(alloy_sol_types::Error::custom(
175 "Insufficient payload length",
176 ));
177 }
178
179 let variant = alloy_primitives::U256::abi_decode(&bytes[0..32], true)?;
180
181 let lower = variant & alloy_primitives::U256::from(0xffu8);
183 if variant != lower {
184 return Err(alloy_sol_types::Error::custom(
185 "Invalid payload (nonzero upper bits)",
186 ));
187 }
188
189 match variant.byte(0) {
190 InterchainTransfer::MESSAGE_TYPE_ID => Ok(GMPPayload::InterchainTransfer(
191 InterchainTransfer::abi_decode_params(bytes, true)?,
192 )),
193 DeployInterchainToken::MESSAGE_TYPE_ID => Ok(GMPPayload::DeployInterchainToken(
194 DeployInterchainToken::abi_decode_params(bytes, true)?,
195 )),
196 SendToHub::MESSAGE_TYPE_ID => Ok(GMPPayload::SendToHub(SendToHub::abi_decode_params(
197 bytes, true,
198 )?)),
199 ReceiveFromHub::MESSAGE_TYPE_ID => Ok(GMPPayload::ReceiveFromHub(
200 ReceiveFromHub::abi_decode_params(bytes, true)?,
201 )),
202 RegisterTokenMetadata::MESSAGE_TYPE_ID => Ok(GMPPayload::RegisterTokenMetadata(
203 RegisterTokenMetadata::abi_decode_params(bytes, true)?,
204 )),
205 LinkToken::MESSAGE_TYPE_ID => Ok(GMPPayload::LinkToken(LinkToken::abi_decode_params(
206 bytes, true,
207 )?)),
208 _ => Err(alloy_sol_types::Error::custom(
209 "Invalid selector for InterchainTokenService message",
210 )),
211 }
212 }
213
214 pub fn encode(&self) -> Vec<u8> {
215 match self {
216 GMPPayload::InterchainTransfer(data) => data.abi_encode_params(),
217 GMPPayload::DeployInterchainToken(data) => data.abi_encode_params(),
218 GMPPayload::SendToHub(data) => data.abi_encode_params(),
219 GMPPayload::ReceiveFromHub(data) => data.abi_encode_params(),
220 GMPPayload::LinkToken(data) => data.abi_encode_params(),
221 GMPPayload::RegisterTokenMetadata(data) => data.abi_encode_params(),
222 }
223 }
224
225 pub fn token_id(&self) -> Result<[u8; 32], alloy_sol_types::Error> {
226 match self {
227 GMPPayload::InterchainTransfer(data) => Ok(*data.token_id),
228 GMPPayload::DeployInterchainToken(data) => Ok(*data.token_id),
229 GMPPayload::SendToHub(inner) => GMPPayload::decode(&inner.payload)?.token_id(),
230 GMPPayload::ReceiveFromHub(inner) => GMPPayload::decode(&inner.payload)?.token_id(),
231 GMPPayload::LinkToken(data) => Ok(*data.token_id),
232 GMPPayload::RegisterTokenMetadata(_) => Err(alloy_sol_types::Error::Other(
233 Cow::Borrowed("RegisterTokenMetadata does not have a token_id"),
234 )),
235 }
236 }
237}
238
239impl From<InterchainTransfer> for GMPPayload {
240 fn from(data: InterchainTransfer) -> Self {
241 GMPPayload::InterchainTransfer(data)
242 }
243}
244
245impl From<DeployInterchainToken> for GMPPayload {
246 fn from(data: DeployInterchainToken) -> Self {
247 GMPPayload::DeployInterchainToken(data)
248 }
249}
250
251#[cfg(test)]
252mod tests {
253
254 use alloy_primitives::U256;
255
256 use super::*;
257
258 #[test]
259 fn u256_into_u8() {
260 let u256 = U256::from(42);
261 let byte = u256.byte(0);
262 assert_eq!(byte, 42);
263 }
264
265 const INTERCHAIN_TRANSFER: &str = "0000000000000000000000000000000000000000000000000000000000000000cccdb55f29bb017269049e59732c01ac41239e7b61e8a83be5c0ae1143ed806400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
274
275 #[test]
276 fn interchain_transfer_decode() {
277 let gmp = GMPPayload::decode(&hex::decode(INTERCHAIN_TRANSFER).unwrap()).unwrap();
279
280 let GMPPayload::InterchainTransfer(data) = gmp else {
282 panic!("wrong variant");
283 };
284
285 assert_eq!(
287 hex::encode(data.token_id.abi_encode()),
288 "cccdb55f29bb017269049e59732c01ac41239e7b61e8a83be5c0ae1143ed8064"
289 );
290 assert_eq!(
291 data.source_address,
292 hex::decode("f39fd6e51aad88f6f4ce6ab8827279cfffb92266").unwrap()
293 );
294 assert_eq!(
295 data.destination_address,
296 hex::decode("f39fd6e51aad88f6f4ce6ab8827279cfffb92266").unwrap()
297 );
298 assert_eq!(data.amount, U256::from(1234));
299 assert_eq!(data.data, Vec::<u8>::new());
300 }
301
302 #[test]
303 fn interchain_transfer_encode() {
304 assert_eq!(
305 hex::encode(
306 GMPPayload::decode(&hex::decode(INTERCHAIN_TRANSFER).unwrap())
307 .unwrap()
308 .encode()
309 ),
310 INTERCHAIN_TRANSFER,
311 "encode-decode should be idempotent"
312 );
313 }
314
315 const DEPLOY_INTERCHAIN_TOKEN: &str = "0000000000000000000000000000000000000000000000000000000000000001d8a4ae903349d12f4f96391cb47ea769a5535e57963562a5ae0ef932b18137e200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000a546f6b656e204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002544e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000";
325
326 #[test]
327 fn deploy_token_interchain_token_decode() {
328 let gmp = GMPPayload::decode(&hex::decode(DEPLOY_INTERCHAIN_TOKEN).unwrap()).unwrap();
330
331 let GMPPayload::DeployInterchainToken(data) = gmp else {
333 panic!("wrong variant");
334 };
335
336 assert_eq!(
338 hex::encode(data.token_id.abi_encode()),
339 "d8a4ae903349d12f4f96391cb47ea769a5535e57963562a5ae0ef932b18137e2"
340 );
341 assert_eq!(data.name, "Token Name".to_string());
342 assert_eq!(data.symbol, "TN".to_string(),);
343 assert_eq!(data.decimals, 13,);
344 assert_eq!(
345 data.minter,
346 hex::decode("f39fd6e51aad88f6f4ce6ab8827279cfffb92266").unwrap()
347 );
348 }
349
350 #[test]
351 fn deploy_token_interchain_token_encode() {
352 assert_eq!(
353 hex::encode(
354 GMPPayload::decode(&hex::decode(DEPLOY_INTERCHAIN_TOKEN).unwrap())
355 .unwrap()
356 .encode()
357 ),
358 DEPLOY_INTERCHAIN_TOKEN,
359 "encode-decode should be idempotent"
360 );
361 }
362}