hedera 0.9.0

The SDK for interacting with Hedera Hashgraph.
/*
 * ‌
 * Hedera Rust SDK
 * ​
 * Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
 * ​
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ‍
 */

use std::ops::Not;

use hedera_proto::services;

use crate::protobuf::ToProtobuf;
use crate::{
    AccountId,
    ContractId,
    Error,
    FileId,
    FromProtobuf,
    ScheduleId,
    Status,
    TokenId,
    TopicId,
    TransactionId,
};

/// The summary of a transaction's result so far, if the transaction has reached consensus.
/// Response from [`TransactionReceiptQuery`][crate::TransactionReceiptQuery].

#[cfg_attr(feature = "ffi", serde_with::skip_serializing_none)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
pub struct TransactionReceipt {
    // fixme(sr): better doc comment.
    /// The ID of the transaction that this is a receipt for.
    pub transaction_id: Option<TransactionId>,

    /// The consensus status of the transaction; is UNKNOWN if consensus has not been reached, or if
    /// the associated transaction did not have a valid payer signature.
    pub status: Status,

    /// In the receipt for an `AccountCreateTransaction`, the id of the newly created account.
    pub account_id: Option<AccountId>,

    /// In the receipt for a `FileCreateTransaction`, the id of the newly created file.
    pub file_id: Option<FileId>,

    /// In the receipt for a `ContractCreateTransaction`, the id of the newly created contract.
    pub contract_id: Option<ContractId>,

    // The exchange rates in effect when the transaction reached consensus.
    // TODO: pub exchange_rate: ExchangeRate,
    /// In the receipt for a `TopicCreateTransaction`, the id of the newly created topic.
    pub topic_id: Option<TopicId>,

    /// In the receipt for a `TopicMessageSubmitTransaction`, the new sequence number of the topic
    /// that received the message.
    pub topic_sequence_number: u64,

    // TODO: use a hash type (for display/debug/serialize purposes)
    /// In the receipt for a `TopicMessageSubmitTransaction`, the new running hash of the
    /// topic that received the message.
    #[cfg_attr(
        feature = "ffi",
        serde(with = "serde_with::As::<Option<serde_with::base64::Base64>>")
    )]
    pub topic_running_hash: Option<Vec<u8>>,

    /// In the receipt of a `TopicMessageSubmitTransaction`, the version of the SHA-384
    /// digest used to update the running hash.
    pub topic_running_hash_version: u64,

    /// In the receipt for a `TokenCreateTransaction`, the id of the newly created token.
    pub token_id: Option<TokenId>,

    /// Populated in the receipt of `TokenMint`, `TokenWipe`, and `TokenBurn` transactions.
    ///
    /// For fungible tokens, the current total supply of this token.
    /// For non-fungible tokens, the total number of NFTs issued for a given token id.
    pub total_supply: u64,

    /// In the receipt for a `ScheduleCreateTransaction`, the id of the newly created schedule.
    pub schedule_id: Option<ScheduleId>,

    /// In the receipt of a `ScheduleCreateTransaction` or `ScheduleSignTransaction` that resolves
    /// to `Success`, the `TransactionId` that should be used to query for the receipt or
    /// record of the relevant scheduled transaction.
    pub scheduled_transaction_id: Option<TransactionId>,

    /// In the receipt of a `TokenMintTransaction` for tokens of type `NonFungibleUnique`,
    /// the serial numbers of the newly created NFTs.
    pub serials: Vec<i64>,

    /// The receipts of processing all transactions with the given id, in consensus time order.
    pub duplicates: Vec<TransactionReceipt>,

    /// The receipts (if any) of all child transactions spawned by the transaction with the
    /// given top-level id, in consensus order.
    pub children: Vec<TransactionReceipt>,
}

