Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transaction capabilities #1377

Merged
merged 31 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
36e2c82
Add transaction capabilities
Oct 2, 2023
c28c69a
Add test and fix a bug
Oct 2, 2023
8e9087e
Add semantic validation
Oct 3, 2023
bd892aa
Merge branch '2.0' into feat/transaction-capabilities
Oct 3, 2023
d76dedb
add field to dto and disable some tests
Oct 3, 2023
c8b9b35
refactor
Oct 3, 2023
9dd4e28
fix semantic validation?
Oct 4, 2023
1146aad
Merge branch '2.0' into feat/transaction-capabilities
Oct 4, 2023
39fe86d
Merge branch '2.0' into feat/transaction-capabilities
Oct 11, 2023
352e09a
Share capabilities code
Oct 11, 2023
2b689cb
Merge branch '2.0' into feat/transaction-capabilities
Oct 11, 2023
14bbe07
fix no_std
Oct 11, 2023
9579ec0
todos
Oct 13, 2023
aacccd0
Merge branch '2.0' into feat/transaction-capabilities
Oct 13, 2023
106f2cd
oops
Oct 13, 2023
2ca6b3b
Merge branch '2.0' into feat/transaction-capabilities
Oct 13, 2023
6f72ea6
New lines
thibault-martinez Oct 16, 2023
4394528
Move test
thibault-martinez Oct 16, 2023
ab7b102
Order!
thibault-martinez Oct 16, 2023
37a0958
NOT
Oct 16, 2023
c488cce
Merge branch '2.0' into feat/transaction-capabilities
Oct 16, 2023
62ef62d
cleanup
Oct 16, 2023
e31e5f5
cleanup 2
Oct 16, 2023
84a216a
!!!
Oct 16, 2023
08e1655
Add melt_and_burn_native_tokens test
thibault-martinez Oct 16, 2023
dcb6a41
Fix TransactionCapabilityFlag::BurnNativeTokens
thibault-martinez Oct 16, 2023
59b311f
Set burn NT capacity in ISA
thibault-martinez Oct 17, 2023
9f5d035
use all capabilities for ISA
Oct 17, 2023
10ac6db
Merge branch '2.0' into feat/transaction-capabilities
thibault-martinez Oct 17, 2023
7e8ec0e
Merge branch '2.0' into feat/transaction-capabilities
Oct 17, 2023
6c3daaa
Update sdk/src/types/block/capabilities.rs
thibault-martinez Oct 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/src/client/api/block_builder/input_selection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ impl InputSelection {
foundry_output,
input_native_tokens_builder.deref(),
output_native_tokens_builder.deref(),
Default::default(),
) {
log::debug!("validate_transitions error {err:?}");
return Err(Error::UnfulfillableRequirement(Requirement::Foundry(
Expand Down
4 changes: 4 additions & 0 deletions sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ use crate::types::block::{
pub enum Error {
ManaAllotmentsNotUniqueSorted,
ConsumedAmountOverflow,
ConsumedManaOverflow,
ConsumedNativeTokensAmountOverflow,
CreatedAmountOverflow,
CreatedManaOverflow,
CreatedNativeTokensAmountOverflow,
Crypto(CryptoError),
DuplicateBicAccountId(AccountId),
Expand Down Expand Up @@ -182,8 +184,10 @@ impl fmt::Display for Error {
match self {
Self::ManaAllotmentsNotUniqueSorted => write!(f, "mana allotments are not unique and/or sorted"),
Self::ConsumedAmountOverflow => write!(f, "consumed amount overflow"),
Self::ConsumedManaOverflow => write!(f, "consumed mana overflow"),
Self::ConsumedNativeTokensAmountOverflow => write!(f, "consumed native tokens amount overflow"),
Self::CreatedAmountOverflow => write!(f, "created amount overflow"),
Self::CreatedManaOverflow => write!(f, "created mana overflow"),
Self::CreatedNativeTokensAmountOverflow => write!(f, "created native tokens amount overflow"),
Self::Crypto(e) => write!(f, "cryptographic error: {e}"),
Self::DuplicateBicAccountId(account_id) => write!(f, "duplicate BIC account id: {account_id}"),
Expand Down
11 changes: 10 additions & 1 deletion sdk/src/types/block/output/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::types::{
NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError,
StateTransitionVerifier,
},
payload::transaction::TransactionCapabilityFlag,
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
unlock::Unlock,
Expand Down Expand Up @@ -646,7 +647,15 @@ impl StateTransitionVerifier for AccountOutput {
)
}

fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
if context
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
.essence
.capabilities()
.has_capability(TransactionCapabilityFlag::DestroyAccountOutputs)
{
// TODO: is this correct?
return Err(StateTransitionError::UnsupportedStateTransition);
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(())
}
}
Expand Down
1 change: 1 addition & 0 deletions sdk/src/types/block/output/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ impl DelegationOutput {

// Transition, just without full ValidationContext.
pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> {
#[allow(clippy::nonminimal_bool)]
kwek20 marked this conversation as resolved.
Show resolved Hide resolved
if !(current_state.delegation_id.is_null() && !next_state.delegation_id().is_null()) {
return Err(StateTransitionError::NonDelayedClaimingTransition);
}
Expand Down
17 changes: 17 additions & 0 deletions sdk/src/types/block/output/foundry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::types::{
NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError,
StateTransitionVerifier, TokenId, TokenScheme,
},
payload::transaction::{TransactionCapabilities, TransactionCapabilityFlag},
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
unlock::Unlock,
Expand Down Expand Up @@ -457,6 +458,7 @@ impl FoundryOutput {
next_state: &Self,
input_native_tokens: &BTreeMap<TokenId, U256>,
output_native_tokens: &BTreeMap<TokenId, U256>,
capabilities: TransactionCapabilities,
) -> Result<(), StateTransitionError> {
if current_state.account_address() != next_state.account_address()
|| current_state.serial_number != next_state.serial_number
Expand Down Expand Up @@ -510,6 +512,11 @@ impl FoundryOutput {
Ordering::Greater => {
// Melt / Burn

if capabilities.has_capability(TransactionCapabilityFlag::BurnNativeTokens) {
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
// TODO: is this correct?
return Err(StateTransitionError::UnsupportedStateTransition);
}
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved

if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens()
&& current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens()
{
Expand Down Expand Up @@ -574,10 +581,20 @@ impl StateTransitionVerifier for FoundryOutput {
next_state,
&context.input_native_tokens,
&context.output_native_tokens,
context.essence.capabilities(),
)
}

fn destruction(current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
if context
.essence
.capabilities()
.has_capability(TransactionCapabilityFlag::DestroyFoundryOutputs)
{
// TODO: is this correct?
return Err(StateTransitionError::UnsupportedStateTransition);
}

let token_id = current_state.token_id();
let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default();
let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme;
Expand Down
11 changes: 10 additions & 1 deletion sdk/src/types/block/output/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::types::{
NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError,
StateTransitionVerifier,
},
payload::transaction::TransactionCapabilityFlag,
protocol::ProtocolParameters,
semantic::{TransactionFailureReason, ValidationContext},
unlock::Unlock,
Expand Down Expand Up @@ -456,7 +457,15 @@ impl StateTransitionVerifier for NftOutput {
Self::transition_inner(current_state, next_state)
}

fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
fn destruction(_current_state: &Self, context: &ValidationContext<'_>) -> Result<(), StateTransitionError> {
if context
.essence
.capabilities()
.has_capability(TransactionCapabilityFlag::DestroyNftOutputs)
{
// TODO: is this correct?
return Err(StateTransitionError::UnsupportedStateTransition);
}
Ok(())
}
}
Expand Down
4 changes: 3 additions & 1 deletion sdk/src/types/block/payload/transaction/essence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use derive_more::From;
use packable::PackableExt;

pub(crate) use self::regular::{ContextInputCount, InputCount, OutputCount};
pub use self::regular::{RegularTransactionEssence, RegularTransactionEssenceBuilder};
pub use self::regular::{
RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionCapabilities, TransactionCapabilityFlag,
};
use crate::types::block::Error;

/// A generic essence that can represent different types defining transaction essences.
Expand Down
113 changes: 112 additions & 1 deletion sdk/src/types/block/payload/transaction/essence/regular.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use alloc::{collections::BTreeSet, vec::Vec};

use derive_more::Deref;
use hashbrown::HashSet;
use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable};

Expand Down Expand Up @@ -30,6 +31,7 @@ pub struct RegularTransactionEssenceBuilder {
inputs_commitment: InputsCommitment,
outputs: Vec<Output>,
allotments: BTreeSet<ManaAllotment>,
capabilities: TransactionCapabilities,
payload: OptionalPayload,
creation_slot: Option<SlotIndex>,
}
Expand All @@ -44,6 +46,7 @@ impl RegularTransactionEssenceBuilder {
inputs_commitment,
outputs: Vec::new(),
allotments: BTreeSet::new(),
capabilities: Default::default(),
payload: OptionalPayload::default(),
creation_slot: None,
}
Expand Down Expand Up @@ -109,6 +112,11 @@ impl RegularTransactionEssenceBuilder {
self
}

pub fn with_capabilities(mut self, capabilities: impl Into<TransactionCapabilities>) -> Self {
self.capabilities = capabilities.into();
self
}

/// Finishes a [`RegularTransactionEssenceBuilder`] into a [`RegularTransactionEssence`].
pub fn finish_with_params<'a>(
self,
Expand Down Expand Up @@ -182,6 +190,7 @@ impl RegularTransactionEssenceBuilder {
inputs_commitment: self.inputs_commitment,
outputs,
allotments,
capabilities: self.capabilities,
payload: self.payload,
})
}
Expand Down Expand Up @@ -220,6 +229,7 @@ pub struct RegularTransactionEssence {
#[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidOutputCount(p.into())))]
outputs: BoxedSlicePrefix<Output, OutputCount>,
allotments: ManaAllotments,
capabilities: TransactionCapabilities,
#[packable(verify_with = verify_payload_packable)]
payload: OptionalPayload,
}
Expand Down Expand Up @@ -268,6 +278,10 @@ impl RegularTransactionEssence {
&self.allotments
}

pub fn capabilities(&self) -> TransactionCapabilities {
self.capabilities
}

/// Returns the optional payload of a [`RegularTransactionEssence`].
pub fn payload(&self) -> Option<&Payload> {
self.payload.as_ref()
Expand Down Expand Up @@ -419,6 +433,99 @@ fn verify_payload_packable<const VERIFY: bool>(
Ok(())
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[non_exhaustive]
#[repr(u8)]
pub enum TransactionCapabilityFlag {
BurnNativeTokens = 0b00000001,
BurnMana = 0b00000010,
DestroyAccountOutputs = 0b00000100,
DestroyFoundryOutputs = 0b00001000,
DestroyNftOutputs = 0b00010000,
}

impl TransactionCapabilityFlag {
pub fn all() -> impl Iterator<Item = Self> {
[
Self::BurnNativeTokens,
Self::BurnMana,
Self::DestroyAccountOutputs,
Self::DestroyFoundryOutputs,
Self::DestroyNftOutputs,
]
.into_iter()
}
}

#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Packable)]
#[repr(transparent)]
pub struct TransactionCapabilities(u8);
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved

impl TransactionCapabilities {
pub const NONE: Self = Self(0);
pub const ALL: Self = Self(0b00011111);

pub fn is_all(&self) -> bool {
Self::ALL.eq(self)
}

pub fn is_none(&self) -> bool {
Self::NONE.eq(self)
}

pub fn set_all(&mut self) -> &mut Self {
*self = Self::ALL;
self
}

pub fn set_none(&mut self) -> &mut Self {
*self = Self::NONE;
self
}

pub fn add_capability(&mut self, flag: TransactionCapabilityFlag) -> &mut Self {
self.0 |= flag as u8;
self
}

pub fn add_capabilities(&mut self, flags: impl IntoIterator<Item = TransactionCapabilityFlag>) -> &mut Self {
for flag in flags {
self.add_capability(flag);
}
self
}

pub fn with_capabilities(mut self, flags: impl IntoIterator<Item = TransactionCapabilityFlag>) -> Self {
for flag in flags {
self.add_capability(flag);
}
self
}

pub fn set_capabilities(&mut self, flags: impl IntoIterator<Item = TransactionCapabilityFlag>) -> &mut Self {
*self = Self::default().with_capabilities(flags);
self
}

pub fn has_capability(&self, flag: TransactionCapabilityFlag) -> bool {
self.0 & flag as u8 == flag as u8
}

pub fn has_capabilities(&self, flags: impl IntoIterator<Item = TransactionCapabilityFlag>) -> bool {
flags.into_iter().all(|flag| self.has_capability(flag))
}

pub fn split(self) -> impl Iterator<Item = TransactionCapabilityFlag> {
TransactionCapabilityFlag::all().filter(move |f| self.has_capability(*f))
}
}

impl<I: IntoIterator<Item = TransactionCapabilityFlag>> From<I> for TransactionCapabilities {
fn from(value: I) -> Self {
Self::default().with_capabilities(value)
}
}

#[cfg(feature = "serde")]
pub(crate) mod dto {
use alloc::string::{String, ToString};
Expand All @@ -445,6 +552,8 @@ pub(crate) mod dto {
pub inputs_commitment: String,
pub outputs: Vec<OutputDto>,
pub allotments: Vec<ManaAllotmentDto>,
// TODO: what does the API want?
pub capabilities: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload: Option<PayloadDto>,
}
Expand All @@ -460,6 +569,7 @@ pub(crate) mod dto {
inputs_commitment: value.inputs_commitment().to_string(),
outputs: value.outputs().iter().map(Into::into).collect(),
allotments: value.mana_allotments().iter().map(Into::into).collect(),
capabilities: value.capabilities().0,
payload: match value.payload() {
Some(p @ Payload::TaggedData(_)) => Some(p.into()),
Some(_) => unimplemented!(),
Expand Down Expand Up @@ -494,7 +604,8 @@ pub(crate) mod dto {
.with_context_inputs(dto.context_inputs)
.with_inputs(dto.inputs)
.with_outputs(outputs)
.with_mana_allotments(mana_allotments);
.with_mana_allotments(mana_allotments)
.with_capabilities(TransactionCapabilities(dto.capabilities));

builder = if let Some(p) = dto.payload {
if let PayloadDto::TaggedData(i) = p {
Expand Down
5 changes: 4 additions & 1 deletion sdk/src/types/block/payload/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use packable::{error::UnpackError, packer::Packer, unpacker::Unpacker, Packable,

pub(crate) use self::essence::{ContextInputCount, InputCount, OutputCount};
pub use self::{
essence::{RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionEssence},
essence::{
RegularTransactionEssence, RegularTransactionEssenceBuilder, TransactionCapabilities,
TransactionCapabilityFlag, TransactionEssence,
},
transaction_id::TransactionId,
};
use crate::types::block::{protocol::ProtocolParameters, unlock::Unlocks, Error};
Expand Down
Loading
Loading