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

Move address caps verification to syntactic #1804

Merged
4 changes: 3 additions & 1 deletion sdk/src/types/block/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use primitive_types::U256;

use super::slot::EpochIndex;
use crate::types::block::{
address::WeightedAddressCount,
address::{AddressCapabilityFlag, WeightedAddressCount},
context_input::RewardContextInputIndex,
input::UtxoInput,
mana::ManaAllotmentCount,
Expand Down Expand Up @@ -201,6 +201,7 @@ pub enum Error {
target: EpochIndex,
},
TrailingCapabilityBytes,
RestrictedAddressCapability(AddressCapabilityFlag),
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -432,6 +433,7 @@ impl fmt::Display for Error {
write!(f, "invalid epoch delta: created {created}, target {target}")
}
Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"),
Self::RestrictedAddressCapability(cap) => write!(f, "restricted address capability: {cap:?}"),
}
}
}
Expand Down
20 changes: 13 additions & 7 deletions sdk/src/types/block/output/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use crate::types::block::{
address::{AccountAddress, Address},
output::{
feature::{verify_allowed_features, Feature, FeatureFlags, Features},
unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
unlock_condition::{
verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags,
UnlockConditions,
},
ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
Expand Down Expand Up @@ -217,6 +220,12 @@ impl AccountOutputBuilder {

let features = Features::from_set(self.features)?;

verify_restricted_addresses(
&unlock_conditions,
AccountOutput::KIND,
features.native_token(),
self.mana,
)?;
verify_allowed_features(&features, AccountOutput::ALLOWED_FEATURES)?;

let immutable_features = Features::from_set(self.immutable_features)?;
Expand Down Expand Up @@ -493,6 +502,8 @@ impl Packable for AccountOutput {
let features = Features::unpack::<_, VERIFY>(unpacker, &())?;

if VERIFY {
verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana)
.map_err(UnpackError::Packable)?;
verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
}

Expand Down Expand Up @@ -611,12 +622,7 @@ mod tests {

use super::*;
use crate::types::block::{
output::account::dto::AccountOutputDto,
protocol::protocol_parameters,
rand::output::{
feature::rand_allowed_features, rand_account_id, rand_account_output,
unlock_condition::rand_address_unlock_condition_different_from_account_id,
},
output::account::dto::AccountOutputDto, protocol::protocol_parameters, rand::output::rand_account_output,
};

#[test]
Expand Down
24 changes: 13 additions & 11 deletions sdk/src/types/block/output/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use crate::types::block::{
address::{Address, AnchorAddress},
output::{
feature::{verify_allowed_features, Feature, FeatureFlags, Features},
unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
unlock_condition::{
verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags,
UnlockConditions,
},
ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
Expand Down Expand Up @@ -248,6 +251,12 @@ impl AnchorOutputBuilder {

let features = Features::from_set(self.features)?;

verify_restricted_addresses(
&unlock_conditions,
AnchorOutput::KIND,
features.native_token(),
self.mana,
)?;
verify_allowed_features(&features, AnchorOutput::ALLOWED_FEATURES)?;

let immutable_features = Features::from_set(self.immutable_features)?;
Expand Down Expand Up @@ -549,6 +558,8 @@ impl Packable for AnchorOutput {
let features = Features::unpack::<_, VERIFY>(unpacker, &())?;

if VERIFY {
verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana)
.map_err(UnpackError::Packable)?;
verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
}

Expand Down Expand Up @@ -675,16 +686,7 @@ mod dto {
mod tests {
use super::*;
use crate::types::block::{
output::anchor::dto::AnchorOutputDto,
protocol::protocol_parameters,
rand::output::{
feature::rand_allowed_features,
rand_anchor_id, rand_anchor_output,
unlock_condition::{
rand_governor_address_unlock_condition_different_from,
rand_state_controller_address_unlock_condition_different_from,
},
},
output::anchor::dto::AnchorOutputDto, protocol::protocol_parameters, rand::output::rand_anchor_output,
};

#[test]
Expand Down
20 changes: 18 additions & 2 deletions sdk/src/types/block/output/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::types::block::{
output::{
feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature},
unlock_condition::{
verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition,
UnlockCondition, UnlockConditionFlags, UnlockConditions,
verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition,
StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions,
},
MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters,
},
Expand Down Expand Up @@ -192,6 +192,12 @@ impl BasicOutputBuilder {

let features = Features::from_set(self.features)?;

verify_restricted_addresses(
&unlock_conditions,
BasicOutput::KIND,
features.native_token(),
self.mana,
)?;
verify_features::<true>(&features)?;

let mut output = BasicOutput {
Expand Down Expand Up @@ -230,6 +236,7 @@ impl From<&BasicOutput> for BasicOutputBuilder {
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
#[packable(unpack_error = Error)]
#[packable(unpack_visitor = ProtocolParameters)]
#[packable(verify_with = verify_basic_output)]
pub struct BasicOutput {
/// Amount of IOTA coins held by the output.
amount: u64,
Expand Down Expand Up @@ -392,6 +399,15 @@ fn verify_features_packable<const VERIFY: bool>(features: &Features, _: &Protoco
verify_features::<VERIFY>(features)
}

fn verify_basic_output<const VERIFY: bool>(output: &BasicOutput, _: &ProtocolParameters) -> Result<(), Error> {
verify_restricted_addresses(
output.unlock_conditions(),
BasicOutput::KIND,
output.features.native_token(),
output.mana,
)
}

#[cfg(feature = "serde")]
mod dto {
use alloc::vec::Vec;
Expand Down
14 changes: 13 additions & 1 deletion sdk/src/types/block/output/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use crate::types::block::{
address::{AccountAddress, Address},
output::{
chain_id::ChainId,
unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
unlock_condition::{
verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags,
UnlockConditions,
},
MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters,
},
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
Expand Down Expand Up @@ -172,6 +175,7 @@ impl DelegationOutputBuilder {
let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?;

verify_unlock_conditions::<true>(&unlock_conditions)?;
verify_restricted_addresses(&unlock_conditions, DelegationOutput::KIND, None, 0)?;

let mut output = DelegationOutput {
amount: 0,
Expand Down Expand Up @@ -215,6 +219,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder {
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
#[packable(unpack_error = Error)]
#[packable(unpack_visitor = ProtocolParameters)]
#[packable(verify_with = verify_delegation_output)]
pub struct DelegationOutput {
/// Amount of IOTA coins held by the output.
amount: u64,
Expand Down Expand Up @@ -392,6 +397,13 @@ fn verify_unlock_conditions_packable<const VERIFY: bool>(
verify_unlock_conditions::<VERIFY>(unlock_conditions)
}

fn verify_delegation_output<const VERIFY: bool>(
output: &DelegationOutput,
_: &ProtocolParameters,
) -> Result<(), Error> {
verify_restricted_addresses(output.unlock_conditions(), DelegationOutput::KIND, None, 0)
}

#[cfg(feature = "serde")]
mod dto {
use alloc::vec::Vec;
Expand Down
7 changes: 5 additions & 2 deletions sdk/src/types/block/output/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::types::block::{
output::{
feature::{verify_allowed_features, Feature, FeatureFlags, Features},
unlock_condition::{
verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition,
UnlockCondition, UnlockConditionFlags, UnlockConditions,
verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition,
StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions,
},
BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore,
StorageScoreParameters,
Expand Down Expand Up @@ -256,6 +256,7 @@ impl NftOutputBuilder {

let features = Features::from_set(self.features)?;

verify_restricted_addresses(&unlock_conditions, NftOutput::KIND, features.native_token(), self.mana)?;
verify_allowed_features(&features, NftOutput::ALLOWED_FEATURES)?;

let immutable_features = Features::from_set(self.immutable_features)?;
Expand Down Expand Up @@ -469,6 +470,8 @@ impl Packable for NftOutput {
let features = Features::unpack::<_, VERIFY>(unpacker, &())?;

if VERIFY {
verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana)
.map_err(UnpackError::Packable)?;
verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
}

Expand Down
90 changes: 88 additions & 2 deletions sdk/src/types/block/output/unlock_condition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ pub use self::{
storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition,
};
use crate::types::block::{
address::Address,
output::{StorageScore, StorageScoreParameters},
address::{Address, AddressCapabilityFlag, RestrictedAddress},
output::{
feature::NativeTokenFeature, AccountOutput, AnchorOutput, DelegationOutput, NftOutput, StorageScore,
StorageScoreParameters,
},
protocol::{CommittableAgeRange, ProtocolParameters, WorkScore},
slot::SlotIndex,
Error,
Expand Down Expand Up @@ -314,6 +317,22 @@ impl UnlockConditions {

Ok(address)
}

/// Returns an iterator over all addresses except StorageDepositReturn address.
pub fn addresses(&self) -> impl Iterator<Item = &Address> {
self.iter().filter_map(|uc| match uc {
UnlockCondition::Address(uc) => Some(uc.address()),
UnlockCondition::Expiration(uc) => Some(uc.return_address()),
UnlockCondition::StateControllerAddress(uc) => Some(uc.address()),
UnlockCondition::GovernorAddress(uc) => Some(uc.address()),
_ => None,
})
}

/// Returns an iterator over all restricted addresses.
pub fn restricted_addresses(&self) -> impl Iterator<Item = &RestrictedAddress> {
self.addresses().filter_map(Address::as_restricted_opt)
}
}

impl StorageScore for UnlockConditions {
Expand Down Expand Up @@ -355,6 +374,73 @@ pub(crate) fn verify_allowed_unlock_conditions(
Ok(())
}

pub(crate) fn verify_restricted_addresses(
unlock_conditions: &UnlockConditions,
output_kind: u8,
native_token: Option<&NativeTokenFeature>,
mana: u64,
) -> Result<(), Error> {
let addresses = unlock_conditions.restricted_addresses();

for address in addresses {
if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) {
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::OutputsWithNativeTokens,
));
}

if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) {
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::OutputsWithMana,
));
}

if unlock_conditions.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock)
{
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::OutputsWithTimelock,
));
}

if unlock_conditions.expiration().is_some()
&& !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration)
{
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::OutputsWithExpiration,
));
}

if unlock_conditions.storage_deposit_return().is_some()
&& !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn)
{
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::OutputsWithStorageDepositReturn,
));
}

match output_kind {
AccountOutput::KIND if !address.has_capability(AddressCapabilityFlag::AccountOutputs) => {
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::AccountOutputs,
));
}
AnchorOutput::KIND if !address.has_capability(AddressCapabilityFlag::AnchorOutputs) => {
return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::AnchorOutputs));
}
NftOutput::KIND if !address.has_capability(AddressCapabilityFlag::NftOutputs) => {
return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::NftOutputs));
}
DelegationOutput::KIND if !address.has_capability(AddressCapabilityFlag::DelegationOutputs) => {
return Err(Error::RestrictedAddressCapability(
AddressCapabilityFlag::DelegationOutputs,
));
}
_ => {}
}
}
Ok(())
}

#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
Expand Down
Loading