From 5e94e5d0eecb7912d650b6c6b9e1f35f54598aa2 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Thu, 13 Aug 2026 19:21:16 -0300 Subject: [PATCH 1/6] Add set_symbol, set_name, unpause, pause --- pallets/precompiles/src/interface/erc20.rs | 4 +- pallets/precompiles/src/interface/erc3643.rs | 193 +++++++++++++++++++ pallets/precompiles/src/interface/mod.rs | 18 ++ 3 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 pallets/precompiles/src/interface/erc3643.rs diff --git a/pallets/precompiles/src/interface/erc20.rs b/pallets/precompiles/src/interface/erc20.rs index 8892f0dadb..491af01b66 100644 --- a/pallets/precompiles/src/interface/erc20.rs +++ b/pallets/precompiles/src/interface/erc20.rs @@ -33,7 +33,7 @@ use polymesh_primitives::traits::SettlementFnTrait; use polymesh_primitives::{AccountId as AccountId32, AssetHolder, WeightMeter}; use crate::interface::FungibleAssetInterface; -use crate::interface::ERR_INVALID_ACCOUNT_ID; +use crate::interface::{DECIMALS, ERR_INVALID_ACCOUNT_ID}; use crate::interface::{ERR_ASSET_NOT_FOUND, ERR_INST_NOT_EXECUTED}; impl FungibleAssetInterface @@ -348,6 +348,6 @@ where _asset_id: AssetId, _env: &mut impl Ext, ) -> Result, Error> { - Ok(IFungibleAsset::decimalsCall::abi_encode_returns(&6)) + Ok(IFungibleAsset::decimalsCall::abi_encode_returns(&DECIMALS)) } } diff --git a/pallets/precompiles/src/interface/erc3643.rs b/pallets/precompiles/src/interface/erc3643.rs new file mode 100644 index 0000000000..190fc0fe9f --- /dev/null +++ b/pallets/precompiles/src/interface/erc3643.rs @@ -0,0 +1,193 @@ +// This file is part of the Polymesh distribution (https://github.com/PolymeshAssociation/Polymesh). +// Copyright (c) 2020 Polymesh Association + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use alloc::vec::Vec; +use frame_support::traits::Get; +use frame_support::dispatch::RawOrigin; + +use pallet_revive::precompiles::alloy::primitives::FixedBytes; +use pallet_revive::precompiles::alloy::sol_types::Revert; +use pallet_revive::precompiles::Ext; +use pallet_revive::precompiles::{AddressMapper, Error}; + +use pallet_asset::{AssetIdTicker, AssetNames, WeightInfo}; +use polymesh_precompiles::{IFungibleAsset, IFungibleAssetEvents}; +use polymesh_primitives::asset::{AssetId, AssetName}; +use polymesh_primitives::ticker::TICKER_LEN; +use polymesh_primitives::Ticker; + +use crate::interface::FungibleAssetInterface; +use crate::interface::{DECIMALS, ERR_INVALID_SYMBOL}; + +impl FungibleAssetInterface +where + T: pallet_revive::Config + + pallet_asset::Config + + pallet_asset::checkpoint::Config + + pallet_settlement::Config, +{ + /// Freezes the asset, preventing token transfers. Only an agent of the token can call this function. + pub(crate) fn pause(asset_id: AssetId, env: &mut impl Ext) -> Result, Error> { + env.charge(::WeightInfo::freeze())?; + + let caller = Self::caller(env)?; + let caller_acc = ::AddressMapper::to_account_id(&caller); + + match pallet_asset::Pallet::::freeze(RawOrigin::Signed(caller_acc).into(), asset_id) { + Ok(_) => { + Self::deposit_event( + env, + IFungibleAssetEvents::Paused(IFungibleAsset::Paused { + userAddress: caller.0.into(), + }), + )?; + Ok(Vec::new()) + } + Err(e) => Err(Self::extrinsic_error(e)), + } + } + + /// Unfreezes the token contract, allowing token transfers. Only an agent of the token can call this function. + pub(crate) fn unpause(asset_id: AssetId, env: &mut impl Ext) -> Result, Error> { + env.charge(::WeightInfo::unfreeze())?; + + let caller = Self::caller(env)?; + let caller_acc = ::AddressMapper::to_account_id(&caller); + + match pallet_asset::Pallet::::unfreeze(RawOrigin::Signed(caller_acc).into(), asset_id) { + Ok(_) => { + Self::deposit_event( + env, + IFungibleAssetEvents::Unpaused(IFungibleAsset::Unpaused { + userAddress: caller.0.into(), + }), + )?; + Ok(Vec::new()) + } + Err(e) => Err(Self::extrinsic_error(e)), + } + } + + /// Sets the token name. Only the owner of the token contract can call this function. + pub(crate) fn set_name( + asset_id: AssetId, + call: &IFungibleAsset::setNameCall, + env: &mut impl Ext, + ) -> Result, Error> { + let asset_name_str = &call.name; + let new_asset_name = AssetName::from(asset_name_str.as_bytes().to_vec()); + env.charge( + ::WeightInfo::rename_asset(new_asset_name.len() as u32) + .saturating_add(T::DbWeight::get().reads(1)), + )?; + + let caller = Self::caller(env)?; + let caller_acc = ::AddressMapper::to_account_id(&caller); + + match pallet_asset::Pallet::::rename_asset( + RawOrigin::Signed(caller_acc).into(), + asset_id, + new_asset_name.clone(), + ) { + Ok(_) => { + let ticker = AssetIdTicker::::get(&asset_id).unwrap_or_default(); + Self::deposit_event( + env, + IFungibleAssetEvents::UpdatedTokenInformation( + IFungibleAsset::UpdatedTokenInformation { + newName: FixedBytes::try_from(new_asset_name.0.as_slice()) + .unwrap_or_default(), + newSymbol: FixedBytes::try_from(ticker.as_ref()).unwrap_or_default(), + newDecimals: DECIMALS, + newVersion: Default::default(), + newOnchainID: Default::default(), + }, + ), + )?; + Ok(Vec::new()) + } + Err(e) => Err(Self::extrinsic_error(e)), + } + } + + /// Sets the token symbol. Only the owner of the token contract can call this function. + pub(crate) fn set_symbol( + asset_id: AssetId, + call: &IFungibleAsset::setSymbolCall, + env: &mut impl Ext, + ) -> Result, Error> { + env.charge( + ::WeightInfo::link_ticker_to_asset_id() + .saturating_add(::WeightInfo::register_unique_ticker()) + .saturating_add(T::DbWeight::get().reads(1)), + )?; + + let new_symbol = &call.symbol.as_bytes(); + + if new_symbol.len() > TICKER_LEN { + return Err(Error::Revert(Revert { + reason: ERR_INVALID_SYMBOL.into(), + })); + } + + let ticker = Ticker::from_slice_truncated(new_symbol); + let caller = Self::caller(env)?; + let caller_acc = ::AddressMapper::to_account_id(&caller); + + if let Err(e) = pallet_asset::Pallet::::register_unique_ticker( + RawOrigin::Signed(caller_acc.clone()).into(), + ticker, + ) { + return Err(Self::extrinsic_error(e)); + } + + match pallet_asset::Pallet::::link_ticker_to_asset_id( + RawOrigin::Signed(caller_acc).into(), + ticker, + asset_id, + ) { + Ok(_) => { + let asset_name = AssetNames::::get(&asset_id).unwrap_or_default(); + Self::deposit_event( + env, + IFungibleAssetEvents::UpdatedTokenInformation( + IFungibleAsset::UpdatedTokenInformation { + newName: FixedBytes::try_from(asset_name.0.as_slice()) + .unwrap_or_default(), + newSymbol: FixedBytes::try_from(ticker.as_ref()).unwrap_or_default(), + newDecimals: DECIMALS, + newVersion: Default::default(), + newOnchainID: Default::default(), + }, + ), + )?; + Ok(Vec::new()) + } + Err(e) => Err(Self::extrinsic_error(e)), + } + } + + /// Sets the frozen status of a specific address. Only an agent of the token can call this function. + pub(crate) fn set_address_frozen( + _asset_id: AssetId, + _call: &IFungibleAsset::setAddressFrozenCall, + _env: &mut impl Ext, + ) -> Result, Error> { + log::warn!("set_address_frozen is not implemented yet"); + Err(Error::Revert(Revert { + reason: "set_address_frozen is not implemented yet".into(), + })) + } +} diff --git a/pallets/precompiles/src/interface/mod.rs b/pallets/precompiles/src/interface/mod.rs index 0ce58bd297..93f882e6f3 100644 --- a/pallets/precompiles/src/interface/mod.rs +++ b/pallets/precompiles/src/interface/mod.rs @@ -33,6 +33,7 @@ use polymesh_primitives::asset::AssetId; use polymesh_primitives::Balance; mod erc20; +mod erc3643; mod erc7943; mod polymesh_specific; @@ -44,8 +45,11 @@ pub(crate) const ERR_ASSET_NOT_FOUND: &str = "Asset not found"; pub(crate) const ERR_ASSET_NOT_FUNGIBLE: &str = "Asset is not fungible"; pub(crate) const ERR_INVALID_ACCOUNT_ID: &str = "Invalid account id"; pub(crate) const ERR_INST_NOT_EXECUTED: &str = "Instruction was not executed; Most likely the instruction is missing an affirmation from the receiver/mediator"; +pub(crate) const ERR_INVALID_SYMBOL: &str = "Invalid symbol; Ticker is too long"; // ======================================================== +pub const DECIMALS: u8 = 6; + /// All precompile calls exposed by the Polymesh runtime. pub struct FungibleAssetInterface(PhantomData); @@ -89,6 +93,11 @@ where | IFungibleAssetCalls::permit(_) | IFungibleAssetCalls::forcedTransfer(_) | IFungibleAssetCalls::setFrozenTokens(_) + | IFungibleAssetCalls::setName(_) + | IFungibleAssetCalls::setSymbol(_) + | IFungibleAssetCalls::pause(_) + | IFungibleAssetCalls::unpause(_) + | IFungibleAssetCalls::setAddressFrozen(_) if env.is_read_only() => { Err(Error::Error( @@ -131,6 +140,15 @@ where IFungibleAssetCalls::setFrozenTokens(call) => { Self::set_frozen_tokens(asset_id, call, env) } + + // ERC3643 functions + IFungibleAssetCalls::pause(_) => Self::pause(asset_id, env), + IFungibleAssetCalls::unpause(_) => Self::unpause(asset_id, env), + IFungibleAssetCalls::setName(call) => Self::set_name(asset_id, call, env), + IFungibleAssetCalls::setSymbol(call) => Self::set_symbol(asset_id, call, env), + IFungibleAssetCalls::setAddressFrozen(call) => { + Self::set_address_frozen(asset_id, call, env) + } } } } From 9ba787a57c9cef6695ce1e2aeac2b63f4f967def Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Thu, 13 Aug 2026 19:21:35 -0300 Subject: [PATCH 2/6] Add set_symbol, set_name, unpause, pause --- .../src/interfaces/FungibleAssetStub.sol | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/precompiles/src/interfaces/FungibleAssetStub.sol b/precompiles/src/interfaces/FungibleAssetStub.sol index fa4478acf5..01d1c0c317 100644 --- a/precompiles/src/interfaces/FungibleAssetStub.sol +++ b/precompiles/src/interfaces/FungibleAssetStub.sol @@ -209,6 +209,40 @@ interface IFungibleAsset { /// @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); + + // ============================================================ + // ERC-3643 + // ============================================================ + + /// @notice Emitted when the token information is updated. This includes updates to the token's name, symbol, decimals, version, and onchainID. + event UpdatedTokenInformation( + string indexed newName, + string indexed newSymbol, + uint8 newDecimals, + string newVersion, + address indexed newOnchainID + ); + + /// @notice Emitted when the token contract is paused. + event Paused(address userAddress); + + /// @notice Emitted when the token contract is unpaused. + event Unpaused(address userAddress); + + /// @notice Sets the token name. Only the owner of the token contract can call this function. + function setName(string calldata name) external; + + /// @notice Sets the token symbol. Only the owner of the token contract can call this function. + function setSymbol(string calldata symbol) external; + + /// @notice Pauses the token contract, preventing token transfers. Only an agent of the token can call this function. + function pause() external; + + /// @notice Unpauses the token contract, allowing token transfers. Only an agent of the token can call this function. + function unpause() external; + + /// @notice Sets the frozen status of a specific address. Only an agent of the token can call this function. + function setAddressFrozen(address userAddress, bool freeze) external; } contract FungibleAssetStub is IFungibleAsset { @@ -337,4 +371,33 @@ contract FungibleAssetStub is IFungibleAsset { account; revert NotExecutable(); } + + /// @notice Sets the token name. Only the owner of the token contract can call this function. + function setName(string calldata name) external override { + name; + revert NotExecutable(); + } + + /// @notice Sets the token symbol. Only the owner of the token contract can call this function. + function setSymbol(string calldata symbol) external override { + symbol; + revert NotExecutable(); + } + + /// @notice Pauses the token contract, preventing token transfers. Only an agent of the token can call this function. + function pause() external override { + revert NotExecutable(); + } + + /// @notice Unpauses the token contract, allowing token transfers. Only an agent of the token can call this function. + function unpause() external override { + revert NotExecutable(); + } + + /// @notice Sets the frozen status of a specific address. Only an agent of the token can call this function. + function setAddressFrozen(address userAddress, bool freeze) external override { + userAddress; + freeze; + revert NotExecutable(); + } } From 5aa726654b8b0bc410c8a9e36b63fdf799e138fc Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 17 Aug 2026 17:06:24 -0300 Subject: [PATCH 3/6] Add set_address_frozen --- pallets/asset/src/benchmarking.rs | 6 ++ pallets/asset/src/lib.rs | 57 +++++++++++++++++++ pallets/precompiles/src/interface/erc3643.rs | 33 ++++++++--- pallets/weights/src/pallet_asset.rs | 16 ++++++ .../src/interfaces/FungibleAssetStub.sol | 5 +- 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/pallets/asset/src/benchmarking.rs b/pallets/asset/src/benchmarking.rs index 745dddc7b1..bdedd7765c 100644 --- a/pallets/asset/src/benchmarking.rs +++ b/pallets/asset/src/benchmarking.rs @@ -1011,4 +1011,10 @@ benchmarks! { ) ); } + + set_address_frozen { + let bob = UserBuilder::::default().generate_did().build("Bob"); + let alice = UserBuilder::::default().generate_did().build("Alice"); + let asset_id = create_sample_asset::(&alice, true); + }: _(alice.origin, asset_id, true, bob.account()) } diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index d198c0ef3a..cfeee1235b 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -366,6 +366,13 @@ pub mod pallet { asset_id: AssetId, frozen_balance: Balance, }, + /// The account status has been set to `freeze`. + SetAccountFreeze { + caller_did: IdentityId, + account: T::AccountId, + asset_id: AssetId, + freeze: bool, + }, } /// Map each [`Ticker`] to its registration details ([`TickerRegistration`]). @@ -646,6 +653,18 @@ pub mod pallet { ValueQuery, >; + /// Tracks if the account is frozen. + #[pallet::storage] + pub type FrozenAccounts = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Blake2_128Concat, + AssetId, + bool, + ValueQuery, + >; + /// Storage version. #[pallet::storage] pub type StorageVersion = StorageValue<_, Version, ValueQuery>; @@ -1833,6 +1852,18 @@ pub mod pallet { ) -> DispatchResult { Self::base_set_frozen_tokens(origin, asset_id, asset_holder, amount) } + + /// Set the status of `account` for `asset_id` to `freeze`. + #[pallet::call_index(39)] + #[pallet::weight(::WeightInfo::set_address_frozen())] + pub fn set_address_frozen( + origin: OriginFor, + asset_id: AssetId, + freeze: bool, + account: T::AccountId, + ) -> DispatchResult { + Self::base_set_address_frozen(origin, asset_id, freeze, account) + } } #[pallet::error] @@ -1994,6 +2025,7 @@ pub mod pallet { fn get_holders_frozen_balance() -> Weight; fn transfer_is_allowed_for_holder_best_case() -> Weight; fn transfer_is_allowed_for_holder_worst_case() -> Weight; + fn set_address_frozen() -> Weight; } } @@ -2994,6 +3026,31 @@ impl Pallet { Self::unverified_set_frozen_tokens(caller_did, asset_holder, asset_id, amount); Ok(()) } + + /// Sets the frozen transfer amount for an account on a given asset. + fn base_set_address_frozen( + origin: T::RuntimeOrigin, + asset_id: AssetId, + freeze: bool, + account: T::AccountId, + ) -> DispatchResult { + let caller_did = ExternalAgents::::ensure_perms(origin, &asset_id)?; + + if freeze { + FrozenAccounts::::insert(account.clone(), asset_id, true); + } else { + FrozenAccounts::::remove(&account, &asset_id); + } + + Self::deposit_event(Event::SetAccountFreeze { + caller_did, + account, + asset_id, + freeze, + }); + + Ok(()) + } } //========================================================================== diff --git a/pallets/precompiles/src/interface/erc3643.rs b/pallets/precompiles/src/interface/erc3643.rs index 7070ecbd96..8076723127 100644 --- a/pallets/precompiles/src/interface/erc3643.rs +++ b/pallets/precompiles/src/interface/erc3643.rs @@ -157,13 +157,32 @@ impl FungibleAssetInterface { /// Sets the frozen status of a specific address. Only an agent of the token can call this function. pub(crate) fn set_address_frozen( - _asset_id: AssetId, - _call: &IFungibleAsset::setAddressFrozenCall, - _env: &mut impl Ext, + asset_id: AssetId, + call: &IFungibleAsset::setAddressFrozenCall, + env: &mut impl Ext, ) -> Result, Error> { - log::warn!("set_address_frozen is not implemented yet"); - Err(Error::Revert(Revert { - reason: "set_address_frozen is not implemented yet".into(), - })) + let caller = Common::::caller(env)?; + + let acc_to_freeze = Common::::account_id(call.account); + + Common::::call_runtime( + env, + caller.runtime_origin(), + pallet_asset::Call::::set_address_frozen { + asset_id, + freeze: call.freeze, + account: acc_to_freeze, + }, + )?; + + Common::::deposit_event( + env, + IFungibleAssetEvents::AddressFrozen(IFungibleAsset::AddressFrozen { + account: call.account, + freeze: call.freeze, + owner: caller.address.0.into(), + }), + )?; + Ok(Vec::new()) } } diff --git a/pallets/weights/src/pallet_asset.rs b/pallets/weights/src/pallet_asset.rs index f18dce7db6..635e328d59 100644 --- a/pallets/weights/src/pallet_asset.rs +++ b/pallets/weights/src/pallet_asset.rs @@ -931,4 +931,20 @@ impl pallet_asset::WeightInfo for SubstrateWeight { // Minimum execution time: 92_745 nanoseconds. Weight::from_parts(94_474_000, 0).saturating_add(DbWeight::get().reads(6)) } + // 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::FrozenAccounts` (r:0 w:1) + // Proof: `Asset::FrozenAccounts` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn set_address_frozen() -> Weight { + // Minimum execution time: 42_414 nanoseconds. + Weight::from_parts(43_671_000, 0) + .saturating_add(DbWeight::get().reads(4)) + .saturating_add(DbWeight::get().writes(1)) + } } diff --git a/precompiles/src/interfaces/FungibleAssetStub.sol b/precompiles/src/interfaces/FungibleAssetStub.sol index 01d1c0c317..e777b96583 100644 --- a/precompiles/src/interfaces/FungibleAssetStub.sol +++ b/precompiles/src/interfaces/FungibleAssetStub.sol @@ -229,6 +229,9 @@ interface IFungibleAsset { /// @notice Emitted when the token contract is unpaused. event Unpaused(address userAddress); + /// @notice Emitted when the account of an investor is frozen or unfrozen. + event AddressFrozen(address indexed account, bool freeze, address indexed owner); + /// @notice Sets the token name. Only the owner of the token contract can call this function. function setName(string calldata name) external; @@ -242,7 +245,7 @@ interface IFungibleAsset { function unpause() external; /// @notice Sets the frozen status of a specific address. Only an agent of the token can call this function. - function setAddressFrozen(address userAddress, bool freeze) external; + function setAddressFrozen(address account, bool freeze) external; } contract FungibleAssetStub is IFungibleAsset { From b032b247d82857f98637c9849ab1968689480c82 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 17 Aug 2026 17:56:20 -0300 Subject: [PATCH 4/6] Add ensure_holder_is_not_frozen for transfers --- pallets/asset/src/lib.rs | 31 ++++++++++++-------- pallets/precompiles/src/interface/erc3643.rs | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index cfeee1235b..dbe307558b 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -369,7 +369,7 @@ pub mod pallet { /// The account status has been set to `freeze`. SetAccountFreeze { caller_did: IdentityId, - account: T::AccountId, + account: AccountId32, asset_id: AssetId, freeze: bool, }, @@ -655,15 +655,8 @@ pub mod pallet { /// Tracks if the account is frozen. #[pallet::storage] - pub type FrozenAccounts = StorageDoubleMap< - _, - Twox64Concat, - T::AccountId, - Blake2_128Concat, - AssetId, - bool, - ValueQuery, - >; + pub type FrozenAccounts = + StorageDoubleMap<_, Twox64Concat, AccountId32, Blake2_128Concat, AssetId, bool, ValueQuery>; /// Storage version. #[pallet::storage] @@ -1860,7 +1853,7 @@ pub mod pallet { origin: OriginFor, asset_id: AssetId, freeze: bool, - account: T::AccountId, + account: AccountId32, ) -> DispatchResult { Self::base_set_address_frozen(origin, asset_id, freeze, account) } @@ -1978,6 +1971,8 @@ pub mod pallet { SelfOwnershipTransferNotAllowed, /// The weight Limit for the extrinsic has been exceeded. WeightLimitExceeded, + /// The account is frozen and cannot transfer assets. + InvalidTransferFrozenAccount, } pub trait WeightInfo { @@ -3032,7 +3027,7 @@ impl Pallet { origin: T::RuntimeOrigin, asset_id: AssetId, freeze: bool, - account: T::AccountId, + account: AccountId32, ) -> DispatchResult { let caller_did = ExternalAgents::::ensure_perms(origin, &asset_id)?; @@ -3434,6 +3429,17 @@ impl Pallet { Ok(()) } + /// Returns `Ok` if the holder is not frozen for the given asset. + fn ensure_holder_is_not_frozen(holder: &AssetHolder, asset_id: &AssetId) -> DispatchResult { + if let AssetHolder::Account(account_id) = holder { + ensure!( + !FrozenAccounts::::get(account_id, asset_id), + Error::::InvalidTransferFrozenAccount + ); + } + Ok(()) + } + /// Returns a vector containing all errors for the transfer. An empty vec means there's no error. pub fn asset_transfer_report( sender: &AssetHolder, @@ -3826,6 +3832,7 @@ impl Pallet { if is_controller_transfer { current_balance.saturating_sub(locked_balance) } else { + Self::ensure_holder_is_not_frozen(holder, asset_id)?; 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) diff --git a/pallets/precompiles/src/interface/erc3643.rs b/pallets/precompiles/src/interface/erc3643.rs index 8076723127..2897da7f2f 100644 --- a/pallets/precompiles/src/interface/erc3643.rs +++ b/pallets/precompiles/src/interface/erc3643.rs @@ -163,7 +163,7 @@ impl FungibleAssetInterface { ) -> Result, Error> { let caller = Common::::caller(env)?; - let acc_to_freeze = Common::::account_id(call.account); + let acc_to_freeze = Common::::account_id32(call.account)?; Common::::call_runtime( env, From 53777849ad28ec3c93171b65763ca1a6fbf90ebc Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 17 Aug 2026 17:58:27 -0300 Subject: [PATCH 5/6] Update precompile .bin --- .../src/interfaces/FungibleAssetStub.bin | Bin 1048 -> 1321 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/precompiles/src/interfaces/FungibleAssetStub.bin b/precompiles/src/interfaces/FungibleAssetStub.bin index 7490fb2805412c5d425cb16deced6d97903c1220..e9a24b2766e72d9946842b5f606d6c4f467acf0e 100644 GIT binary patch literal 1321 zcmZ`%TSyd97~ZopR*IRI-Nn>$(kLh-tab~@+O9+w7&qE2#+-9n(Jq#n1qD(wd&BC% z*=tD(ixehw0eg`GlZYM)^g)!O3U>7^b5L+w9jXLAjjWts2)zwi4m=L}Y`-LX|M z6T=FZEYC_J=55&oU2w@(JW-^$uMZ6va){xBOL6xf@f-jc1~{7bXc*u{9h!)7Kfv+R z-**B$25_=&`&Sb|V*sZotDggGXXO)BK(rQ^*EbC7b@-+;eHh?2T|~Qrrw>Fv=ev`@ zWCGMinrNj-fYJB&b^~k%7;i2+0|E=c-n7maDXlgRo(ts=v;i0jb!+*Ksd|7vAZyz< zWd=9|aM$676?&UJv4&*9rnsjZ1bzBgOJ+ZF5Ynzor{C59YzLTL)ALM+=Gz4>rdQng zmgVCBY3R~#-}V$f}Hjf*lvQlY?vpU*(niYE8{e? zS#@9mfdP_eFe#}_u!}kBNhxz`Q3MWxW|DRBycV>ixQ%cGF{flm_1^&p&2MHyJ)sdu z7E*lCqUbn8e+T4W!GIVxb(j9ixPreFLN6XcOqZRNe=ER5ja7 zj2~TCPy=^BIN+2-sPFMZi0!y|B`)?N3y?uk48h+iDOQZ&7cb;gi)tcV zHoN7Z29yj=ub;~`6K);L5^j^17-yVP1Pfs;jI3dimXNXF@iEy^UFP&KSvZv7p%C^3 zn4(cvT=g+UNF-bU3w{_C%7&t$2sp4ks0EQ#ORe0)e4;fun4k~#lI1Bx$?=oNz?U%r;cbay2#Q$nBC7)5 zrHSx|5>4pIilM~$$@IY!ZMW#|{CVAdxs7cb6AACZ!p+3)QCPbTStBCG@Cu7qfft@< zm%=|_&R9qEt{9Gmq7fYTG3g+^gv32AToyl~_xY5ZlJMW?m%_Sg`gC*MwP%~^T27SY w3Rwj$LVaar@BQ4~)vi28es#s;td7=_jOkUQa|gRPRNKXinkT#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{ From b0272deb9e0d20d0b48b0a26fc01e7469ba93bb3 Mon Sep 17 00:00:00 2001 From: Henrique Nogara Date: Mon, 17 Aug 2026 18:33:00 -0300 Subject: [PATCH 6/6] Fix benchark build; Use keccak256 --- pallets/asset/src/benchmarking.rs | 3 ++- pallets/asset/src/lib.rs | 8 +++++++- pallets/precompiles/src/interface/erc3643.rs | 9 +++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pallets/asset/src/benchmarking.rs b/pallets/asset/src/benchmarking.rs index bdedd7765c..932a41335c 100644 --- a/pallets/asset/src/benchmarking.rs +++ b/pallets/asset/src/benchmarking.rs @@ -1016,5 +1016,6 @@ benchmarks! { let bob = UserBuilder::::default().generate_did().build("Bob"); let alice = UserBuilder::::default().generate_did().build("Alice"); let asset_id = create_sample_asset::(&alice, true); - }: _(alice.origin, asset_id, true, bob.account()) + let bob_account: [u8; 32] = bob.account().encode().try_into().unwrap(); + }: _(alice.origin, asset_id, true, bob_account.into()) } diff --git a/pallets/asset/src/lib.rs b/pallets/asset/src/lib.rs index dbe307558b..adb66e37c2 100644 --- a/pallets/asset/src/lib.rs +++ b/pallets/asset/src/lib.rs @@ -3022,7 +3022,7 @@ impl Pallet { Ok(()) } - /// Sets the frozen transfer amount for an account on a given asset. + /// Sets whether `account` is frozen for transfers of `asset_id`. fn base_set_address_frozen( origin: T::RuntimeOrigin, asset_id: AssetId, @@ -3977,6 +3977,12 @@ impl Pallet { return false; } + if holder_is_the_sender { + if Self::ensure_holder_is_not_frozen(asset_holder, asset_id).is_err() { + return false; + } + } + let holder_did = { match pallet_identity::Pallet::::asset_holder_did(&asset_holder) { Ok(did) => did, diff --git a/pallets/precompiles/src/interface/erc3643.rs b/pallets/precompiles/src/interface/erc3643.rs index 2897da7f2f..a0bae71e12 100644 --- a/pallets/precompiles/src/interface/erc3643.rs +++ b/pallets/precompiles/src/interface/erc3643.rs @@ -16,6 +16,7 @@ use alloc::vec::Vec; use frame_support::traits::Get; +use pallet_revive::precompiles::alloy::primitives::keccak256; use pallet_revive::precompiles::alloy::primitives::FixedBytes; use pallet_revive::precompiles::alloy::sol_types::Revert; use pallet_revive::precompiles::Error; @@ -97,8 +98,8 @@ impl FungibleAssetInterface { env, IFungibleAssetEvents::UpdatedTokenInformation( IFungibleAsset::UpdatedTokenInformation { - newName: FixedBytes::try_from(new_asset_name.0.as_slice()).unwrap_or_default(), - newSymbol: FixedBytes::try_from(ticker.as_ref()).unwrap_or_default(), + newName: FixedBytes::from(keccak256(new_asset_name.0.as_slice())), + newSymbol: FixedBytes::from(keccak256(ticker.as_ref())), newDecimals: DECIMALS, newVersion: Default::default(), newOnchainID: Default::default(), @@ -144,8 +145,8 @@ impl FungibleAssetInterface { env, IFungibleAssetEvents::UpdatedTokenInformation( IFungibleAsset::UpdatedTokenInformation { - newName: FixedBytes::try_from(asset_name.0.as_slice()).unwrap_or_default(), - newSymbol: FixedBytes::try_from(ticker.as_ref()).unwrap_or_default(), + newName: FixedBytes::from(keccak256(asset_name.0.as_slice())), + newSymbol: FixedBytes::from(keccak256(ticker.as_ref())), newDecimals: DECIMALS, newVersion: Default::default(), newOnchainID: Default::default(),