Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pallets/asset/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,4 +1011,11 @@ benchmarks! {
)
);
}

set_address_frozen {
let bob = UserBuilder::<T>::default().generate_did().build("Bob");
let alice = UserBuilder::<T>::default().generate_did().build("Alice");
let asset_id = create_sample_asset::<T>(&alice, true);
let bob_account: [u8; 32] = bob.account().encode().try_into().unwrap();
}: _(alice.origin, asset_id, true, bob_account.into())
}
70 changes: 70 additions & 0 deletions pallets/asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: AccountId32,
asset_id: AssetId,
freeze: bool,
},
}

/// Map each [`Ticker`] to its registration details ([`TickerRegistration`]).
Expand Down Expand Up @@ -646,6 +653,11 @@ pub mod pallet {
ValueQuery,
>;

/// Tracks if the account is frozen.
#[pallet::storage]
pub type FrozenAccounts<T: Config> =
StorageDoubleMap<_, Twox64Concat, AccountId32, Blake2_128Concat, AssetId, bool, ValueQuery>;

/// Storage version.
#[pallet::storage]
pub type StorageVersion<T: Config> = StorageValue<_, Version, ValueQuery>;
Expand Down Expand Up @@ -1833,6 +1845,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(<T as Config>::WeightInfo::set_address_frozen())]
pub fn set_address_frozen(
origin: OriginFor<T>,
asset_id: AssetId,
freeze: bool,
account: AccountId32,
) -> DispatchResult {
Self::base_set_address_frozen(origin, asset_id, freeze, account)
}
Comment on lines +1849 to +1859

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should implement this freeze feature for both accounts and portfolios (so take an AssetHolder target here). Also need to make sure that the freeze blocks same DID transfers (portfolio moves and transfer_funds).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The precompile can be limited to account for now. I am not sure if we want to commit to supporting Portfolios long-term (since accounts are more commonly used).

}

#[pallet::error]
Expand Down Expand Up @@ -1947,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 {
Expand Down Expand Up @@ -1994,6 +2020,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;
}
}

Expand Down Expand Up @@ -2994,6 +3021,31 @@ impl<T: AssetConfig> Pallet<T> {
Self::unverified_set_frozen_tokens(caller_did, asset_holder, asset_id, amount);
Ok(())
}

/// Sets whether `account` is frozen for transfers of `asset_id`.
fn base_set_address_frozen(
origin: T::RuntimeOrigin,
asset_id: AssetId,
freeze: bool,
account: AccountId32,
) -> DispatchResult {
let caller_did = ExternalAgents::<T>::ensure_perms(origin, &asset_id)?;

if freeze {
FrozenAccounts::<T>::insert(account.clone(), asset_id, true);
} else {
FrozenAccounts::<T>::remove(&account, &asset_id);
}

Self::deposit_event(Event::SetAccountFreeze {
caller_did,
account,
asset_id,
freeze,
});

Ok(())
}
}

//==========================================================================
Expand Down Expand Up @@ -3377,6 +3429,17 @@ impl<T: AssetConfig> Pallet<T> {
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::<T>::get(account_id, asset_id),
Error::<T>::InvalidTransferFrozenAccount
);
}
Comment thread
HenriqueNogara marked this conversation as resolved.
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,
Expand Down Expand Up @@ -3769,6 +3832,7 @@ impl<T: AssetConfig> Pallet<T> {
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)
Expand Down Expand Up @@ -3913,6 +3977,12 @@ impl<T: AssetConfig> Pallet<T> {
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::<T>::asset_holder_did(&asset_holder) {
Ok(did) => did,
Expand Down
4 changes: 2 additions & 2 deletions pallets/precompiles/src/interface/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use polymesh_primitives::WeightMeter;

use crate::common::{revert, revert_err, Common};
use crate::interface::FungibleAssetInterface;
use crate::interface::{ERR_ASSET_NOT_FOUND, ERR_INST_NOT_EXECUTED};
use crate::interface::{DECIMALS, ERR_ASSET_NOT_FOUND, ERR_INST_NOT_EXECUTED};
use crate::Config;

impl<T: Config> FungibleAssetInterface<T> {
Expand Down Expand Up @@ -320,6 +320,6 @@ impl<T: Config> FungibleAssetInterface<T> {
_asset_id: AssetId,
_env: &mut impl Ext<T = T>,
) -> Result<Vec<u8>, Error> {
Ok(IFungibleAsset::decimalsCall::abi_encode_returns(&6))
Ok(IFungibleAsset::decimalsCall::abi_encode_returns(&DECIMALS))
}
}
189 changes: 189 additions & 0 deletions pallets/precompiles/src/interface/erc3643.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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 <http://www.gnu.org/licenses/>.

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;
use pallet_revive::precompiles::Ext;

use pallet_asset::{AssetIdTicker, AssetNames};
use polymesh_precompiles::{IFungibleAsset, IFungibleAssetEvents};
use polymesh_primitives::asset::{AssetId, AssetName};
use polymesh_primitives::ticker::TICKER_LEN;
use polymesh_primitives::Ticker;

use crate::common::Common;
use crate::interface::FungibleAssetInterface;
use crate::interface::{DECIMALS, ERR_INVALID_SYMBOL};
use crate::Config;

impl<T: Config> FungibleAssetInterface<T> {
/// 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<T = T>) -> Result<Vec<u8>, Error> {
let caller = Common::<T>::caller(env)?;

Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::freeze { asset_id },
)?;

Common::<T>::deposit_event(
env,
IFungibleAssetEvents::Paused(IFungibleAsset::Paused {
userAddress: caller.address.0.into(),
}),
)?;
Ok(Vec::new())
}

/// 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<T = T>) -> Result<Vec<u8>, Error> {
let caller = Common::<T>::caller(env)?;

Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::unfreeze { asset_id },
)?;

