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_auto_cfg))]
26
27extern crate alloc;
28
29pub use gear_ss58::Ss58Address;
30pub use nonzero_u256::NonZeroU256;
31pub use primitive_types::{H160, H256, U256};
32
33pub mod utils;
34
35mod macros;
36mod nonzero_u256;
37#[cfg(feature = "ethexe")]
38mod sol_types;
39
40use core::{
41 fmt,
42 str::{self, FromStr},
43};
44use derive_more::{AsMut, AsRef, From, Into};
45use gear_ss58::RawSs58Address;
46#[cfg(feature = "codec")]
47use scale_info::{
48 TypeInfo,
49 scale::{self, Decode, Encode, MaxEncodedLen},
50};
51#[cfg(feature = "serde")]
52use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
56pub enum ConversionError {
57 #[error("Slice should be 32 length")]
59 InvalidSliceLength,
60 #[error("Invalid hex string")]
62 InvalidHexString,
63 #[error("Invalid SS58 address")]
65 InvalidSs58Address,
66 #[error("SS58 encoding failed")]
68 Ss58Encode,
69}
70
71#[repr(transparent)]
78#[derive(Clone, Copy, Debug, PartialEq, Eq, From, Into)]
79#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
80pub struct MessageHandle(u32);
81
82#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
92#[as_ref(forward)]
93#[as_mut(forward)]
94#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
95pub struct ActorId([u8; 32]);
96
97macros::impl_primitive!(new zero into_bytes from_h256 into_h256 try_from_slice debug, ActorId);
98
99impl ActorId {
100 pub fn to_ss58check(&self) -> Result<Ss58Address, ConversionError> {
102 RawSs58Address::from(self.0)
103 .to_ss58check()
104 .map_err(|_| ConversionError::Ss58Encode)
105 }
106
107 pub fn to_ss58check_with_version(&self, version: u16) -> Result<Ss58Address, ConversionError> {
109 RawSs58Address::from(self.0)
110 .to_ss58check_with_prefix(version)
111 .map_err(|_| ConversionError::Ss58Encode)
112 }
113
114 pub fn to_address_lossy(&self) -> H160 {
116 let mut h160 = H160::zero();
117 h160.0.copy_from_slice(&self.into_bytes()[12..]);
118 h160
119 }
120}
121
122impl From<u64> for ActorId {
123 fn from(value: u64) -> Self {
124 let mut id = Self::zero();
125 id.0[12..20].copy_from_slice(&value.to_le_bytes()[..]);
126 id
127 }
128}
129
130impl From<H160> for ActorId {
131 fn from(h160: H160) -> Self {
132 let mut actor_id = Self::zero();
133 actor_id.0[12..].copy_from_slice(h160.as_ref());
134 actor_id
135 }
136}
137
138impl TryInto<H160> for ActorId {
139 type Error = &'static str;
140
141 fn try_into(self) -> Result<H160, Self::Error> {
142 if !self.0[..12].iter().all(|i| i.eq(&0)) {
143 Err("ActorId has non-zero prefix")
144 } else {
145 let mut h160 = H160::zero();
146 h160.0.copy_from_slice(&self.into_bytes()[12..]);
147 Ok(h160)
148 }
149 }
150}
151
152impl fmt::Display for ActorId {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 let byte_array = utils::ByteSliceFormatter::Array(&self.0);
155
156 let is_alternate = f.alternate();
157 if is_alternate {
158 f.write_str(concat!(stringify!(ActorId), "("))?;
159 }
160
161 let sign_plus = f.sign_plus();
162 let width = f.width();
163
164 if sign_plus && width.is_some() {
165 return Err(fmt::Error);
166 }
167
168 let version = if sign_plus {
169 Some(gear_ss58::VARA_SS58_PREFIX)
170 } else if let Some(version) = width {
171 Some(version.try_into().map_err(|_| fmt::Error)?)
172 } else {
173 None
174 };
175
176 if let Some(version) = version {
177 let address = self
178 .to_ss58check_with_version(version)
179 .map_err(|_| fmt::Error)?;
180 let address_str = address.as_str();
181
182 let len = address.as_str().len();
183 let median = len.div_ceil(2);
184
185 let mut e1 = median;
186 let mut s2 = median;
187
188 if let Some(precision) = f.precision()
189 && precision < median
190 {
191 e1 = precision;
192 s2 = len - precision;
193 }
194
195 let p1 = &address_str[..e1];
196 let p2 = &address_str[s2..];
197 let sep = if e1.ne(&s2) { ".." } else { Default::default() };
198
199 write!(f, "{p1}{sep}{p2}")?;
200 } else {
201 byte_array.fmt(f)?;
202 }
203
204 if is_alternate {
205 f.write_str(")")?;
206 }
207
208 Ok(())
209 }
210}
211
212impl FromStr for ActorId {
213 type Err = ConversionError;
214
215 fn from_str(s: &str) -> Result<Self, Self::Err> {
216 let actod_id = if let Some(s) = s.strip_prefix("0x") {
217 if s.len() != 64 {
218 return Err(ConversionError::InvalidHexString);
219 }
220 let mut actor_id = Self::zero();
221 hex::decode_to_slice(s, &mut actor_id.0)
222 .map_err(|_| ConversionError::InvalidHexString)?;
223 actor_id
224 } else {
225 let raw_address = RawSs58Address::from_ss58check(s)
226 .map_err(|_| ConversionError::InvalidSs58Address)?
227 .into();
228 Self::new(raw_address)
229 };
230
231 Ok(actod_id)
232 }
233}
234
235#[cfg(all(feature = "serde", not(feature = "ethexe")))]
236impl Serialize for ActorId {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 let address = self
242 .to_ss58check_with_version(gear_ss58::VARA_SS58_PREFIX)
243 .map_err(serde::ser::Error::custom)?;
244 serializer.serialize_str(address.as_str())
245 }
246}
247
248#[cfg(all(feature = "serde", feature = "ethexe"))]
249impl Serialize for ActorId {
250 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251 where
252 S: Serializer,
253 {
254 let id: H160 = self.to_address_lossy();
255 id.serialize(serializer)
256 }
257}
258
259#[cfg(feature = "serde")]
260impl<'de> Deserialize<'de> for ActorId {
261 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262 where
263 D: Deserializer<'de>,
264 {
265 struct ActorIdVisitor;
266
267 impl de::Visitor<'_> for ActorIdVisitor {
268 type Value = ActorId;
269
270 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
271 formatter.write_str("a string in SS58 format")
272 }
273
274 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
275 where
276 E: de::Error,
277 {
278 let raw_address = RawSs58Address::from_ss58check(value)
279 .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(value), &self))?
280 .into();
281 Ok(Self::Value::new(raw_address))
282 }
283 }
284
285 deserializer.deserialize_identifier(ActorIdVisitor)
286 }
287}
288
289#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
297#[as_ref(forward)]
298#[as_mut(forward)]
299#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
300pub struct MessageId([u8; 32]);
301
302macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, MessageId);
303
304#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
313#[as_ref(forward)]
314#[as_mut(forward)]
315#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
316pub struct CodeId([u8; 32]);
317
318macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str try_from_slice display debug serde, CodeId);
319
320#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
325#[as_ref(forward)]
326#[as_mut(forward)]
327#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
328pub struct ReservationId([u8; 32]);
329
330macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, ReservationId);
331
332#[cfg(test)]
333mod tests {
334 extern crate alloc;
335
336 use crate::{ActorId, H160};
337 use alloc::format;
338 use core::str::FromStr;
339
340 fn actor_id() -> ActorId {
341 ActorId::from_str("0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e")
342 .unwrap()
343 }
344
345 #[test]
348 #[should_panic]
349 fn duplicate_version_in_actor_id_fmt_test() {
350 let id = actor_id();
351 let _ = format!("{id:+42}");
352 }
353
354 #[test]
355 fn formatting_test() {
356 let id = actor_id();
357
358 assert_eq!(
360 format!("{id:?}"),
361 "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
362 );
363 assert_eq!(format!("{id:.0?}"), "0x..");
365 assert_eq!(format!("{id:.1?}"), "0x6a..3e");
367 assert_eq!(format!("{id:.2?}"), "0x6a51..ed3e");
369 assert_eq!(format!("{id:.4?}"), "0x6a519a19..f765ed3e");
371 assert_eq!(
373 format!("{id:.15?}"),
374 "0x6a519a19ffdfd8f45c310b44aecf15..0c713bf841a8cb695b0ea5f765ed3e"
375 );
376 assert_eq!(
378 format!("{id:.30?}"),
379 "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
380 );
381 assert_eq!(
383 format!("{id:+}"),
384 "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
385 );
386 assert_eq!(
388 format!("{id:42}"),
389 "5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD"
390 );
391 assert_eq!(format!("{id:+.0}"), "..");
393 assert_eq!(format!("{id:+.1}"), "k..m");
395 assert_eq!(format!("{id:+.2}"), "kG..Sm");
397 assert_eq!(format!("{id:+.4}"), "kGhw..6PSm");
399 assert_eq!(format!("{id:+.15}"), "kGhwPiWGsCZkaUN..APCSDJM2uJv6PSm");
401 assert_eq!(
403 format!("{id:+.25}"),
404 "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
405 );
406 assert_eq!(
408 format!("{id:#}"),
409 "ActorId(0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e)"
410 );
411 assert_eq!(format!("{id:#.2}"), "ActorId(0x6a51..ed3e)");
413 assert_eq!(format!("{id:+#.2}"), "ActorId(kG..Sm)");
415 assert_eq!(
417 format!("{id:+#}"),
418 "ActorId(kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm)"
419 );
420 assert_eq!(
422 format!("{id:#42}"),
423 "ActorId(5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD)"
424 );
425 }
426
427 #[test]
430 fn actor_id_from_slice_error_implementation() {
431 let bytes = "foobar";
432 let result: Result<ActorId, _> = bytes.as_bytes().try_into();
433 assert!(result.is_err());
434 }
435
436 #[test]
437 fn actor_id_ethereum_address() {
438 let address: H160 = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
439 .parse()
440 .unwrap();
441 assert_eq!(
442 format!("{address:?}"),
443 "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
444 );
445
446 let actor_id: ActorId = address.into();
447 assert_eq!(
448 format!("{actor_id}"),
449 "0x00000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5"
450 );
451
452 let address = actor_id.to_address_lossy();
453 assert_eq!(
454 format!("{address:?}"),
455 "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
456 );
457 }
458}