gear-core 2.0.0-pre.1

Gear core library
Documentation
// Copyright (C) Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

//! This module contains types commonly used as output in RPC calls.

use alloc::vec::Vec;
use gear_core_errors::ReplyCode;
use gprimitives::H256;
use parity_scale_codec::{Decode, Encode};
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use scale_info::TypeInfo;

use crate::message::UserMessage;

/// Pre-calculated gas consumption estimate for a message.
///
/// Intended to be used as a result in `calculateGasFor*` RPC calls.
#[derive(
    Clone, Debug, Default, PartialEq, Eq, Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct GasInfo {
    /// The minimum amount of gas required for successful execution.
    pub min_limit: u64,
    /// The amount of gas that would be reserved.
    pub reserved: u64,
    /// The amount of gas that would be burned.
    pub burned: u64,
    /// The amount of gas that may be returned.
    pub may_be_returned: u64,
    /// Indicates whether the message was placed into the waitlist.
    ///
    /// This flag signifies that `min_limit` guarantees apply only to the first execution attempt.
    pub waited: bool,
}

/// Pre-calculated reply information.
///
/// Intended to be used as a result in `calculateReplyFor*` RPC calls.
#[derive(
    Clone, Debug, PartialEq, Eq, Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo, Hash,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct ReplyInfo {
    /// Payload of the reply.
    #[cfg_attr(feature = "std", serde(with = "impl_serde::serialize"))]
    pub payload: Vec<u8>,
    /// Value attached to the reply.
    pub value: u128,
    /// Reply code of the reply.
    #[cfg_attr(feature = "std", serde(with = "serialize_reply_code"))]
    pub code: ReplyCode,
}

/// Pre-calculated reply information with user messages produced during calculation.
#[derive(
    Clone, Debug, PartialEq, Eq, Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo, Hash,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct CalculateReplyForHandleResult {
    /// Reply to the calculated handle message.
    pub reply: ReplyInfo,
    /// User messages sent during the calculated execution.
    pub messages: Vec<UserMessage>,
}

impl ReplyInfo {
    /// Calculates `blake2b` hash from [`ReplyInfo`].
    pub fn to_hash(&self) -> H256 {
        let ReplyInfo {
            payload,
            value,
            code,
        } = self;

        let bytes = [
            payload.as_ref(),
            value.to_be_bytes().as_ref(),
            code.to_bytes().as_ref(),
        ]
        .concat();
        super::utils::hash(&bytes).into()
    }
}

/// Serializer and deserializer for ReplyCode as 0x-prefixed hex string.
#[cfg(feature = "std")]
pub(crate) mod serialize_reply_code {
    use super::ReplyCode;
    use core::fmt::Write;
    use serde::de;

    pub fn serialize<S>(code: &ReplyCode, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut s = alloc::string::String::with_capacity(10);
        s.push_str("0x");
        for byte in code.to_bytes() {
            write!(&mut s, "{:02x}", byte).unwrap();
        }
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<ReplyCode, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct Visitor;

        impl<'b> de::Visitor<'b> for Visitor {
            type Value = ReplyCode;

            fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
                formatter.write_str("a 0x-prefixed hex string representing a 4-byte ReplyCode")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                let v = v.strip_prefix("0x").ok_or_else(|| {
                    E::custom("invalid format: expected a 0x-prefixed hex string")
                })?;
                let mut bytes = [0u8; 4];
                hex::decode_to_slice(v, &mut bytes)
                    .map_err(|e| E::custom(alloc::format!("invalid hex string: {e}")))?;
                Ok(ReplyCode::from_bytes(bytes))
            }
        }
        deserializer.deserialize_str(Visitor)
    }
}

/// `u128` value wrapper intended for usage in RPC calls due to serialization specifications.
#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    PartialEq,
    Eq,
    Encode,
    EncodeAsType,
    Decode,
    DecodeAsType,
    TypeInfo,
    derive_more::From,
    derive_more::Into,
)]
pub struct RpcValue(pub u128);

#[cfg(feature = "std")]
impl<'de> serde::Deserialize<'de> for RpcValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use alloc::format;
        use core::fmt;
        use serde::de::{self, Visitor};

        struct RpcValueVisitor;

        impl<'de> Visitor<'de> for RpcValueVisitor {
            type Value = RpcValue;

            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
                f.write_str("a numeric literal, a 0x-prefixed string with big-endian bytes, or a numeric string representing a u128; for large integer literals, consider using string options for clarity and to avoid potential parsing issues")
            }

            fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E> {
                Ok(RpcValue(v))
            }

            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
                Ok(RpcValue(v as u128))
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                if let Some(hex) = v.strip_prefix("0x") {
                    let bytes = hex::decode(hex)
                        .map_err(|e| E::custom(format!("invalid hex string: {e}")))?;

                    if bytes.len() > 16 {
                        return Err(E::custom("invalid hex string: too long for u128"));
                    }

                    // left pad to 16 bytes (big-endian)
                    let mut padded = [0u8; 16];
                    padded[16 - bytes.len()..].copy_from_slice(&bytes);

                    Ok(RpcValue(u128::from_be_bytes(padded)))
                } else {
                    v.parse::<u128>()
                        .map(RpcValue)
                        .map_err(|e| E::custom(format!("invalid numeric string: {e}")))
                }
            }
        }

        deserializer.deserialize_any(RpcValueVisitor)
    }
}