Common::<T>::deposit_event(
env,
IFungibleAssetEvents::Unpaused(IFungibleAsset::Unpaused {
userAddress: caller.address.0.into(),
}),
)?;
Ok(Vec::new())
}

/// 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<T = T>,
) -> Result<Vec<u8>, Error> {
// Charges the additional `AssetIdTicker` read
env.charge(T::DbWeight::get().reads(1))?;

let caller = Common::<T>::caller(env)?;
let new_asset_name = AssetName::from(&call.name.as_bytes().to_vec());

Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::rename_asset {
asset_id,
asset_name: new_asset_name.clone(),
},
)?;

let ticker = AssetIdTicker::<T>::get(&asset_id).unwrap_or_default();
Common::<T>::deposit_event(
env,
IFungibleAssetEvents::UpdatedTokenInformation(
IFungibleAsset::UpdatedTokenInformation {
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(),
},
),
)?;
Ok(Vec::new())
}

/// 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<T = T>,
) -> Result<Vec<u8>, Error> {
// Charges the additional `AssetNames` read
env.charge(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 = Common::<T>::caller(env)?;

Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::register_unique_ticker { ticker },
)?;
Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::link_ticker_to_asset_id { ticker, asset_id },
)?;

let asset_name = AssetNames::<T>::get(&asset_id).unwrap_or_default();
Common::<T>::deposit_event(
env,
IFungibleAssetEvents::UpdatedTokenInformation(
IFungibleAsset::UpdatedTokenInformation {
newName: FixedBytes::from(keccak256(asset_name.0.as_slice())),
newSymbol: FixedBytes::from(keccak256(ticker.as_ref())),
newDecimals: DECIMALS,
newVersion: Default::default(),
newOnchainID: Default::default(),
},
),
)?;
Ok(Vec::new())
}

/// 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<T = T>,
) -> Result<Vec<u8>, Error> {
let caller = Common::<T>::caller(env)?;

let acc_to_freeze = Common::<T>::account_id32(call.account)?;

Common::<T>::call_runtime(
env,
caller.runtime_origin(),
pallet_asset::Call::<T>::set_address_frozen {
asset_id,
freeze: call.freeze,
account: acc_to_freeze,
},
)?;

Common::<T>::deposit_event(
env,
IFungibleAssetEvents::AddressFrozen(IFungibleAsset::AddressFrozen {
account: call.account,
freeze: call.freeze,
owner: caller.address.0.into(),
}),
)?;
Ok(Vec::new())
}
}
18 changes: 18 additions & 0 deletions pallets/precompiles/src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ use crate::common::{revert, revert_err, Common};
use crate::Config;

mod erc20;
mod erc3643;
mod erc7943;
mod polymesh_specific;

// ==================== Error Messages ====================
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_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<T>(PhantomData<T>);

Expand Down Expand Up @@ -75,6 +79,11 @@ impl<T: Config> Precompile for FungibleAssetInterface<T> {
| IFungibleAssetCalls::permit(_)
| IFungibleAssetCalls::forcedTransfer(_)
| IFungibleAssetCalls::setFrozenTokens(_)
| IFungibleAssetCalls::setName(_)
| IFungibleAssetCalls::setSymbol(_)
| IFungibleAssetCalls::pause(_)
| IFungibleAssetCalls::unpause(_)
| IFungibleAssetCalls::setAddressFrozen(_)
if env.is_read_only() =>
{
Err(Common::<T>::state_change_denied())
Expand Down Expand Up @@ -115,6 +124,15 @@ impl<T: Config> Precompile for FungibleAssetInterface<T> {
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)
}
}
}
}
Expand Down
Loading