diff --git a/sdk/src/types/block/context_input/block_issuance_credit.rs b/sdk/src/types/block/context_input/block_issuance_credit.rs new file mode 100644 index 0000000000..3a1b0fcec5 --- /dev/null +++ b/sdk/src/types/block/context_input/block_issuance_credit.rs @@ -0,0 +1,65 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use derive_more::{Display, From}; + +use crate::types::block::output::AccountId; + +/// A Block Issuance Credit (BIC) Input provides the VM with context for the value of +/// the BIC vector of a specific slot. +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] +pub struct BlockIssuanceCreditContextInput(AccountId); + +impl BlockIssuanceCreditContextInput { + /// The context input kind of a [`BlockIssuanceCreditContextInput`]. + pub const KIND: u8 = 1; + + /// Creates a new [`BlockIssuanceCreditContextInput`]. + pub fn new(account_id: AccountId) -> Self { + Self(account_id) + } + + /// Returns the account id of the [`BlockIssuanceCreditContextInput`]. + pub fn account_id(&self) -> AccountId { + self.0 + } +} + +mod dto { + use alloc::format; + + use serde::{Deserialize, Serialize}; + + use super::*; + + /// A Block Issuance Credit Input provides the VM with context for the value of + /// the BIC vector of a specific slot. + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + struct BlockIssuanceCreditContextInputDto { + #[serde(rename = "type")] + kind: u8, + account_id: AccountId, + } + + impl From<&BlockIssuanceCreditContextInput> for BlockIssuanceCreditContextInputDto { + fn from(value: &BlockIssuanceCreditContextInput) -> Self { + Self { + kind: BlockIssuanceCreditContextInput::KIND, + account_id: value.account_id(), + } + } + } + + impl From for BlockIssuanceCreditContextInput { + fn from(value: BlockIssuanceCreditContextInputDto) -> Self { + Self::new(value.account_id) + } + } + + impl_serde_typed_dto!( + BlockIssuanceCreditContextInput, + BlockIssuanceCreditContextInputDto, + "block issuance credit context input" + ); +} diff --git a/sdk/src/types/block/context_input/commitment.rs b/sdk/src/types/block/context_input/commitment.rs new file mode 100644 index 0000000000..2e00488779 --- /dev/null +++ b/sdk/src/types/block/context_input/commitment.rs @@ -0,0 +1,62 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use derive_more::{Display, From}; + +use crate::types::block::slot::SlotCommitmentId; + +/// A Commitment Input indicates that the input references a commitment to a certain slot. +#[derive(Clone, Copy, Display, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] +pub struct CommitmentContextInput(SlotCommitmentId); + +impl CommitmentContextInput { + /// The context input kind of a [`CommitmentContextInput`]. + pub const KIND: u8 = 0; + + /// Creates a new [`CommitmentContextInput`]. + pub fn new(commitment_id: SlotCommitmentId) -> Self { + Self(commitment_id) + } + + /// Returns the commitment id of the [`CommitmentContextInput`]. + pub fn commitment_id(&self) -> SlotCommitmentId { + self.0 + } +} + +mod dto { + use alloc::format; + + use serde::{Deserialize, Serialize}; + + use super::*; + + /// A Commitment Input indicates that the input references a commitment to a certain slot. + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + struct CommitmentContextInputDto { + #[serde(rename = "type")] + kind: u8, + commitment_id: SlotCommitmentId, + } + + impl From<&CommitmentContextInput> for CommitmentContextInputDto { + fn from(value: &CommitmentContextInput) -> Self { + Self { + kind: CommitmentContextInput::KIND, + commitment_id: value.commitment_id(), + } + } + } + + impl From for CommitmentContextInput { + fn from(value: CommitmentContextInputDto) -> Self { + Self::new(value.commitment_id) + } + } + impl_serde_typed_dto!( + CommitmentContextInput, + CommitmentContextInputDto, + "commitment context input" + ); +} diff --git a/sdk/src/types/block/context_input/mod.rs b/sdk/src/types/block/context_input/mod.rs index 140eec203f..33e9ed107a 100644 --- a/sdk/src/types/block/context_input/mod.rs +++ b/sdk/src/types/block/context_input/mod.rs @@ -1,28 +1,41 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod block_issuance_credit; +mod commitment; mod reward; -use derive_more::From; +use derive_more::{Display, From}; -pub use self::reward::RewardContextInput; +pub use self::{ + block_issuance_credit::BlockIssuanceCreditContextInput, commitment::CommitmentContextInput, + reward::RewardContextInput, +}; use crate::types::block::Error; /// A Context Input provides additional contextual information for the execution of a transaction, such as for different /// functionality related to accounts, commitments, or Mana rewards. A Context Input does not need to be unlocked. -#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] +#[derive(Clone, Eq, Display, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] #[packable(unpack_error = Error)] #[packable(tag_type = u8, with_error = Error::InvalidContextInputKind)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))] pub enum ContextInput { + /// A [`CommitmentContextInput`]. + #[packable(tag = CommitmentContextInput::KIND)] + Commitment(CommitmentContextInput), + /// A [`BlockIssuanceCreditContextInput`]. + #[packable(tag = BlockIssuanceCreditContextInput::KIND)] + BlockIssuanceCredit(BlockIssuanceCreditContextInput), /// A [`RewardContextInput`]. #[packable(tag = RewardContextInput::KIND)] Reward(RewardContextInput), - // TODO: Commitment Input https://github.com/iotaledger/iota-sdk/issues/901 and Block Issuance Credit Input https://github.com/iotaledger/iota-sdk/issues/906 } impl core::fmt::Debug for ContextInput { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { + Self::Commitment(input) => input.fmt(f), + Self::BlockIssuanceCredit(input) => input.fmt(f), Self::Reward(input) => input.fmt(f), } } @@ -32,10 +45,42 @@ impl ContextInput { /// Returns the context input kind of a `ContextInput`. pub fn kind(&self) -> u8 { match self { + Self::Commitment(_) => CommitmentContextInput::KIND, + Self::BlockIssuanceCredit(_) => BlockIssuanceCreditContextInput::KIND, Self::Reward(_) => RewardContextInput::KIND, } } + /// Checks whether the context input is a [`CommitmentContextInput`]. + pub fn is_commitment(&self) -> bool { + matches!(self, Self::Commitment(_)) + } + + /// Gets the input as an actual [`CommitmentContextInput`]. + /// PANIC: do not call on a non-commitment context input. + pub fn as_commitment(&self) -> &CommitmentContextInput { + if let Self::Commitment(input) = self { + input + } else { + panic!("invalid downcast of non-CommitmentContextInput"); + } + } + + /// Checks whether the context input is a [`BlockIssuanceCreditContextInput`]. + pub fn is_block_issuance_credit(&self) -> bool { + matches!(self, Self::BlockIssuanceCredit(_)) + } + + /// Gets the input as an actual [`BlockIssuanceCreditContextInput`]. + /// PANIC: do not call on a non-block-issuance-credit context input. + pub fn as_block_issuance_credit(&self) -> &BlockIssuanceCreditContextInput { + if let Self::BlockIssuanceCredit(input) = self { + input + } else { + panic!("invalid downcast of non-BlockIssuanceCreditContextInput"); + } + } + /// Checks whether the context input is a [`RewardContextInput`]. pub fn is_reward(&self) -> bool { matches!(self, Self::Reward(_)) @@ -44,40 +89,100 @@ impl ContextInput { /// Gets the input as an actual [`RewardContextInput`]. /// PANIC: do not call on a non-reward context input. pub fn as_reward(&self) -> &RewardContextInput { - let Self::Reward(input) = self; - input + if let Self::Reward(input) = self { + input + } else { + panic!("invalid downcast of non-RewardContextInput"); + } } } -pub mod dto { - use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests { + + use super::ContextInput; - pub use super::reward::dto::RewardContextInputDto; - use super::*; - use crate::types::block::Error; + #[test] + fn test_commitment() { + let commitment: ContextInput = serde_json::from_str( + r#" + { + "type": 0, + "commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689" + } + "#, + ) + .unwrap(); + assert!(commitment.is_commitment()); + assert_eq!( + commitment.as_commitment().commitment_id().to_string(), + "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689" + ); - /// Describes all the different context input types. - #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, From)] - #[serde(untagged)] - pub enum ContextInputDto { - Reward(RewardContextInputDto), + // Test wrong type returns error. + let commitment_deserialization_result: Result = serde_json::from_str( + r#" + { + "type": 2, + "commitmentId": "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d82bf96689" + } + "#, + ); + assert!(commitment_deserialization_result.is_err()); } - impl From<&ContextInput> for ContextInputDto { - fn from(value: &ContextInput) -> Self { - match value { - ContextInput::Reward(u) => Self::Reward(u.into()), + #[test] + fn test_block_issuance_credit() { + let bic: ContextInput = serde_json::from_str( + r#" + { + "type": 1, + "accountId": "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" } - } + "#, + ) + .unwrap(); + assert!(bic.is_block_issuance_credit()); + assert_eq!( + bic.as_block_issuance_credit().account_id().to_string(), + "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + ); + + // Test wrong type returns error. + let bic_deserialization_result: Result = serde_json::from_str( + r#" + { + "type": 2, + "accountId": "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + } + "#, + ); + assert!(bic_deserialization_result.is_err()); } - impl TryFrom for ContextInput { - type Error = Error; + #[test] + fn test_reward() { + let reward: ContextInput = serde_json::from_str( + r#" + { + "type": 2, + "index": 10 + } + "#, + ) + .unwrap(); + assert!(reward.is_reward()); + assert_eq!(reward.as_reward().index(), 10); - fn try_from(value: ContextInputDto) -> Result { - match value { - ContextInputDto::Reward(u) => Ok(Self::Reward(u.try_into()?)), + // Test wrong type returns error. + let reward_serialization_result: Result = serde_json::from_str( + r#" + { + "type": 0, + "index": 10 } - } + "#, + ); + assert!(reward_serialization_result.is_err()) } } diff --git a/sdk/src/types/block/context_input/reward.rs b/sdk/src/types/block/context_input/reward.rs index 3ee3afb106..3c01197ef4 100644 --- a/sdk/src/types/block/context_input/reward.rs +++ b/sdk/src/types/block/context_input/reward.rs @@ -22,17 +22,19 @@ impl RewardContextInput { } } -pub(crate) mod dto { +mod dto { + use alloc::format; + use serde::{Deserialize, Serialize}; use super::*; /// A Reward Context Input is an input that indicates which transaction Input is claiming Mana rewards. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] - pub struct RewardContextInputDto { + struct RewardContextInputDto { #[serde(rename = "type")] - pub kind: u8, - pub index: u16, + kind: u8, + index: u16, } impl From<&RewardContextInput> for RewardContextInputDto { @@ -49,4 +51,6 @@ pub(crate) mod dto { Self::new(value.index) } } + + impl_serde_typed_dto!(RewardContextInput, RewardContextInputDto, "reward context input"); }