From 7a3b642604707eac8d633203ea0597c1c22a0c18 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Thu, 11 Jun 2026 15:06:45 -0300 Subject: [PATCH] Add secondary key permissions check in transfer_funds --- pallets/portfolio/src/lib.rs | 10 ++++- .../src/settlement_pallet/transfer_funds.rs | 37 +++++++++++++++++-- pallets/settlement/src/lib.rs | 6 ++- primitives/src/traits.rs | 14 +++++-- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/pallets/portfolio/src/lib.rs b/pallets/portfolio/src/lib.rs index b14017dbcf..ff75ae806f 100644 --- a/pallets/portfolio/src/lib.rs +++ b/pallets/portfolio/src/lib.rs @@ -1213,11 +1213,19 @@ impl Pallet { } } -impl PortfolioFnTrait for Pallet { +impl PortfolioFnTrait for Pallet { fn ensure_portfolio_custody( portfolio: &PortfolioId, custodian: IdentityId, ) -> Result<(), DispatchError> { Self::ensure_portfolio_custody(portfolio, custodian) } + + fn ensure_portfolio_custody_and_permission( + portfolio: &PortfolioId, + custodian: IdentityId, + secondary_key: Option<&SecondaryKey>, + ) -> Result<(), DispatchError> { + Self::ensure_portfolio_custody_and_permission(portfolio, custodian, secondary_key) + } } diff --git a/pallets/runtime/tests/src/settlement_pallet/transfer_funds.rs b/pallets/runtime/tests/src/settlement_pallet/transfer_funds.rs index f5489898eb..0068c18f84 100644 --- a/pallets/runtime/tests/src/settlement_pallet/transfer_funds.rs +++ b/pallets/runtime/tests/src/settlement_pallet/transfer_funds.rs @@ -4,10 +4,12 @@ use sp_keyring::Sr25519Keyring; use pallet_asset::{Allowances, BalanceOf}; use polymesh_primitives::asset::{AssetHolder, AssetHolderKind, AssetType, NonFungibleType}; use polymesh_primitives::nft::{NFTId, NFTOwnerStatus}; -use polymesh_primitives::{Balance, Fund, FundDescription, NFTs, PortfolioId}; +use polymesh_primitives::{ + Balance, Fund, FundDescription, NFTs, Permissions, PortfolioId, SubsetRestriction, +}; -use crate::asset_pallet::setup::ISSUE_AMOUNT; -use crate::storage::User; +use crate::asset_pallet::setup::{create_and_issue_sample_asset, ISSUE_AMOUNT}; +use crate::storage::{add_secondary_key_with_perms, User}; use crate::{ExtBuilder, TestStorage}; type Asset = pallet_asset::Pallet; @@ -512,3 +514,32 @@ fn nft_reject_frozen_asset() { ); }); } + +#[test] +fn reject_secondary_key_transfer_without_permission() { + ExtBuilder::default().build().execute_with(|| { + let alice = User::new(Sr25519Keyring::Alice); + let charlie = User::new_with(alice.did, Sr25519Keyring::Charlie); + + // Add charlie as secondary key for alice, but without portfolio permissions. + let mut permissions = Permissions::default(); + permissions.portfolio = SubsetRestriction::empty(); + add_secondary_key_with_perms(alice.did, charlie.acc(), permissions); + + let asset_id = create_and_issue_sample_asset(&alice); + + // charlie tries to transfer from alice's default portfolio without portfolio permissions + let from = AssetHolder::Portfolio(PortfolioId::default_portfolio(alice.did)); + let to = AssetHolder::Account(charlie.acc()); + + assert_noop!( + Settlement::transfer_funds( + charlie.origin(), + Some(from), + to, + fungible_fund(asset_id, 100) + ), + PortfolioError::SecondaryKeyNotAuthorizedForPortfolio + ); + }); +} diff --git a/pallets/settlement/src/lib.rs b/pallets/settlement/src/lib.rs index dcfc010b51..82b5d83168 100644 --- a/pallets/settlement/src/lib.rs +++ b/pallets/settlement/src/lib.rs @@ -1692,7 +1692,11 @@ impl Pallet { } } AssetHolder::Portfolio(ref portfolio_id) => { - T::PortfolioFn::ensure_portfolio_custody(portfolio_id, caller_data.primary_did)?; + T::PortfolioFn::ensure_portfolio_custody_and_permission( + portfolio_id, + caller_data.primary_did, + caller_data.secondary_key.as_ref(), + )?; } } Ok(()) diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 2cd9c5e8e9..88addd22f1 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -20,7 +20,7 @@ use sp_runtime::transaction_validity::InvalidTransaction; use crate::asset::AssetId; use crate::asset_metadata::AssetMetadataKey; use crate::transaction_payment::CallPaymentInfo; -use crate::{Balance, IdentityId, NFTId, PortfolioId, WeightMeter}; +use crate::{Balance, IdentityId, NFTId, PortfolioId, SecondaryKey, WeightMeter}; #[cfg(feature = "runtime-benchmarks")] use crate::{asset::NonFungibleType, NFTCollectionKeys}; @@ -100,18 +100,26 @@ pub trait SubsidiserTrait { ); } -pub trait PortfolioFnTrait { +pub trait PortfolioFnTrait { /// Returns `Ok(())` if `custodian` has custody over the portfolio. /// The portfolio owner is the default custodian when none is assigned. fn ensure_portfolio_custody( portfolio: &PortfolioId, custodian: IdentityId, ) -> Result<(), DispatchError>; + + /// Ensures that the portfolio's custody is with the provided identity and the secondary key + /// has the relevant portfolio permission. + fn ensure_portfolio_custody_and_permission( + portfolio: &PortfolioId, + custodian: IdentityId, + secondary_key: Option<&SecondaryKey>, + ) -> Result<(), DispatchError>; } /// Supertrait config for pallets that need portfolio custody queries. pub trait PortfolioFnConfig: frame_system::Config { - type PortfolioFn: PortfolioFnTrait; + type PortfolioFn: PortfolioFnTrait; } pub trait ComplianceFnConfig {