Skip to content

Commit

Permalink
Add new rpc calls (#1650)
Browse files Browse the repository at this point in the history
* Add nft_trasfer_report, asset_transfer_report, execute_instruction_report

* Remove cdd check for controller transfer

* Add compliance_report rpc call

* Add helper function get_conditions_report

* Add missing types to schema

* Change rpc return type

* Add new definitions to schema

* linting

* Fix build.

* Fix name.

---------

Co-authored-by: Adam Dossa <adam.dossa@gmail.com>
Co-authored-by: Robert G. Jakabosky <rjakabosky+neopallium@neoawareness.com>
  • Loading branch information
3 people committed May 7, 2024
1 parent 2b3e32e commit 9e5fa26
Show file tree
Hide file tree
Showing 23 changed files with 1,691 additions and 678 deletions.
6 changes: 5 additions & 1 deletion pallets/asset/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ decl_error! {
/// Failed to transfer the asset - asset is frozen.
InvalidTransferFrozenAsset,
/// Failed to transfer an NFT - compliance failed.
InvalidTransferComplianceFailure
InvalidTransferComplianceFailure,
/// Failed to transfer the asset - receiver cdd is not valid.
InvalidTransferInvalidReceiverCDD,
/// Failed to transfer the asset - sender cdd is not valid.
InvalidTransferInvalidSenderCDD,
}
}
121 changes: 120 additions & 1 deletion pallets/asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ use sp_std::prelude::*;
use pallet_base::{
ensure_opt_string_limited, ensure_string_limited, try_next_pre, Error::CounterOverflow,
};
use pallet_portfolio::{Error as PortfolioError, PortfolioAssetBalances};
use polymesh_common_utilities::asset::AssetFnTrait;
use polymesh_common_utilities::compliance_manager::ComplianceFnConfig;
use polymesh_common_utilities::constants::*;
use polymesh_common_utilities::protocol_fee::{ChargeProtocolFee, ProtocolOp};
pub use polymesh_common_utilities::traits::asset::{Config, Event, RawEvent, WeightInfo};
use polymesh_common_utilities::traits::nft::NFTTrait;
use polymesh_common_utilities::with_transaction;

