From 7b1254a9ccfc1c5cc223ca758dfeb64e38037852 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 10 Aug 2026 17:03:57 -0300 Subject: [PATCH 1/8] Add canSend, canReceive, setFrozenTokens, getFrozenTokens --- pallets/asset/src/benchmarking.rs | 65 ++++++++ pallets/asset/src/lib.rs | 149 ++++++++++++++++-- pallets/compliance-manager/src/lib.rs | 39 +++++ pallets/portfolio/src/lib.rs | 30 ++++ pallets/precompiles/src/interface/erc7943.rs | 149 ++++++++++++++++++ pallets/precompiles/src/interface/mod.rs | 9 ++ pallets/weights/src/pallet_asset.rs | 52 ++++++ .../src/interfaces/FungibleAssetStub.sol | 66 +++++++- primitives/src/traits.rs | 8 + 9 files changed, 551 insertions(+), 16 deletions(-) diff --git a/pallets/asset/src/benchmarking.rs b/pallets/asset/src/benchmarking.rs index 63045c78f2..e131b6aef6 100644 --- a/pallets/asset/src/benchmarking.rs +++ b/pallets/asset/src/benchmarking.rs @@ -945,4 +945,69 @@ benchmarks! { .is_empty() ); } + + set_frozen_tokens { + let alice = UserBuilder::::default().generate_did().build("Alice"); + let asset_id = create_sample_asset::(&alice, true); + }: _(alice.origin, asset_id, AssetHolder::try_from(alice.account().encode()).unwrap(), ONE_UNIT) + + get_holders_frozen_balance { + let alice = UserBuilder::::default().generate_did().build("Alice"); + let asset_id = create_sample_asset::(&alice, true); + let alice_asset_holder = AssetHolder::try_from(alice.account().encode()).unwrap(); + + Pallet::::set_frozen_tokens( + alice.origin.into(), + asset_id, + alice_asset_holder.clone(), + ONE_UNIT + ) + .unwrap(); + }: { + assert_eq!( + Pallet::::get_holders_frozen_balance( + &alice_asset_holder, + &asset_id, + ), + ONE_UNIT + ); + } + + transfer_is_allowed_for_holder_best_case { + // No statistics or compliance rules are set + let alice = UserBuilder::::default().generate_did().build("Alice"); + let bob = UserBuilder::::default().generate_did().build("Bob"); + let mut weight_meter = WeightMeter::max_limit_no_minimum(); + + let (sender, receiver, _, asset_id) = + setup_asset_transfer::(&alice, &bob, None, None, true, true, 0, true, true); + }: { + assert!( + Pallet::::transfer_is_allowed_for_holder( + &sender, + &asset_id, + true, + &mut weight_meter + ) + ); + } + + transfer_is_allowed_for_holder_worst_case { + // Max Statistics and Compliance rules are set + let alice = UserBuilder::::default().generate_did().build("Alice"); + let bob = UserBuilder::::default().generate_did().build("Bob"); + let mut weight_meter = WeightMeter::max_limit_no_minimum(); + + let (sender, receiver, _, asset_id) = + setup_asset_transfer::(&alice, &bob, None, None, false, false, 0, true, true); + }: { + assert!( + Pallet::::transfer_is_allowed_for_holder( + &sender, + &asset_id, + true, + &mut weight_meter + ) + ); + } } diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index 8b6122525e..cf87f5c8b7 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -359,6 +359,13 @@ pub mod pallet { amount_spent: Balance, remaining_allowance: Balance, }, + /// The asset's frozen balance was set for an asset holder. + FrozenBalanceSet { + caller_did: IdentityId, + asset_holder: AssetHolder, + asset_id: AssetId, + frozen_balance: Balance, + }, } /// Map each [`Ticker`] to its registration details ([`TickerRegistration`]). @@ -627,6 +634,18 @@ pub mod pallet { ValueQuery, >; + /// Tracks the amount of frozen tokens for each asset held by the account. + #[pallet::storage] + pub type FrozenBalance = StorageDoubleMap< + _, + Twox64Concat, + AccountId32, + Blake2_128Concat, + AssetId, + Balance, + ValueQuery, + >; + /// Storage version. #[pallet::storage] pub type StorageVersion = StorageValue<_, Version, ValueQuery>; @@ -1802,6 +1821,18 @@ pub mod pallet { Ok(()) } + + /// Freezes `amount` of `asset_id` tokens from `account`. + #[pallet::call_index(38)] + #[pallet::weight(::WeightInfo::set_frozen_tokens())] + pub fn set_frozen_tokens( + origin: OriginFor, + asset_id: AssetId, + asset_holder: AssetHolder, + amount: Balance, + ) -> DispatchResult { + Self::base_set_frozen_tokens(origin, asset_id, asset_holder, amount) + } } #[pallet::error] @@ -1959,6 +1990,10 @@ pub mod pallet { fn issue_without_statistics() -> Weight; fn asset_transfer_report_best_case() -> Weight; fn asset_transfer_report_worst_case() -> Weight; + fn set_frozen_tokens() -> Weight; + fn get_holders_frozen_balance() -> Weight; + fn transfer_is_allowed_for_holder_best_case() -> Weight; + fn transfer_is_allowed_for_holder_worst_case() -> Weight; } } @@ -2933,6 +2968,54 @@ impl Pallet { Ok(PostDispatchInfo::from(Some(weight_meter.consumed()))) } + + /// Sets the frozen transfer amount for an account on a given asset. + fn base_set_frozen_tokens( + origin: T::RuntimeOrigin, + asset_id: AssetId, + asset_holder: AssetHolder, + amount: Balance, + ) -> DispatchResult { + let caller_did = ExternalAgents::::ensure_perms(origin, &asset_id)?; + + let asset_details = Self::try_get_asset_details(&asset_id)?; + ensure!( + asset_details.asset_type.is_fungible(), + Error::::UnexpectedNonFungibleToken + ); + + match &asset_holder { + AssetHolder::Account(account) => { + if amount.is_zero() { + FrozenBalance::::remove(account, &asset_id); + } else { + FrozenBalance::::insert(account, asset_id, amount); + } + + Self::deposit_event(Event::FrozenBalanceSet { + caller_did, + asset_holder, + asset_id, + frozen_balance: amount, + }); + } + AssetHolder::Portfolio(portfolio) => { + pallet_portfolio::Pallet::::set_portfolio_frozen_balance( + portfolio.clone(), + asset_id, + amount, + ); + Self::deposit_event(Event::FrozenBalanceSet { + caller_did, + asset_holder, + asset_id, + frozen_balance: amount, + }); + } + } + + Ok(()) + } } //========================================================================== @@ -3694,16 +3777,17 @@ impl Pallet { let current_balance = Self::get_holders_balance(holder, asset_id); let locked_balance = Self::get_holders_locked_balance(holder, asset_id); + let frozen_balance = Self::get_holders_frozen_balance(holder, asset_id); + + let unavailable_balance = locked_balance.saturating_add(frozen_balance); + let available_balance = current_balance.saturating_sub(unavailable_balance); + + ensure!(available_balance >= value, Error::::InsufficientBalance); let final_balance = current_balance .checked_sub(value) .ok_or(Error::::InsufficientBalance)?; - ensure!( - final_balance >= locked_balance, - Error::::InsufficientBalance - ); - Ok(final_balance) } @@ -3809,6 +3893,54 @@ impl Pallet { } Ok(()) } + + /// Returns the frozen balance for `asset_holder` and `asset_id`. + pub fn get_holders_frozen_balance(asset_holder: &AssetHolder, asset_id: &AssetId) -> Balance { + match asset_holder { + AssetHolder::Portfolio(portfolio_id) => { + pallet_portfolio::Pallet::::get_portfolio_frozen_balance(portfolio_id, asset_id) + } + AssetHolder::Account(account_id) => FrozenBalance::::get(account_id, asset_id), + } + } + + /// Returns `true` if the asset holder can send/receive the asset based on the asset's compliance rules. + /// Note: No balance and no statistics checks are performed. + pub fn transfer_is_allowed_for_holder( + asset_holder: &AssetHolder, + asset_id: &AssetId, + holder_is_the_sender: bool, + weight_meter: &mut WeightMeter, + ) -> bool { + let asset_details = { + match Self::try_get_asset_details(&asset_id) { + Ok(details) => details, + Err(_) => return false, + } + }; + + if !asset_details.asset_type.is_fungible() { + return false; + } + + let holder_did = { + match pallet_identity::Pallet::::asset_holder_did(&asset_holder) { + Ok(did) => did, + Err(_) => return false, + } + }; + + if Self::ensure_asset_is_not_frozen(&asset_id).is_err() { + return false; + } + + T::ComplianceManager::is_holder_compliant( + holder_did, + asset_id, + holder_is_the_sender, + weight_meter, + ) + } } //========================================================================== @@ -4176,11 +4308,8 @@ impl Pallet { Error::::InsufficientBalance ); - match &receiver { - AssetHolder::Portfolio(receiver_pid) => { - PortfolioPallet::::ensure_portfolio_validity(receiver_pid)? - } - AssetHolder::Account(_) => {} + if let AssetHolder::Portfolio(receiver_pid) = &receiver { + PortfolioPallet::::ensure_portfolio_validity(receiver_pid)?; } Self::ensure_sufficient_balance(&sender, &asset_id, transfer_value)?; diff --git a/pallets/compliance-manager/src/lib.rs b/pallets/compliance-manager/src/lib.rs index 39189339b6..63f821bf05 100644 --- a/pallets/compliance-manager/src/lib.rs +++ b/pallets/compliance-manager/src/lib.rs @@ -889,6 +889,45 @@ impl ComplianceFnConfig for Pallet { ) } + fn is_holder_compliant( + holder_did: IdentityId, + asset_id: &AssetId, + holder_is_the_sender: bool, + weight_meter: &mut WeightMeter, + ) -> bool { + let asset_compliance = AssetCompliances::::get(asset_id); + + if asset_compliance.paused || asset_compliance.requirements.is_empty() { + return true; + } + + for requirement in &asset_compliance.requirements { + let req_conditions = { + if holder_is_the_sender { + &requirement.sender_conditions + } else { + &requirement.receiver_conditions + } + }; + + match Self::are_all_conditions_satisfied( + asset_id, + holder_did, + req_conditions, + weight_meter, + ) { + Ok(condition_satisfied) => { + if condition_satisfied { + return true; + } + } + Err(_) => return false, + } + } + + false + } + #[cfg(feature = "runtime-benchmarks")] fn setup_asset_compliance( caller_did: IdentityId, diff --git a/pallets/portfolio/src/lib.rs b/pallets/portfolio/src/lib.rs index 9bb5f64fc0..f33fcd6406 100644 --- a/pallets/portfolio/src/lib.rs +++ b/pallets/portfolio/src/lib.rs @@ -322,6 +322,18 @@ pub mod pallet { pub type AllowedCustodians = StorageDoubleMap<_, Identity, IdentityId, Identity, IdentityId, bool, ValueQuery>; + /// Amount of assets frozen in a portfolio. + #[pallet::storage] + pub type PortfolioFrozenAssets = StorageDoubleMap< + _, + Twox64Concat, + PortfolioId, + Blake2_128Concat, + AssetId, + Balance, + ValueQuery, + >; + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { @@ -1197,6 +1209,24 @@ impl Pallet { Self::set_portfolio_locked_balance(portfolio, asset_id, current_locked - amount); Ok(()) } + + /// Sets the frozen balance of `asset_id` in `portfolio` to `new_frozen_balance`. + pub fn set_portfolio_frozen_balance( + portfolio: PortfolioId, + asset_id: AssetId, + new_frozen_balance: Balance, + ) { + if new_frozen_balance.is_zero() { + PortfolioFrozenAssets::::remove(&portfolio, &asset_id); + } else { + PortfolioFrozenAssets::::insert(portfolio, asset_id, new_frozen_balance); + } + } + + /// Returns the frozen balance of `asset_id` in `portfolio`. + pub fn get_portfolio_frozen_balance(portfolio: &PortfolioId, asset_id: &AssetId) -> Balance { + PortfolioFrozenAssets::::get(portfolio, asset_id) + } } impl PortfolioFnTrait for Pallet { diff --git a/pallets/precompiles/src/interface/erc7943.rs b/pallets/precompiles/src/interface/erc7943.rs index 7cf36a742b..ddb87415c2 100644 --- a/pallets/precompiles/src/interface/erc7943.rs +++ b/pallets/precompiles/src/interface/erc7943.rs @@ -132,4 +132,153 @@ where } } } + + /// Freezes a specific amount of tokens for a given account. + pub(crate) fn set_frozen_tokens( + asset_id: AssetId, + call: &IFungibleAsset::setFrozenTokensCall, + env: &mut impl Ext, + ) -> Result, Error> { + env.charge(::WeightInfo::set_frozen_tokens())?; + + let caller = Self::caller(env)?; + let caller_acc = ::AddressMapper::to_account_id(&caller); + + let acc_to_freeze = call.account.into_array().into(); + let acc_to_freeze = + ::AddressMapper::to_account_id(&acc_to_freeze); + let acc_to_freeze = AssetHolder::try_from(acc_to_freeze.encode()).map_err(|_| { + Error::Revert(Revert { + reason: ERR_INVALID_ACCOUNT_ID.into(), + }) + })?; + + let amount = Self::to_balance(call.amount)?; + + if let Err(e) = pallet_asset::Pallet::::set_frozen_tokens( + RawOrigin::Signed(caller_acc).into(), + asset_id, + acc_to_freeze, + amount, + ) { + return Err(Self::extrinsic_error(e)); + } + + Self::deposit_event( + env, + IFungibleAssetEvents::Frozen(IFungibleAsset::Frozen { + account: call.account.into(), + amount: call.amount, + }), + )?; + + Ok(IFungibleAsset::setFrozenTokensCall::abi_encode_returns( + &true, + )) + } + + /// Returns the amount of frozen tokens for a given account. + pub(crate) fn get_frozen_tokens( + asset_id: AssetId, + call: &IFungibleAsset::getFrozenTokensCall, + env: &mut impl Ext, + ) -> Result, Error> { + env.charge(::WeightInfo::get_holders_frozen_balance())?; + + let from = call.account.into_array().into(); + let from = ::AddressMapper::to_account_id(&from); + let from = AssetHolder::try_from(from.encode()).map_err(|_| { + Error::Revert(Revert { + reason: ERR_INVALID_ACCOUNT_ID.into(), + }) + })?; + + let frozen_tokens = pallet_asset::Pallet::::get_holders_frozen_balance(&from, &asset_id); + let frozen_tokens = Self::to_u256(frozen_tokens)?; + + Ok(IFungibleAsset::getFrozenTokensCall::abi_encode_returns( + &frozen_tokens, + )) + } + + /// Returns `true` if the account is allowed to send tokens according to token rules. + pub(crate) fn can_send( + asset_id: AssetId, + call: &IFungibleAsset::canSendCall, + env: &mut impl Ext, + ) -> Result, Error> { + let transfer_is_allowed_for_holder_worst_case_weight = + ::WeightInfo::transfer_is_allowed_for_holder_worst_case(); + let charged = env.charge(transfer_is_allowed_for_holder_worst_case_weight)?; + + let sender_acc = call.account.into_array().into(); + let sender_acc = ::AddressMapper::to_account_id(&sender_acc); + let sender_acc = AssetHolder::try_from(sender_acc.encode()).map_err(|_| { + Error::Revert(Revert { + reason: ERR_INVALID_ACCOUNT_ID.into(), + }) + })?; + + let mut weight_meter = WeightMeter::max_limit_no_minimum(); + let allowed = pallet_asset::Pallet::::transfer_is_allowed_for_holder( + &sender_acc, + &asset_id, + true, + &mut weight_meter, + ); + + let transfer_is_allowed_weight = + ::WeightInfo::transfer_is_allowed_for_holder_best_case(); + let compliance_weight = weight_meter.consumed(); + let real_consumed_weight = transfer_is_allowed_weight.saturating_add(compliance_weight); + + if real_consumed_weight.ref_time() + < transfer_is_allowed_for_holder_worst_case_weight.ref_time() + { + env.adjust_gas(charged, real_consumed_weight); + } + + Ok(IFungibleAsset::canSendCall::abi_encode_returns(&allowed)) + } + + /// Returns `true` if the account is allowed to receive tokens according to token rules. + pub(crate) fn can_receive( + asset_id: AssetId, + call: &IFungibleAsset::canReceiveCall, + env: &mut impl Ext, + ) -> Result, Error> { + let transfer_is_allowed_for_holder_worst_case_weight = + ::WeightInfo::transfer_is_allowed_for_holder_worst_case(); + let charged = env.charge(transfer_is_allowed_for_holder_worst_case_weight)?; + + let receiver_acc = call.account.into_array().into(); + let receiver_acc = + ::AddressMapper::to_account_id(&receiver_acc); + let receiver_acc = AssetHolder::try_from(receiver_acc.encode()).map_err(|_| { + Error::Revert(Revert { + reason: ERR_INVALID_ACCOUNT_ID.into(), + }) + })?; + + let mut weight_meter = WeightMeter::max_limit_no_minimum(); + let allowed = pallet_asset::Pallet::::transfer_is_allowed_for_holder( + &receiver_acc, + &asset_id, + false, + &mut weight_meter, + ); + + let transfer_is_allowed_weight = + ::WeightInfo::transfer_is_allowed_for_holder_best_case(); + let compliance_weight = weight_meter.consumed(); + let real_consumed_weight = transfer_is_allowed_weight.saturating_add(compliance_weight); + + if real_consumed_weight.ref_time() + < transfer_is_allowed_for_holder_worst_case_weight.ref_time() + { + env.adjust_gas(charged, real_consumed_weight); + } + + Ok(IFungibleAsset::canReceiveCall::abi_encode_returns(&allowed)) + } } diff --git a/pallets/precompiles/src/interface/mod.rs b/pallets/precompiles/src/interface/mod.rs index 6f0e4297f8..0ce58bd297 100644 --- a/pallets/precompiles/src/interface/mod.rs +++ b/pallets/precompiles/src/interface/mod.rs @@ -88,6 +88,7 @@ where | IFungibleAssetCalls::burn(_) | IFungibleAssetCalls::permit(_) | IFungibleAssetCalls::forcedTransfer(_) + | IFungibleAssetCalls::setFrozenTokens(_) if env.is_read_only() => { Err(Error::Error( @@ -122,6 +123,14 @@ where // ERC7943 functions IFungibleAssetCalls::canTransfer(call) => Self::can_transfer(asset_id, call, env), IFungibleAssetCalls::forcedTransfer(call) => Self::forced_transfer(asset_id, call, env), + IFungibleAssetCalls::canSend(call) => Self::can_send(asset_id, call, env), + IFungibleAssetCalls::canReceive(call) => Self::can_receive(asset_id, call, env), + IFungibleAssetCalls::getFrozenTokens(call) => { + Self::get_frozen_tokens(asset_id, call, env) + } + IFungibleAssetCalls::setFrozenTokens(call) => { + Self::set_frozen_tokens(asset_id, call, env) + } } } } diff --git a/pallets/weights/src/pallet_asset.rs b/pallets/weights/src/pallet_asset.rs index f6acc6931c..1ee7194a13 100644 --- a/pallets/weights/src/pallet_asset.rs +++ b/pallets/weights/src/pallet_asset.rs @@ -877,4 +877,56 @@ impl pallet_asset::WeightInfo for SubstrateWeight { // Minimum execution time: 194_666 nanoseconds. Weight::from_parts(198_650_000, 0).saturating_add(DbWeight::get().reads(25)) } + // Storage: `Identity::KeyRecords` (r:1 w:0) + // Proof: `Identity::KeyRecords` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + // Storage: `ExternalAgents::GroupOfAgent` (r:1 w:0) + // Proof: `ExternalAgents::GroupOfAgent` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + // Storage: `Permissions::CurrentPalletName` (r:1 w:0) + // Proof: `Permissions::CurrentPalletName` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Permissions::CurrentDispatchableName` (r:1 w:0) + // Proof: `Permissions::CurrentDispatchableName` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `Asset::Assets` (r:1 w:0) + // Proof: `Asset::Assets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Asset::FrozenBalance` (r:0 w:1) + // Proof: `Asset::FrozenBalance` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn set_frozen_tokens() -> Weight { + // Minimum execution time: 52_367 nanoseconds. + Weight::from_parts(54_138_000, 0) + .saturating_add(DbWeight::get().reads(5)) + .saturating_add(DbWeight::get().writes(1)) + } + // Storage: `Asset::FrozenBalance` (r:1 w:0) + // Proof: `Asset::FrozenBalance` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + fn get_holders_frozen_balance() -> Weight { + // Minimum execution time: 15_089 nanoseconds. + Weight::from_parts(15_834_000, 0).saturating_add(DbWeight::get().reads(1)) + } + // Storage: `Asset::Assets` (r:1 w:0) + // Proof: `Asset::Assets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Identity::KeyRecords` (r:1 w:0) + // Proof: `Identity::KeyRecords` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + // Storage: `Asset::Frozen` (r:1 w:0) + // Proof: `Asset::Frozen` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `ComplianceManager::AssetCompliances` (r:1 w:0) + // Proof: `ComplianceManager::AssetCompliances` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn transfer_is_allowed_for_holder_best_case() -> Weight { + // Minimum execution time: 80_396 nanoseconds. + Weight::from_parts(82_062_000, 0).saturating_add(DbWeight::get().reads(4)) + } + // Storage: `Asset::Assets` (r:1 w:0) + // Proof: `Asset::Assets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Identity::KeyRecords` (r:1 w:0) + // Proof: `Identity::KeyRecords` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + // Storage: `Asset::Frozen` (r:1 w:0) + // Proof: `Asset::Frozen` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + // Storage: `ComplianceManager::AssetCompliances` (r:1 w:0) + // Proof: `ComplianceManager::AssetCompliances` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Timestamp::Now` (r:1 w:0) + // Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + // Storage: `Identity::Claims` (r:1 w:0) + // Proof: `Identity::Claims` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn transfer_is_allowed_for_holder_worst_case() -> Weight { + // Minimum execution time: 92_745 nanoseconds. + Weight::from_parts(94_474_000, 0).saturating_add(DbWeight::get().reads(6)) + } } diff --git a/precompiles/src/interfaces/FungibleAssetStub.sol b/precompiles/src/interfaces/FungibleAssetStub.sol index 0cd924e6c2..fa4478acf5 100644 --- a/precompiles/src/interfaces/FungibleAssetStub.sol +++ b/precompiles/src/interfaces/FungibleAssetStub.sol @@ -32,12 +32,6 @@ interface IFungibleAsset { /// a call to {approve}. `value` is the new allowance. event Approval(address indexed owner, address indexed spender, uint256 value); - /// @notice Emitted when tokens are taken from one address and transferred to another. - /// @param from The address from which tokens were taken. - /// @param to The address to which seized tokens were transferred. - /// @param amount The amount seized. - event ForcedTransfer(address indexed from, address indexed to, uint256 amount); - /// @dev Returns the value of tokens in existence. function totalSupply() external view returns (uint256); @@ -162,6 +156,19 @@ interface IFungibleAsset { // ============================================================ // ERC-7943 // ============================================================ + + /// @notice Emitted when tokens are taken from one address and transferred to another. + /// @param from The address from which tokens were taken. + /// @param to The address to which seized tokens were transferred. + /// @param amount The amount seized. + event ForcedTransfer(address indexed from, address indexed to, uint256 amount); + + /// @notice Emitted when `setFrozenTokens` is called, changing the frozen `amount` of tokens for `account`. + /// @param account The address of the account whose tokens are being frozen. + /// @param amount The amount of tokens frozen after the change. + event Frozen(address indexed account, uint256 amount); + + /// @notice Checks if a transfer is possible according to token rules. /// @dev This involves compliance checks. /// @param from The address sending tokens. @@ -176,6 +183,32 @@ interface IFungibleAsset { /// @param amount The amount to force transfer. /// @return True if the transfer executed correctly. Reverts on failure. function forcedTransfer(address from, uint256 amount) external returns (bool); + + /// @notice Changes the frozen status of `amount` tokens belonging to `account`. + /// @dev Overwrites the current value, similar to an `approve` function. + /// Requires specific authorization. Frozen tokens cannot be transferred by the account. + /// @param account The address of the account whose tokens are to be frozen. + /// @param amount The amount of tokens to freeze. It can be greater than the account balance. + /// @return True if the freezing executed correctly. Reverts on failure. + function setFrozenTokens(address account, uint256 amount) external returns (bool); + + /// @notice Checks the frozen status/amount. + /// @param account The address of the account. + /// @dev It could return an amount higher than the account's balance. + /// @return The amount of tokens currently frozen for `account`. + function getFrozenTokens(address account) external view returns (uint256); + + /// @notice Checks if a specific account is allowed to send tokens according to token rules. + /// @dev This is often used for allowlist/KYC/KYB/AML checks. + /// @param account The address to check. + /// @return True if the account is allowed to send, false otherwise. + function canSend(address account) external view returns (bool); + + /// @notice Checks if a specific account is allowed to receive tokens according to token rules. + /// @dev This is often used for allowlist/KYC/KYB/AML checks. + /// @param account The address to check. + /// @return True if the account is allowed to receive, false otherwise. + function canReceive(address account) external view returns (bool); } contract FungibleAssetStub is IFungibleAsset { @@ -283,4 +316,25 @@ contract FungibleAssetStub is IFungibleAsset { amount; revert NotExecutable(); } + + function setFrozenTokens(address account, uint256 amount) external override returns (bool) { + account; + amount; + revert NotExecutable(); + } + + function getFrozenTokens(address account) external view override returns (uint256) { + account; + revert NotExecutable(); + } + + function canSend(address account) external view override returns (bool) { + account; + revert NotExecutable(); + } + + function canReceive(address account) external view override returns (bool) { + account; + revert NotExecutable(); + } } diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 88addd22f1..61d9576827 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -132,6 +132,14 @@ pub trait ComplianceFnConfig { weight_meter: &mut WeightMeter, ) -> Result; + /// Returns true if the asset holder can send/receive the asset based on the asset's compliance rules. + fn is_holder_compliant( + holder_did: IdentityId, + asset_id: &AssetId, + holder_is_the_sender: bool, + weight_meter: &mut WeightMeter, + ) -> bool; + #[cfg(feature = "runtime-benchmarks")] fn setup_asset_compliance( caler_did: IdentityId, From 411fcb7c7b215c4a151cdff5155c43e186c934ce Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 10 Aug 2026 17:14:43 -0300 Subject: [PATCH 2/8] Regenerate .bin file --- .../src/interfaces/FungibleAssetStub.bin | Bin 990 -> 1048 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/precompiles/src/interfaces/FungibleAssetStub.bin b/precompiles/src/interfaces/FungibleAssetStub.bin index 45fcf4d3c4d717a9873dae05014c90674733a79a..7490fb2805412c5d425cb16deced6d97903c1220 100644 GIT binary patch literal 1048 zcmZ`$T}TvB6y7_d*$XinkT#nlL+@@JBnQv6+Vf`F#oct zXKe`C8cW)44!h8Mah-yYO}NIloV7_RDjS}XMoMPFDg;|730fu<_VGZsL~|P1R!Wor zlu+STSfxS*=S;8H2!TYf0F&hGoMhBt=X0#4$Yy$8gcX$Bne&BN)AAKp&ovk8neIC! zU)U&OGe*Leu<9vlE)HRHz%3%RGfU6F=PL9mAsDko3aYjsvoIkUAuZOTgA^wrO*M>i zFBQ5Jkc_>i<@*~Txd>V6>j=Bh-)XH-(* z2ETmEL`uWT-afGcy&v!_6`u4`!3?WukWyxf_){{o&_IgCx$LVe`x)c1l~d7bQc2h-RC`Q!y|de}9MB)Lr% zO}juk@0e!LFG>7q832DN8Fc)?EZnptbez9 W)9a@G7m0kd`&36{uV?WFPvc)WOmyV{ literal 990 zcmZ`$T}TvB6rMYySxRKGyHr|E`Lsxs)M`+~^z%^oa@~j7XuvtQ6)RXsGwDrc)^*u_ z9_D9v$qHP0si^3opr9Y3$0&m$um{5^dnke^D5%WNy)&8*$uP`!&iQ`My#p3#$*oqD zNh?yOIb%?$YOiO~uaud9sU|DkSIM@mWRjn<(pRf5-b0uUVT6&j2wzoSe~$1x!pTFQ zcOtxr@O|&L4+wSP8Mt717AHgF52X;6CNAuXFliRMmHv%Kusam;Tkam8#Hewca44Lt zblZ;U!w7q@+rD=?icmwiyPR1lT{81V2~BH0J?Tag-*Q>^qjnNhf&$*$H{V~_ofnRBPG*h+ zvyL14FYjgO6~uky)eT0j@NlJ;S6_8Q3gQGsIf=^v?wxM=Q9B548_~6jV9i&bG0a6L ziw{&gX=b}DP;(?{8n#^@WpYOfR69>>(gj*RM@W(|d5vyR^v{l92^uu7hUQ@s!=X#5 zhp%o}2_X1X8WlTcN0^)uKh||{WTJ9-L}@_ptmrcN%rKL6-6(?UJ3dMHlv|icgj`+_ zuZ6_hIYIO&lOKaRf~%m+D|tm!faW?bNjRtkHJcNZu^Kgn0#$C{h6Y_!KPsz^pi}a!@GCqos4yFMFjTRC0p) zh@URFpntcZ6v7-gxCIw;#`rG~sSfq1hu8M!QqR%8-hl%f8r1mjr~8{;f6ot+*7`4< i9d&oc;x8WFf3|7ebmxiAiTL)Ofn%BU9%<=%Y0F>NO<61e From 60f42e04c0c1d8760574f40ed31640482ce2a07d Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Tue, 11 Aug 2026 09:06:00 -0300 Subject: [PATCH 3/8] Add frozen balance logic to controller transfers --- pallets/asset/src/lib.rs | 113 ++++++++++++++++++++++------------ pallets/settlement/src/lib.rs | 7 ++- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index cf87f5c8b7..03ba423943 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -2212,7 +2212,8 @@ impl Pallet { Error::::UnexpectedNonFungibleToken ); - let new_balance = Self::ensure_sufficient_balance(&caller_holding_ctx, &asset_id, value)?; + let new_balance = + Self::ensure_sufficient_balance(&caller_holding_ctx, &asset_id, value, false)?; Self::set_holders_balance(caller_holding_ctx.clone(), asset_id, new_balance)?; asset_details.total_supply = asset_details @@ -2373,6 +2374,7 @@ impl Pallet { None, None, holder_did, + true, weight_meter, )?; @@ -2738,6 +2740,7 @@ impl Pallet { instruction_id, instruction_memo, caller_did, + false, weight_meter, )?; @@ -2984,36 +2987,7 @@ impl Pallet { Error::::UnexpectedNonFungibleToken ); - match &asset_holder { - AssetHolder::Account(account) => { - if amount.is_zero() { - FrozenBalance::::remove(account, &asset_id); - } else { - FrozenBalance::::insert(account, asset_id, amount); - } - - Self::deposit_event(Event::FrozenBalanceSet { - caller_did, - asset_holder, - asset_id, - frozen_balance: amount, - }); - } - AssetHolder::Portfolio(portfolio) => { - pallet_portfolio::Pallet::::set_portfolio_frozen_balance( - portfolio.clone(), - asset_id, - amount, - ); - Self::deposit_event(Event::FrozenBalanceSet { - caller_did, - asset_holder, - asset_id, - frozen_balance: amount, - }); - } - } - + Self::unverified_set_frozen_tokens(caller_did, asset_holder, asset_id, amount); Ok(()) } } @@ -3350,7 +3324,13 @@ impl Pallet { ); // Verifies that both portfolios exist an that the sender portfolio has sufficient balance - Self::ensure_valid_holdings(sender, receiver, &asset_id, transfer_value)?; + Self::ensure_valid_holdings( + sender, + receiver, + &asset_id, + transfer_value, + is_controller_transfer, + )?; // Controllers are exempt from statistics, compliance and frozen rules. if is_controller_transfer { @@ -3463,7 +3443,8 @@ impl Pallet { asset_transfer_errors.push(Error::::InsufficientBalance.into()); } } else { - if let Err(e) = Self::ensure_sufficient_balance(sender, asset_id, transfer_value) { + if let Err(e) = Self::ensure_sufficient_balance(sender, asset_id, transfer_value, false) + { asset_transfer_errors.push(e); } } @@ -3710,7 +3691,7 @@ impl Pallet { asset_id: AssetId, balance_to_add: Balance, ) -> DispatchResult { - let _ = Self::ensure_sufficient_balance(&asset_holder, &asset_id, balance_to_add)?; + let _ = Self::ensure_sufficient_balance(&asset_holder, &asset_id, balance_to_add, false)?; match asset_holder { AssetHolder::Portfolio(portfolio_id) => { PortfolioPallet::::unchecked_lock_tokens(portfolio_id, asset_id, balance_to_add); @@ -3751,6 +3732,7 @@ impl Pallet { receiver: &AssetHolder, asset_id: &AssetId, value: Balance, + is_controller_transfer: bool, ) -> DispatchResult { match receiver { AssetHolder::Portfolio(receiver_portfolio_id) => { @@ -3761,7 +3743,7 @@ impl Pallet { } } - let _ = Self::ensure_sufficient_balance(sender, asset_id, value)?; + let _ = Self::ensure_sufficient_balance(sender, asset_id, value, is_controller_transfer)?; Ok(()) } @@ -3772,15 +3754,22 @@ impl Pallet { holder: &AssetHolder, asset_id: &AssetId, value: Balance, + is_controller_transfer: bool, ) -> Result { Self::ensure_granular(asset_id, value)?; let current_balance = Self::get_holders_balance(holder, asset_id); let locked_balance = Self::get_holders_locked_balance(holder, asset_id); - let frozen_balance = Self::get_holders_frozen_balance(holder, asset_id); - let unavailable_balance = locked_balance.saturating_add(frozen_balance); - let available_balance = current_balance.saturating_sub(unavailable_balance); + let available_balance = { + if is_controller_transfer { + current_balance.saturating_sub(locked_balance) + } else { + let frozen_balance = Self::get_holders_frozen_balance(holder, asset_id); + let unavailable_balance = locked_balance.saturating_add(frozen_balance); + current_balance.saturating_sub(unavailable_balance) + } + }; ensure!(available_balance >= value, Error::::InsufficientBalance); @@ -4105,6 +4094,7 @@ impl Pallet { instruction_id: Option, instruction_memo: Option, caller_did: IdentityId, + is_controller_transfer: bool, weight_meter: &mut WeightMeter, ) -> DispatchResult { // Gets the current balance and advances the checkpoint @@ -4129,6 +4119,19 @@ impl Pallet { BalanceOf::::insert(asset_id, sender_did, sender_new_balance); BalanceOf::::insert(asset_id, receiver_did, receiver_new_balance); + if is_controller_transfer { + let frozen_balance = Self::get_holders_frozen_balance(&sender, &asset_id); + if frozen_balance > 0 { + let new_frozen_balance = frozen_balance.saturating_sub(transfer_value); + Self::unverified_set_frozen_tokens( + caller_did, + sender.clone(), + asset_id, + new_frozen_balance, + ); + } + } + // Updates the balances in the portfolio pallet Self::transfer_holders_balance(sender.clone(), receiver.clone(), asset_id, transfer_value)?; @@ -4312,7 +4315,7 @@ impl Pallet { PortfolioPallet::::ensure_portfolio_validity(receiver_pid)?; } - Self::ensure_sufficient_balance(&sender, &asset_id, transfer_value)?; + Self::ensure_sufficient_balance(&sender, &asset_id, transfer_value, false)?; Statistics::::verify_transfer_restrictions( asset_id, @@ -4333,11 +4336,43 @@ impl Pallet { Some(inst_id), inst_memo, caller_did, + false, weight_meter, )?; Ok(()) } + + fn unverified_set_frozen_tokens( + caller_did: IdentityId, + asset_holder: AssetHolder, + asset_id: AssetId, + amount: Balance, + ) { + match &asset_holder { + AssetHolder::Account(account) => { + if amount.is_zero() { + FrozenBalance::::remove(account, &asset_id); + } else { + FrozenBalance::::insert(account, asset_id, amount); + } + } + AssetHolder::Portfolio(portfolio) => { + pallet_portfolio::Pallet::::set_portfolio_frozen_balance( + portfolio.clone(), + asset_id, + amount, + ); + } + } + + Self::deposit_event(Event::FrozenBalanceSet { + caller_did, + asset_holder, + asset_id, + frozen_balance: amount, + }); + } } //========================================================================== diff --git a/pallets/settlement/src/lib.rs b/pallets/settlement/src/lib.rs index 6af4449835..af91af69ff 100644 --- a/pallets/settlement/src/lib.rs +++ b/pallets/settlement/src/lib.rs @@ -1605,7 +1605,12 @@ impl Pallet { FundDescription::Fungible { asset_id, amount } => { ensure!(amount > 0, Error::::ZeroAmount); Asset::::ensure_asset_is_not_frozen(&asset_id)?; - Asset::::ensure_sufficient_balance(&resolved_from, &asset_id, amount)?; + Asset::::ensure_sufficient_balance( + &resolved_from, + &asset_id, + amount, + false, + )?; Asset::::transfer_holders_balance( resolved_from.clone(), to.clone(), From b6eef7a402aad7e75bdf2c1ec5f67b51b2d1bf8b Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Tue, 11 Aug 2026 09:18:42 -0300 Subject: [PATCH 4/8] Fix tests and comment --- pallets/asset/src/lib.rs | 2 +- pallets/runtime/tests/src/portfolio.rs | 1 + pallets/runtime/tests/src/sto_test.rs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index 03ba423943..a6ba3d0a77 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -1822,7 +1822,7 @@ pub mod pallet { Ok(()) } - /// Freezes `amount` of `asset_id` tokens from `account`. + /// Freezes `amount` of `asset_id` tokens from `asset_holder`. #[pallet::call_index(38)] #[pallet::weight(::WeightInfo::set_frozen_tokens())] pub fn set_frozen_tokens( diff --git a/pallets/runtime/tests/src/portfolio.rs b/pallets/runtime/tests/src/portfolio.rs index 70e4c83be7..3c61e306aa 100644 --- a/pallets/runtime/tests/src/portfolio.rs +++ b/pallets/runtime/tests/src/portfolio.rs @@ -299,6 +299,7 @@ fn do_move_asset_from_portfolio(memo: Option) { &PortfolioId::user_portfolio(bob.did, PortfolioNumber(666)).into(), &asset_id, 1, + false, ), Error::PortfolioDoesNotExist ); diff --git a/pallets/runtime/tests/src/sto_test.rs b/pallets/runtime/tests/src/sto_test.rs index fdd6401a09..c124ecebf9 100644 --- a/pallets/runtime/tests/src/sto_test.rs +++ b/pallets/runtime/tests/src/sto_test.rs @@ -123,6 +123,7 @@ fn raise_happy_path() { None, None, IdentityId::default(), + false, &mut weight_meter )); @@ -365,6 +366,7 @@ fn raise_unhappy_path() { None, None, IdentityId::default(), + false, &mut weight_meter )); From 5895d2cab41d81ff9ad75b7573eb7a34f3054052 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Thu, 13 Aug 2026 13:01:25 -0300 Subject: [PATCH 5/8] Add ensure_portfolio_validity check in base_set_frozen_tokens --- pallets/asset/src/benchmarking.rs | 3 ++- pallets/asset/src/lib.rs | 4 ++++ pallets/weights/src/pallet_asset.rs | 12 +++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pallets/asset/src/benchmarking.rs b/pallets/asset/src/benchmarking.rs index e131b6aef6..745dddc7b1 100644 --- a/pallets/asset/src/benchmarking.rs +++ b/pallets/asset/src/benchmarking.rs @@ -949,7 +949,8 @@ benchmarks! { set_frozen_tokens { let alice = UserBuilder::::default().generate_did().build("Alice"); let asset_id = create_sample_asset::(&alice, true); - }: _(alice.origin, asset_id, AssetHolder::try_from(alice.account().encode()).unwrap(), ONE_UNIT) + let alice_portfolio = create_portfolio::(&alice, "SenderPortfolio"); + }: _(alice.origin, asset_id, alice_portfolio, ONE_UNIT) get_holders_frozen_balance { let alice = UserBuilder::::default().generate_did().build("Alice"); diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index a6ba3d0a77..cfe2dca050 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -2987,6 +2987,10 @@ impl Pallet { Error::::UnexpectedNonFungibleToken ); + if let AssetHolder::Portfolio(receiver_portfolio_id) = &asset_holder { + PortfolioPallet::::ensure_portfolio_validity(receiver_portfolio_id)?; + } + Self::unverified_set_frozen_tokens(caller_did, asset_holder, asset_id, amount); Ok(()) } diff --git a/pallets/weights/src/pallet_asset.rs b/pallets/weights/src/pallet_asset.rs index 1ee7194a13..f18dce7db6 100644 --- a/pallets/weights/src/pallet_asset.rs +++ b/pallets/weights/src/pallet_asset.rs @@ -887,12 +887,14 @@ impl pallet_asset::WeightInfo for SubstrateWeight { // Proof: `Permissions::CurrentDispatchableName` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `Asset::Assets` (r:1 w:0) // Proof: `Asset::Assets` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `Asset::FrozenBalance` (r:0 w:1) - // Proof: `Asset::FrozenBalance` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + // Storage: `Portfolio::Portfolios` (r:1 w:0) + // Proof: `Portfolio::Portfolios` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `Portfolio::PortfolioFrozenAssets` (r:0 w:1) + // Proof: `Portfolio::PortfolioFrozenAssets` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `MaxEncodedLen`) fn set_frozen_tokens() -> Weight { - // Minimum execution time: 52_367 nanoseconds. - Weight::from_parts(54_138_000, 0) - .saturating_add(DbWeight::get().reads(5)) + // Minimum execution time: 62_991 nanoseconds. + Weight::from_parts(64_868_000, 0) + .saturating_add(DbWeight::get().reads(6)) .saturating_add(DbWeight::get().writes(1)) } // Storage: `Asset::FrozenBalance` (r:1 w:0) From 7d9f7ee270c46208e3d851dbe5dff950e49ac106 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Fri, 14 Aug 2026 08:34:54 -0300 Subject: [PATCH 6/8] Remove fungible check for can_send, can_receive --- pallets/asset/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index cfe2dca050..e77742f7ea 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -3912,10 +3912,6 @@ impl Pallet { } }; - if !asset_details.asset_type.is_fungible() { - return false; - } - let holder_did = { match pallet_identity::Pallet::::asset_holder_did(&asset_holder) { Ok(did) => did, From 607939b117be0b6315a48f2cef53e1e956cd1fe6 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Fri, 14 Aug 2026 14:26:51 -0300 Subject: [PATCH 7/8] Remove unused check --- pallets/asset/src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index e77742f7ea..d198c0ef3a 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -3905,12 +3905,13 @@ impl Pallet { holder_is_the_sender: bool, weight_meter: &mut WeightMeter, ) -> bool { - let asset_details = { - match Self::try_get_asset_details(&asset_id) { - Ok(details) => details, - Err(_) => return false, - } - }; + if Self::ensure_asset_exists(asset_id).is_err() { + return false; + } + + if Self::ensure_asset_is_not_frozen(&asset_id).is_err() { + return false; + } let holder_did = { match pallet_identity::Pallet::::asset_holder_did(&asset_holder) { @@ -3919,10 +3920,6 @@ impl Pallet { } }; - if Self::ensure_asset_is_not_frozen(&asset_id).is_err() { - return false; - } - T::ComplianceManager::is_holder_compliant( holder_did, asset_id, From c8ba3245b8d55452563b6f6548b1ed9b307f4efb Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 17 Aug 2026 14:44:21 -0300 Subject: [PATCH 8/8] Use new common type --- pallets/precompiles/src/interface/erc7943.rs | 68 ++++++-------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/pallets/precompiles/src/interface/erc7943.rs b/pallets/precompiles/src/interface/erc7943.rs index 4c79c1d0e6..7524c97ce6 100644 --- a/pallets/precompiles/src/interface/erc7943.rs +++ b/pallets/precompiles/src/interface/erc7943.rs @@ -110,30 +110,21 @@ impl FungibleAssetInterface { ) -> Result, Error> { env.charge(::WeightInfo::set_frozen_tokens())?; - let caller = Self::caller(env)?; - let caller_acc = ::AddressMapper::to_account_id(&caller); - - let acc_to_freeze = call.account.into_array().into(); - let acc_to_freeze = - ::AddressMapper::to_account_id(&acc_to_freeze); - let acc_to_freeze = AssetHolder::try_from(acc_to_freeze.encode()).map_err(|_| { - Error::Revert(Revert { - reason: ERR_INVALID_ACCOUNT_ID.into(), - }) - })?; - - let amount = Self::to_balance(call.amount)?; - - if let Err(e) = pallet_asset::Pallet::::set_frozen_tokens( - RawOrigin::Signed(caller_acc).into(), - asset_id, - acc_to_freeze, - amount, - ) { - return Err(Self::extrinsic_error(e)); - } + let caller = Common::::caller(env)?; + let acc_to_freeze = Common::::asset_holder(call.account)?; + let amount = Common::::to_balance(call.amount)?; + + Common::::call_runtime( + env, + caller.runtime_origin(), + pallet_asset::Call::::set_frozen_tokens { + asset_id, + asset_holder: acc_to_freeze, + amount, + }, + )?; - Self::deposit_event( + Common::::deposit_event( env, IFungibleAssetEvents::Frozen(IFungibleAsset::Frozen { account: call.account.into(), @@ -154,16 +145,10 @@ impl FungibleAssetInterface { ) -> Result, Error> { env.charge(::WeightInfo::get_holders_frozen_balance())?; - let from = call.account.into_array().into(); - let from = ::AddressMapper::to_account_id(&from); - let from = AssetHolder::try_from(from.encode()).map_err(|_| { - Error::Revert(Revert { - reason: ERR_INVALID_ACCOUNT_ID.into(), - }) - })?; + let from = Common::::asset_holder(call.account)?; let frozen_tokens = pallet_asset::Pallet::::get_holders_frozen_balance(&from, &asset_id); - let frozen_tokens = Self::to_u256(frozen_tokens)?; + let frozen_tokens = Common::::to_u256(frozen_tokens)?; Ok(IFungibleAsset::getFrozenTokensCall::abi_encode_returns( &frozen_tokens, @@ -180,17 +165,11 @@ impl FungibleAssetInterface { ::WeightInfo::transfer_is_allowed_for_holder_worst_case(); let charged = env.charge(transfer_is_allowed_for_holder_worst_case_weight)?; - let sender_acc = call.account.into_array().into(); - let sender_acc = ::AddressMapper::to_account_id(&sender_acc); - let sender_acc = AssetHolder::try_from(sender_acc.encode()).map_err(|_| { - Error::Revert(Revert { - reason: ERR_INVALID_ACCOUNT_ID.into(), - }) - })?; + let sender = Common::::asset_holder(call.account)?; let mut weight_meter = WeightMeter::max_limit_no_minimum(); let allowed = pallet_asset::Pallet::::transfer_is_allowed_for_holder( - &sender_acc, + &sender, &asset_id, true, &mut weight_meter, @@ -220,18 +199,11 @@ impl FungibleAssetInterface { ::WeightInfo::transfer_is_allowed_for_holder_worst_case(); let charged = env.charge(transfer_is_allowed_for_holder_worst_case_weight)?; - let receiver_acc = call.account.into_array().into(); - let receiver_acc = - ::AddressMapper::to_account_id(&receiver_acc); - let receiver_acc = AssetHolder::try_from(receiver_acc.encode()).map_err(|_| { - Error::Revert(Revert { - reason: ERR_INVALID_ACCOUNT_ID.into(), - }) - })?; + let receiver = Common::::asset_holder(call.account)?; let mut weight_meter = WeightMeter::max_limit_no_minimum(); let allowed = pallet_asset::Pallet::::transfer_is_allowed_for_holder( - &receiver_acc, + &receiver, &asset_id, false, &mut weight_meter,