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_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/// The error type returned when conversion fails.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
56pub enum ConversionError {
57    /// Invalid slice length.
58    #[error("Slice should be 32 length")]
59    InvalidSliceLength,
60    /// Invalid hex string.
61    #[error("Invalid hex string")]
62    InvalidHexString,
63    /// Invalid SS58 address.
64    #[error("Invalid SS58 address")]
65    InvalidSs58Address,
66    /// SS58 encoding failed.
67    #[error("SS58 encoding failed")]
68    Ss58Encode,
69}
70
71/// Message handle.
72///
73/// Gear allows users and programs to interact with other users and programs via
74/// messages. Message creation consists of the following parts: message
75/// initialization, filling the message with payload (can be gradual), and
76/// message sending.
77#[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/// Program (actor) identifier.
83///
84/// Gear allows user and program interactions via messages. Source and target
85/// program as well as user are represented by 256-bit identifier `ActorId`
86/// struct. The source `ActorId` for a message being processed can be obtained
87/// using `gstd::msg::source()` function. Also, each send function has a target
88/// `ActorId` as one of the arguments.
89///
90/// NOTE: Implementation of `From<u64>` places bytes from idx=12 for Eth compatibility.
91#[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    /// Returns the ss58-check address with default ss58 version.
101    pub fn to_ss58check(&self) -> Result<Ss58Address, ConversionError> {
102        RawSs58Address::from(self.0)
103            .to_ss58check()
104            .map_err(|_| ConversionError::Ss58Encode)
105    }
106
107    /// Returns the ss58-check address with given ss58 version.
108    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    /// Returns [`H160`] with possible loss of the first 12 bytes.
115    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/// Message identifier.
290///
291/// Gear allows users and program interactions via messages. Each message has
292/// its own unique 256-bit id. This id is represented via the `MessageId`
293/// struct. The message identifier can be obtained for the currently processed
294/// message using the `gstd::msg::id()` function. Also, each send and reply
295/// functions return a message identifier.
296#[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/// Code identifier.
305///
306/// This identifier can be obtained as a result of executing the
307/// `gear.uploadCode` extrinsic. Actually, the code identifier is the Blake2
308/// hash of the Wasm binary code blob.
309///
310/// Code identifier is required when creating programs from programs (see
311/// `gstd::prog` module for details).
312#[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/// Reservation identifier.
321///
322/// The identifier is used to reserve and unreserve gas amount for program
323/// execution later.
324#[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 that ActorId cannot be formatted using
346    /// Vara format and custom version at the same time.
347    #[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        // `Debug`/`Display`.
359        assert_eq!(
360            format!("{id:?}"),
361            "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
362        );
363        // `Debug`/`Display` with precision 0.
364        assert_eq!(format!("{id:.0?}"), "0x..");
365        // `Debug`/`Display` with precision 1.
366        assert_eq!(format!("{id:.1?}"), "0x6a..3e");
367        // `Debug`/`Display` with precision 2.
368        assert_eq!(format!("{id:.2?}"), "0x6a51..ed3e");
369        // `Debug`/`Display` with precision 4.
370        assert_eq!(format!("{id:.4?}"), "0x6a519a19..f765ed3e");
371        // `Debug`/`Display` with precision 15.
372        assert_eq!(
373            format!("{id:.15?}"),
374            "0x6a519a19ffdfd8f45c310b44aecf15..0c713bf841a8cb695b0ea5f765ed3e"
375        );
376        // `Debug`/`Display` with precision 30 (the same for any case >= 16).
377        assert_eq!(
378            format!("{id:.30?}"),
379            "0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
380        );
381        // `Debug`/`Display` with sign + (vara address).
382        assert_eq!(
383            format!("{id:+}"),
384            "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
385        );
386        // `Debug`/`Display` with width (custom address, 42 means substrate).
387        assert_eq!(
388            format!("{id:42}"),
389            "5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD"
390        );
391        // `Debug`/`Display` with sign + (vara address) and with precision 0.
392        assert_eq!(format!("{id:+.0}"), "..");
393        // `Debug`/`Display` with sign + (vara address) and with precision 1.
394        assert_eq!(format!("{id:+.1}"), "k..m");
395        // `Debug`/`Display` with sign + (vara address) and with precision 2.
396        assert_eq!(format!("{id:+.2}"), "kG..Sm");
397        // `Debug`/`Display` with sign + (vara address) and with precision 4.
398        assert_eq!(format!("{id:+.4}"), "kGhw..6PSm");
399        // `Debug`/`Display` with sign + (vara address) and with precision 15.
400        assert_eq!(format!("{id:+.15}"), "kGhwPiWGsCZkaUN..APCSDJM2uJv6PSm");
401        // `Debug`/`Display` with sign + (vara address) and with precision 25 (the same for any case >= 25).
402        assert_eq!(
403            format!("{id:+.25}"),
404            "kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
405        );
406        // Alternate formatter.
407        assert_eq!(
408            format!("{id:#}"),
409            "ActorId(0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e)"
410        );
411        // Alternate formatter with precision 2.
412        assert_eq!(format!("{id:#.2}"), "ActorId(0x6a51..ed3e)");
413        // Alternate formatter with precision 2.
414        assert_eq!(format!("{id:+#.2}"), "ActorId(kG..Sm)");
415        // Alternate formatter with sign + (vara address).
416        assert_eq!(
417            format!("{id:+#}"),
418            "ActorId(kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm)"
419        );
420        // Alternate formatter with width (custom address, 42 means substrate).
421        assert_eq!(
422            format!("{id:#42}"),
423            "ActorId(5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD)"
424        );
425    }
426
427    /// Test that ActorId's `try_from(bytes)` constructor causes panic
428    /// when the argument has the wrong length
429    #[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}