use polymesh_primitives::agent::AgentGroup;
use polymesh_primitives::asset::{
AssetName, AssetType, CheckpointId, CustomAssetTypeId, FundingRoundName,
Expand Down Expand Up @@ -1947,6 +1947,16 @@ impl<T: Config> Module<T> {
// Verifies that the asset is not frozen
ensure!(!Frozen::get(ticker), Error::<T>::InvalidTransferFrozenAsset);

ensure!(
Identity::<T>::has_valid_cdd(receiver_portfolio.did),
Error::<T>::InvalidTransferInvalidReceiverCDD
);

ensure!(
Identity::<T>::has_valid_cdd(sender_portfolio.did),
Error::<T>::InvalidTransferInvalidSenderCDD
);

// Verifies that the statistics restrictions are satisfied
Statistics::<T>::verify_transfer_restrictions(
ticker,
Expand All @@ -1972,6 +1982,115 @@ impl<T: Config> Module<T> {
Ok(())
}

/// Returns a vector containing all errors for the transfer. An empty vec means there's no error.
pub fn asset_transfer_report(
sender_portfolio: &PortfolioId,
receiver_portfolio: &PortfolioId,
ticker: &Ticker,
transfer_value: Balance,
skip_locked_check: bool,
weight_meter: &mut WeightMeter,
) -> Vec<DispatchError> {
let mut asset_transfer_errors = Vec::new();

// If the security token doesn't exist or if the token is an NFT, there's no point in assessing anything else
let security_token = {
match Tokens::try_get(ticker) {
Ok(security_token) => security_token,
Err(_) => return vec![Error::<T>::NoSuchAsset.into()],
}
};
if !security_token.asset_type.is_fungible() {
return vec![Error::<T>::UnexpectedNonFungibleToken.into()];
}

if let Err(e) = Self::ensure_token_granular(&security_token, &transfer_value) {
asset_transfer_errors.push(e);
}

let sender_current_balance = BalanceOf::get(ticker, &sender_portfolio.did);
if sender_current_balance < transfer_value {
asset_transfer_errors.push(Error::<T>::InsufficientBalance.into());
}

let receiver_current_balance = BalanceOf::get(ticker, &receiver_portfolio.did);
if receiver_current_balance
.checked_add(transfer_value)
.is_none()
{
asset_transfer_errors.push(Error::<T>::BalanceOverflow.into());
}

if sender_portfolio.did == receiver_portfolio.did {
asset_transfer_errors
.push(PortfolioError::<T>::InvalidTransferSenderIdMatchesReceiverId.into());
}

if let Err(e) = Portfolio::<T>::ensure_portfolio_validity(sender_portfolio) {
asset_transfer_errors.push(e);
}

if let Err(e) = Portfolio::<T>::ensure_portfolio_validity(receiver_portfolio) {
asset_transfer_errors.push(e);
}

if skip_locked_check {
if PortfolioAssetBalances::get(sender_portfolio, ticker) < transfer_value {
asset_transfer_errors
.push(PortfolioError::<T>::InsufficientPortfolioBalance.into());
}
} else {
if let Err(e) =
Portfolio::<T>::ensure_sufficient_balance(sender_portfolio, ticker, transfer_value)
{
asset_transfer_errors.push(e);
}
}

if !Identity::<T>::has_valid_cdd(receiver_portfolio.did) {
asset_transfer_errors.push(Error::<T>::InvalidTransferInvalidReceiverCDD.into());
}

if !Identity::<T>::has_valid_cdd(sender_portfolio.did) {
asset_transfer_errors.push(Error::<T>::InvalidTransferInvalidSenderCDD.into());
}

if Frozen::get(ticker) {
asset_transfer_errors.push(Error::<T>::InvalidTransferFrozenAsset.into());
}

if let Err(e) = Statistics::<T>::verify_transfer_restrictions(
ticker,
&sender_portfolio.did,
&receiver_portfolio.did,
sender_current_balance,
receiver_current_balance,
transfer_value,
security_token.total_supply,
weight_meter,
) {
asset_transfer_errors.push(e);
}

match T::ComplianceManager::is_compliant(
ticker,
sender_portfolio.did,
receiver_portfolio.did,
weight_meter,
) {
Ok(is_compliant) => {
if !is_compliant {
asset_transfer_errors.push(Error::<T>::InvalidTransferComplianceFailure.into());
}
}
Err(e) => {
asset_transfer_errors.push(e);
}
}

asset_transfer_errors
}

// Get the total supply of an asset `id`.
pub fn total_supply(ticker: &Ticker) -> Balance {
Self::token_details(ticker)
Expand Down
87 changes: 86 additions & 1 deletion pallets/compliance-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ pub use polymesh_common_utilities::traits::compliance_manager::{
ComplianceFnConfig, Config, Event, WeightInfo,
};
use polymesh_primitives::compliance_manager::{
AssetCompliance, AssetComplianceResult, ComplianceRequirement, ConditionResult,
AssetCompliance, AssetComplianceResult, ComplianceReport, ComplianceRequirement,
ConditionReport, ConditionResult, RequirementReport,
};
use polymesh_primitives::{
proposition, storage_migration_ver, Claim, Condition, ConditionType, Context, IdentityId,
Expand Down Expand Up @@ -773,3 +774,87 @@ impl<T: Config> ComplianceFnConfig for Module<T> {
benchmarking::setup_ticker_compliance::<T>(caller_did, ticker, n, pause_compliance);
}
}

//==========================================================================
// All RPC functions!
//==========================================================================

impl<T: Config> Module<T> {
/// Returns a [`ComplianceReport`] for the given `ticker`.
pub fn compliance_report(
ticker: &Ticker,
sender_identity: &IdentityId,
receiver_identity: &IdentityId,
weight_meter: &mut WeightMeter,
) -> Result<ComplianceReport, DispatchError> {
let asset_compliance = Self::asset_compliance(ticker);

if asset_compliance.requirements.is_empty() {
return Ok(ComplianceReport::new(
Vec::new(),
true,
asset_compliance.paused,
));
}

let mut any_requirement_satisfied = false;
// Get the [`RequirementReport`] for each requirement
let mut requirements_report = Vec::new();
for requirement in asset_compliance.requirements {
// The requirement is satisfied only if all sender and receiver conditions hold.
let mut requirement_satisfied = true;
// Get the [`ConditionrReport`] for all sender conditions
let sender_conditions_report = Self::get_conditions_report(
ticker,
*sender_identity,
requirement.sender_conditions,
&mut requirement_satisfied,
weight_meter,
)?;
// Get the [`ConditionrReport`] for all receiver conditions
let receiver_conditions_report = Self::get_conditions_report(
ticker,
*receiver_identity,
requirement.receiver_conditions,
&mut requirement_satisfied,
weight_meter,
)?;
requirements_report.push(RequirementReport::new(
sender_conditions_report,
receiver_conditions_report,
requirement.id,
requirement_satisfied,
));
any_requirement_satisfied = any_requirement_satisfied || requirement_satisfied;
}

Ok(ComplianceReport::new(
requirements_report,
any_requirement_satisfied,
asset_compliance.paused,
))
}

/// Returns all [`ConditionReport`] for the given `conditions`.
fn get_conditions_report(
ticker: &Ticker,
identity: IdentityId,
conditions: Vec<Condition>,
requirement_satisfied: &mut bool,
weight_meter: &mut WeightMeter,
) -> Result<Vec<ConditionReport>, DispatchError> {
let mut conditions_report = Vec::new();
for condition in conditions {
let is_condition_satisfied = Self::is_condition_satisfied(
ticker,
identity,
&condition,
&mut None,
weight_meter,
)?;
conditions_report.push(ConditionReport::new(condition, is_condition_satisfied));
*requirement_satisfied = *requirement_satisfied && is_condition_satisfied;
}
Ok(conditions_report)
}
}

0 comments on commit 9e5fa26

Please sign in to comment.