1#![cfg_attr(not(feature = "std"), no_std)]
22#![warn(missing_docs)]
23#![doc(html_logo_url = "https://gear-tech.io/logo.png")]
24#![doc(html_favicon_url = "https://gear-tech.io/favicon.ico")]
25#![cfg_attr(docsrs, feature(doc_cfg))]
26
27extern crate alloc;
28
29use bytemuck::{Pod, Zeroable};
30pub use gear_ss58::Ss58Address;
31pub use nonzero_u256::NonZeroU256;
32pub use primitive_types::{H160, H256, U256};
33
34pub mod utils;
35
36mod macros;
37mod nonzero_u256;
38#[cfg(feature = "ethexe")]
39mod sol_types;
40
41use core::{
42 fmt,
43 str::{self, FromStr},
44};
45use derive_more::{AsMut, AsRef, From, Into};
46use gear_ss58::RawSs58Address;
47#[cfg(feature = "codec")]
48use scale_decode::DecodeAsType;
49#[cfg(feature = "codec")]
50use scale_encode::EncodeAsType;
51#[cfg(feature = "codec")]
52use scale_info::{
53 TypeInfo,
54 scale::{self, Decode, Encode, MaxEncodedLen},
55};
56#[cfg(all(feature = "serde", not(feature = "ethexe")))]
57use serde::de;
58#[cfg(feature = "serde")]
59use serde::{Deserialize, Deserializer, Serialize, Serializer};
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
63pub enum ConversionError {
64 #[error("Slice should be 32 length")]
66 InvalidSliceLength,
67 #[error("Invalid hex string")]
69 InvalidHexString,
70 #[error("Invalid SS58 address")]
72 InvalidSs58Address,
73 #[error("SS58 encoding failed")]
75 Ss58Encode,
76}
77
78#[repr(transparent)]
85#[derive(Clone, Copy, Debug, PartialEq, Eq, From, Into, Zeroable, Pod)]
86#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
87pub struct MessageHandle(u32);
88
89#[repr(transparent)]
99#[derive(
100 Clone,
101 Copy,
102 Default,
103 Hash,
104 Ord,
105 PartialEq,
106 PartialOrd,
107 Eq,
108 From,
109 Into,
110 AsRef,
111 AsMut,
112 Zeroable,
113 Pod,
114)]
115#[as_ref(forward)]
116#[as_mut(forward)]
117#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
118pub struct ActorId([u8; 32]);
119
120macros::impl_primitive!(new zero into_bytes from_h256 into_h256 try_from_slice debug, ActorId);
121
122impl ActorId {
123 pub fn to_ss58check(&self) -> Result<Ss58Address, ConversionError> {
125 RawSs58Address::from(self.0)
126 .to_ss58check()
127 .map_err(|_| ConversionError::Ss58Encode)
128 }
129
130 pub fn to_ss58check_with_version(&self, version: u16) -> Result<Ss58Address, ConversionError> {
132 RawSs58Address::from(self.0)
133 .to_ss58check_with_prefix(version)
134 .map_err(|_| ConversionError::Ss58Encode)
135 }
136
137 pub fn to_address_lossy(&self) -> H160 {
139 let mut h160 = H160::zero();
140 h160.0.copy_from_slice(&self.into_bytes()[12..]);
141 h160
142 }
143}
144
145impl From<u64> for ActorId {
146 fn from(value: u64) -> Self {
147 let mut id = Self::zero();
148 id.0[12..20].copy_from_slice(&value.to_le_bytes()[..]);
149 id
150 }
151}
152
153impl From<H160> for ActorId {
154 fn from(h160: H160) -> Self {
155 let mut actor_id = Self::zero();
156 actor_id.0[12..].copy_from_slice(h160.as_ref());
157 actor_id
158 }
159}
160
161impl TryInto<H160> for ActorId {
162 type Error = &'static str;
163
164 fn try_into(self) -> Result<H160, Self::Error> {
165 if !self.0[..12].iter().all(|i| i.eq(&0)) {
166 Err("ActorId has non-zero prefix")
167 } else {
168 let mut h160 = H160::zero();
169 h160.0.copy_from_slice(&self.into_bytes()[12..]);
170 Ok(h160)
171 }
172 }
173}
174
175impl fmt::Display for ActorId {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 let byte_array = utils::ByteSliceFormatter::Array(&self.0);
179
180 let is_alternate = f.alternate();
181 if is_alternate {
182 f.write_str(concat!(stringify!(ActorId), "("))?;
183 }
184
185 let sign_plus = f.sign_plus();
186 let width = f.width();
187
188 if sign_plus && width.is_some() {
189 return Err(fmt::Error);
190 }
191
192 let version = if sign_plus {
193 Some(gear_ss58::VARA_SS58_PREFIX)
194 } else if let Some(version) = width {
195 Some(version.try_into().map_err(|_| fmt::Error)?)
196 } else {
197 None
198 };
199
200 if let Some(version) = version {
201 let address = self
202 .to_ss58check_with_version(version)
203 .map_err(|_| fmt::Error)?;
204 let address_str = address.as_str();
205
206 let len = address.as_str().len();
207 let median = len.div_ceil(2);
208
209 let mut e1 = median;
210 let mut s2 = median;
211
212 if let Some(precision) = f.precision()
213 && precision < median
214 {
215 e1 = precision;
216 s2 = len - precision;
217 }
218
219 let p1 = &address_str[..e1];
220 let p2 = &address_str[s2..];
221 let sep = if e1.ne(&s2) { ".." } else { Default::default() };
222
223 write!(f, "{p1}{sep}{p2}")?;
224 } else {
225 byte_array.fmt(f)?;
226 }
227
228 if is_alternate {
229 f.write_str(")")?;
230 }
231
232 Ok(())
233 }
234}
235
236impl FromStr for ActorId {
237 type Err = ConversionError;
238
239 fn from_str(s: &str) -> Result<Self, Self::Err> {
240 let actod_id = if let Some(s) = s.strip_prefix("0x") {
241 if s.len() != 64 {
242 return Err(ConversionError::InvalidHexString);
243 }
244 let mut actor_id = Self::zero();
245 hex::decode_to_slice(s, &mut actor_id.0)
246 .map_err(|_| ConversionError::InvalidHexString)?;
247 actor_id
248 } else {
249 let raw_address = RawSs58Address::from_ss58check(s)
250 .map_err(|_| ConversionError::InvalidSs58Address)?
251 .into();
252 Self::new(raw_address)
253 };
254
255 Ok(actod_id)
256 }
257}
258
259#[cfg(all(feature = "serde", not(feature = "ethexe")))]
260impl Serialize for ActorId {
261 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
262 where
263 S: Serializer,
264 {
265 let address = self
266 .to_ss58check_with_version(gear_ss58::VARA_SS58_PREFIX)
267 .map_err(serde::ser::Error::custom)?;
268 serializer.serialize_str(address.as_str())
269 }
270}
271
272#[cfg(all(feature = "serde", feature = "ethexe"))]
273impl Serialize for ActorId {
274 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275 where
276 S: Serializer,
277 {
278 let id: H160 = self.to_address_lossy();
279 id.serialize(serializer)
280 }
281}
282
283#[cfg(all(feature = "serde", not(feature = "ethexe")))]
284impl<'de> Deserialize<'de> for ActorId {
285 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
286 where
287 D: Deserializer<'de>,
288 {
289 struct ActorIdVisitor;
290
291 impl de::Visitor<'_> for ActorIdVisitor {
292 type Value = ActorId;
293
294 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
295 formatter.write_str("a string in SS58 format")
296 }
297
298 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
299 where
300 E: de::Error,
301 {
302 let raw_address = RawSs58Address::from_ss58check(value)
303 .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(value), &self))?
304 .into();
305 Ok(Self::Value::new(raw_address))
306 }
307 }
308
309 deserializer.deserialize_identifier(ActorIdVisitor)
310 }
311}
312
313#[cfg(all(feature = "serde", feature = "ethexe"))]
314impl<'de> Deserialize<'de> for ActorId {
315 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
316 where
317 D: Deserializer<'de>,
318 {
319 let str = String::deserialize(deserializer)?;
320 let hex_str = str.strip_prefix("0x").unwrap_or(&str);
321 let bytes = hex::decode(hex_str)
322 .map_err(|e| serde::de::Error::custom(format!("Invalid hex: {e}")))?;
323
324 if bytes.len() != 20 {
325 return Err(serde::de::Error::custom(format!(
326 "Expected 20 bytes, got {}",
327 bytes.len()
328 )));
329 }
330
331 let mut actor_id = [0u8; 32];
332 actor_id[12..].copy_from_slice(&bytes);
333
334 Ok(ActorId(actor_id))
335 }
336}
337
338#[repr(transparent)]
346#[derive(
347 Clone,
348 Copy,
349 Default,
350 Hash,
351 Ord,
352 PartialEq,
353 PartialOrd,
354 Eq,
355 From,
356 Into,
357 AsRef,
358 AsMut,
359 Zeroable,
360 Pod,
361)]
362#[as_ref(forward)]
363#[as_mut(forward)]
364#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
365pub struct MessageId([u8; 32]);
366
367macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, MessageId);
368
369#[repr(transparent)]
378#[derive(
379 Clone,
380 Copy,
381 Default,
382 Hash,
383 Ord,
384 PartialEq,
385 PartialOrd,
386 Eq,
387 From,
388 Into,
389 AsRef,
390 AsMut,
391 Zeroable,
392 Pod,
393)]
394#[as_ref(forward)]
395#[as_mut(forward)]
396#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
397pub struct CodeId([u8; 32]);
398
399macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str try_from_slice display debug serde, CodeId);
400
401#[repr(transparent)]
406#[derive(
407 Clone,
408 Copy,
409 Default,
410 Hash,
411 Ord,
412 PartialEq,
413 PartialOrd,
414 Eq,
415 From,
416 Into,
417 AsRef,
418 AsMut,
419 Zeroable,
420 Pod,
421)]
422#[as_ref(forward)]
423#[as_mut(forward)]
424#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
425pub struct ReservationId([u8; 32]);
426
427macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, ReservationId);
428
429#[cfg(test)]
430mod tests {
431 extern crate alloc;
432
433 use crate::{ActorId, H160};
434 use alloc::format;
435 use core::str::FromStr;
436
437 fn actor_id() -> ActorId {
438 ActorId::from_str("0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e")
439 .unwrap()
440 }
441
442 #[test]
445 #[should_panic]
446 fn duplicate_version_in_actor_id_fmt_test() {
447 let id = actor_id();
448 let _ = format!("{id:+42}");
449 }
450
451 #[test]
452 fn formatting_test() {
453 let id = actor_id();
454
455 assert_eq!(
457 format!("{id:?}"),
458 "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
459 );
460 assert_eq!(format!("{id:.0?}"), "0x..");
462 assert_eq!(format!("{id:.1?}"), "0x6a..3e");
464 assert_eq!(format!("{id:.2?}"), "0x6a51..ed3e");
466 assert_eq!(format!("{id:.4?}"), "0x6a519a19..f765ed3e");
468 assert_eq!(
470 format!("{id:.15?}"),
471 "0x6a519a19ffdfd8f45c310b44aecf15..0c713bf841a8cb695b0ea5f765ed3e"
472 );
473 assert_eq!(
475 format!("{id:.30?}"),
476 "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
477 );
478 assert_eq!(
480 format!("{id:+}"),
481 "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
482 );
483 assert_eq!(
485 format!("{id:42}"),
486 "5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD"
487 );
488 assert_eq!(format!("{id:+.0}"), "..");
490 assert_eq!(format!("{id:+.1}"), "k..m");
492 assert_eq!(format!("{id:+.2}"), "kG..Sm");
494 assert_eq!(format!("{id:+.4}"), "kGhw..6PSm");
496 assert_eq!(format!("{id:+.15}"), "kGhwPiWGsCZkaUN..APCSDJM2uJv6PSm");
498 assert_eq!(
500 format!("{id:+.25}"),
501 "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
502 );
503 assert_eq!(
505 format!("{id:#}"),
506 "ActorId(0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e)"
507 );
508 assert_eq!(format!("{id:#.2}"), "ActorId(0x6a51..ed3e)");
510 assert_eq!(format!("{id:+#.2}"), "ActorId(kG..Sm)");
512 assert_eq!(
514 format!("{id:+#}"),
515 "ActorId(kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm)"
516 );
517 assert_eq!(
519 format!("{id:#42}"),
520 "ActorId(5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD)"
521 );
522 }
523
524 #[test]
527 fn actor_id_from_slice_error_implementation() {
528 let bytes = "foobar";
529 let result: Result<ActorId, _> = bytes.as_bytes().try_into();
530 assert!(result.is_err());
531 }
532
533 #[test]
534 fn actor_id_ethereum_address() {
535 let address: H160 = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
536 .parse()
537 .unwrap();
538 assert_eq!(
539 format!("{address:?}"),
540 "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
541 );
542
543 let actor_id: ActorId = address.into();
544 assert_eq!(
545 format!("{actor_id}"),
546 "0x00000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5"
547 );
548
549 let address = actor_id.to_address_lossy();
550 assert_eq!(
551 format!("{address:?}"),
552 "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
553 );
554 }
555}