impl TransactionReceipt {
    /// Create a new `FileInfo` from protobuf-encoded `bytes`.
    ///
    /// # Errors
    /// - [`Error::FromProtobuf`] if decoding the bytes fails to produce a valid protobuf.
    /// - [`Error::FromProtobuf`] if decoding the protobuf fails.
    pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
        FromProtobuf::<services::TransactionReceipt>::from_bytes(bytes)
    }

    /// Convert `self` to a protobuf-encoded [`Vec<u8>`].
    #[must_use]
    pub fn to_bytes(&self) -> Vec<u8> {
        ToProtobuf::to_bytes(self)
    }

    /// Validate [`status`](Self.status) and return an `Err` if it isn't [`Status::Ok`]
    ///
    /// # Errors
    /// - [`Error::ReceiptStatus`] if `validate && self.status != Status::Ok`
    pub fn validate_status(&self, validate: bool) -> crate::Result<&Self> {
        if validate && self.status != Status::Ok {
            Err(Error::ReceiptStatus { status: self.status, transaction_id: self.transaction_id })
        } else {
            Ok(self)
        }
    }

    fn from_protobuf(
        receipt: services::TransactionReceipt,
        duplicates: Vec<Self>,
        children: Vec<Self>,
        transaction_id: Option<TransactionId>,
    ) -> crate::Result<Self> {
        let status = if let Some(status) = Status::from_i32(receipt.status) {
            status
        } else {
            return Err(Error::ResponseStatusUnrecognized(receipt.status));
        };

        let account_id = Option::from_protobuf(receipt.account_id)?;
        let file_id = Option::from_protobuf(receipt.file_id)?;
        let contract_id = Option::from_protobuf(receipt.contract_id)?;
        let topic_id = Option::from_protobuf(receipt.topic_id)?;
        let token_id = Option::from_protobuf(receipt.token_id)?;
        let schedule_id = Option::from_protobuf(receipt.schedule_id)?;

        let scheduled_transaction_id = Option::from_protobuf(receipt.scheduled_transaction_id)?;

        Ok(Self {
            status,
            total_supply: receipt.new_total_supply,
            serials: receipt.serial_numbers,
            topic_running_hash_version: receipt.topic_running_hash_version,
            topic_sequence_number: receipt.topic_sequence_number,
            topic_running_hash: receipt
                .topic_running_hash
                .is_empty()
                .not()
                .then_some(receipt.topic_running_hash),
            scheduled_transaction_id,
            account_id,
            file_id,
            contract_id,
            topic_id,
            token_id,
            schedule_id,
            duplicates,
            children,
            transaction_id,
        })
    }

    pub(crate) fn from_response_protobuf(
        pb: services::response::Response,
        transaction_id: Option<TransactionId>,
    ) -> crate::Result<Self> {
        let pb = pb_getv!(pb, TransactionGetReceipt, services::response::Response);

        let receipt = pb_getf!(pb, receipt)?;

        let duplicates = Vec::from_protobuf(pb.duplicate_transaction_receipts)?;

        let children = Vec::from_protobuf(pb.child_transaction_receipts)?;

        Self::from_protobuf(receipt, duplicates, children, transaction_id)
    }
}

impl FromProtobuf<services::response::Response> for TransactionReceipt {
    fn from_protobuf(pb: services::response::Response) -> crate::Result<Self>
    where
        Self: Sized,
    {
        Self::from_response_protobuf(pb, None)
    }
}

impl FromProtobuf<services::TransactionReceipt> for TransactionReceipt {
    fn from_protobuf(receipt: services::TransactionReceipt) -> crate::Result<Self>
    where
        Self: Sized,
    {
        Self::from_protobuf(receipt, Vec::new(), Vec::new(), None)
    }
}

impl ToProtobuf for TransactionReceipt {
    type Protobuf = services::TransactionReceipt;

    fn to_protobuf(&self) -> Self::Protobuf {
        services::TransactionReceipt {
            status: self.status as i32,
            account_id: self.account_id.to_protobuf(),
            file_id: self.file_id.to_protobuf(),
            contract_id: self.contract_id.to_protobuf(),
            exchange_rate: None,
            topic_id: self.topic_id.to_protobuf(),
            topic_sequence_number: self.topic_sequence_number,
            topic_running_hash: self.topic_running_hash.clone().unwrap_or_default(),
            topic_running_hash_version: self.topic_running_hash_version,
            token_id: self.token_id.to_protobuf(),
            new_total_supply: self.total_supply,
            schedule_id: self.schedule_id.to_protobuf(),
            scheduled_transaction_id: self.scheduled_transaction_id.to_protobuf(),
            serial_numbers: self.serials.clone(),
        }
    }
}