gprimitives/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Gear primitive types.
20
21#![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/// The error type returned when conversion fails.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
63pub enum ConversionError {
64    /// Invalid slice length.
65    #[error("Slice should be 32 length")]
66    InvalidSliceLength,
67    /// Invalid hex string.
68    #[error("Invalid hex string")]
69    InvalidHexString,
70    /// Invalid SS58 address.
71    #[error("Invalid SS58 address")]
72    InvalidSs58Address,
73    /// SS58 encoding failed.
74    #[error("SS58 encoding failed")]
75    Ss58Encode,
76}
77
78/// Message handle.
79///
80/// Gear allows users and programs to interact with other users and programs via
81/// messages. Message creation consists of the following parts: message
82/// initialization, filling the message with payload (can be gradual), and
83/// message sending.
84#[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/// Program (actor) identifier.
90///
91/// Gear allows user and program interactions via messages. Source and target
92/// program as well as user are represented by 256-bit identifier `ActorId`
93/// struct. The source `ActorId` for a message being processed can be obtained
94/// using `gstd::msg::source()` function. Also, each send function has a target
95/// `ActorId` as one of the arguments.
96///
97/// NOTE: Implementation of `From<u64>` places bytes from idx=12 for Eth compatibility.
98#[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    /// Returns the ss58-check address with default ss58 version.
124    pub fn to_ss58check(&self) -> Result<Ss58Address, ConversionError> {
125        RawSs58Address::from(self.0)
126            .to_ss58check()
127            .map_err(|_| ConversionError::Ss58Encode)
128    }
129
130    /// Returns the ss58-check address with given ss58 version.
131    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    /// Returns [`H160`] with possible loss of the first 12 bytes.
138    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
175// TODO kuzmindev: implement Display for ActorId as Ethereum address when `ethexe` feature enabled.
176impl 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/// Message identifier.
339///
340/// Gear allows users and program interactions via messages. Each message has
341/// its own unique 256-bit id. This id is represented via the `MessageId`
342/// struct. The message identifier can be obtained for the currently processed
343/// message using the `gstd::msg::id()` function. Also, each send and reply
344/// functions return a message identifier.
345#[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/// Code identifier.
370///
371/// This identifier can be obtained as a result of executing the
372/// `gear.uploadCode` extrinsic. Actually, the code identifier is the Blake2
373/// hash of the Wasm binary code blob.
374///
375/// Code identifier is required when creating programs from programs (see
376/// `gstd::prog` module for details).
377#[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/// Reservation identifier.
402///
403/// The identifier is used to reserve and unreserve gas amount for program
404/// execution later.
405#[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 that ActorId cannot be formatted using
443    /// Vara format and custom version at the same time.
444    #[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        // `Debug`/`Display`.
456        assert_eq!(
457            format!("{id:?}"),
458            "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
459        );
460        // `Debug`/`Display` with precision 0.
461        assert_eq!(format!("{id:.0?}"), "0x..");
462        // `Debug`/`Display` with precision 1.
463        assert_eq!(format!("{id:.1?}"), "0x6a..3e");
464        // `Debug`/`Display` with precision 2.
465        assert_eq!(format!("{id:.2?}"), "0x6a51..ed3e");
466        // `Debug`/`Display` with precision 4.
467        assert_eq!(format!("{id:.4?}"), "0x6a519a19..f765ed3e");
468        // `Debug`/`Display` with precision 15.
469        assert_eq!(
470            format!("{id:.15?}"),
471            "0x6a519a19ffdfd8f45c310b44aecf15..0c713bf841a8cb695b0ea5f765ed3e"
472        );
473        // `Debug`/`Display` with precision 30 (the same for any case >= 16).
474        assert_eq!(
475            format!("{id:.30?}"),
476            "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
477        );
478        // `Debug`/`Display` with sign + (vara address).
479        assert_eq!(
480            format!("{id:+}"),
481            "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
482        );
483        // `Debug`/`Display` with width (custom address, 42 means substrate).
484        assert_eq!(
485            format!("{id:42}"),
486            "5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD"
487        );
488        // `Debug`/`Display` with sign + (vara address) and with precision 0.
489        assert_eq!(format!("{id:+.0}"), "..");
490        // `Debug`/`Display` with sign + (vara address) and with precision 1.
491        assert_eq!(format!("{id:+.1}"), "k..m");
492        // `Debug`/`Display` with sign + (vara address) and with precision 2.
493        assert_eq!(format!("{id:+.2}"), "kG..Sm");
494        // `Debug`/`Display` with sign + (vara address) and with precision 4.
495        assert_eq!(format!("{id:+.4}"), "kGhw..6PSm");
496        // `Debug`/`Display` with sign + (vara address) and with precision 15.
497        assert_eq!(format!("{id:+.15}"), "kGhwPiWGsCZkaUN..APCSDJM2uJv6PSm");
498        // `Debug`/`Display` with sign + (vara address) and with precision 25 (the same for any case >= 25).
499        assert_eq!(
500            format!("{id:+.25}"),
501            "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
502        );
503        // Alternate formatter.
504        assert_eq!(
505            format!("{id:#}"),
506            "ActorId(0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e)"
507        );
508        // Alternate formatter with precision 2.
509        assert_eq!(format!("{id:#.2}"), "ActorId(0x6a51..ed3e)");
510        // Alternate formatter with precision 2.
511        assert_eq!(format!("{id:+#.2}"), "ActorId(kG..Sm)");
512        // Alternate formatter with sign + (vara address).
513        assert_eq!(
514            format!("{id:+#}"),
515            "ActorId(kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm)"
516        );
517        // Alternate formatter with width (custom address, 42 means substrate).
518        assert_eq!(
519            format!("{id:#42}"),
520            "ActorId(5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD)"
521        );
522    }
523
524    /// Test that ActorId's `try_from(bytes)` constructor causes panic
525    /// when the argument has the wrong length
526    #[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}