Add controller_transfer_to - #2001
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
It introduces new extrinsics/ABI but leaves generated artifacts and weights in a likely-stale/placeholder state (and lacks runtime tests for the new controller_transfer_to path).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends Polymesh’s forced/controller transfer and freezing capabilities by adding destination-addressable controller transfers (controller_transfer_to) for both fungible assets and NFTs, and by introducing incremental freeze/unfreeze operations for fungible assets (including EVM precompile surface-area updates).
Changes:
- Added
controller_transfer_toextrinsics (asset + NFT) and updated the fungible-asset precompileforcedTransferflow to target an explicit destination. - Added
freeze_partial_tokens/unfreeze_partial_tokensextrinsics and exposed them via the ERC-3643 precompile (plus integration tests). - Adjusted controller-transfer unfreeze behavior for frozen balances and added runtime tests; added new benchmarks/weights entries (with two weights currently left as placeholders).
File summaries
| File | Description |
|---|---|
| precompiles/src/interfaces/FungibleAssetStub.sol | Updates Solidity stub ABI: forcedTransfer(from,to,amount) and adds partial freeze/unfreeze funcs + events. |
| pallets/weights/src/pallet_nft.rs | Adds weight for controller_transfer_to(n). |
| pallets/weights/src/pallet_asset.rs | Adds weights for new extrinsics; partial freeze/unfreeze weights currently marked as placeholders. |
| pallets/runtime/tests/src/asset_pallet/controller_transfer.rs | Adds tests for controller-transfer freeze/unfreeze shortfall behavior and refactors agent setup helper. |
| pallets/precompiles/src/interface/fungible_asset/mod.rs | Routes new ABI calls to the new precompile handlers. |
| pallets/precompiles/src/interface/fungible_asset/erc7943.rs | Updates forced_transfer to accept to and dispatch controller_transfer_to. |
| pallets/precompiles/src/interface/fungible_asset/erc3643.rs | Adds precompile handlers for partial freeze/unfreeze and emits EVM-side events. |
| pallets/nft/src/lib.rs | Adds controller_transfer_to extrinsic + base implementation and a new error for receiver affirmation requirement. |
| pallets/nft/src/benchmarking.rs | Adds benchmark for controller_transfer_to. |
| pallets/asset/src/lib.rs | Adds controller_transfer_to, partial freeze/unfreeze extrinsics, new errors, and updates controller-transfer unfreeze logic. |
| pallets/asset/src/benchmarking.rs | Adds benchmarks for controller_transfer_to, partial freeze, and partial unfreeze. |
| integration/tests/utility_calls.rs | Formatting-only changes. |
| integration/tests/sto.rs | Formatting-only changes. |
| integration/tests/statistics_enforcement.rs | Formatting-only changes. |
| integration/tests/settlement_venues.rs | Formatting-only changes. |
| integration/tests/settlement_scheduling.rs | Formatting-only changes. |
| integration/tests/settlement_mediators.rs | Formatting-only changes. |
| integration/tests/revive_erc721.rs | Formatting-only changes. |
| integration/tests/revive_erc3643.rs | Adds integration tests for partial freeze/unfreeze via the ERC-3643 precompile. |
| integration/tests/relayer_negative.rs | Formatting-only changes. |
| integration/tests/portfolio_custody.rs | Formatting-only changes. |
| integration/tests/nft.rs | Formatting-only changes. |
| integration/tests/identity_lifecycle.rs | Formatting-only changes. |
| integration/tests/economics.rs | Formatting-only changes. |
| integration/tests/corporate_ballot.rs | Formatting-only changes. |
| integration/tests/compliance_enforcement.rs | Formatting-only changes. |
| integration/tests/checkpoints.rs | Formatting-only changes. |
| integration/tests/capital_distribution.rs | Formatting-only changes. |
| integration/tests/ca_extended.rs | Formatting-only changes. |
| integration/tests/asset_controls.rs | Formatting-only changes. |
| integration/src/lib.rs | Minor pattern-match refactor. |
| integration/src/erc20_helper.rs | Adds ERC-3643 helper methods for partial freeze/unfreeze calls. |
Review details
Suppressed comments (1)
pallets/weights/src/pallet_asset.rs:963
- Same issue for
unfreeze_partial_tokens: the weight is a TODO placeholder despite a benchmark being added inpallets/asset/src/benchmarking.rs. Please regenerate the weights output so this extrinsic’s weight reflects the actual benchmark results.
// TODO: Placeholder weight copied from `set_frozen_tokens`, pending its own benchmark run.
fn unfreeze_partial_tokens() -> Weight {
// 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))
}
- Files reviewed: 32/33 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| #[test] | ||
| fn controller_transfer_within_free_balance_does_not_unfreeze() { | ||
| ExtBuilder::default().build().execute_with(|| { | ||
| let bob = User::new(Sr25519Keyring::Bob); | ||
| let alice = User::new(Sr25519Keyring::Alice); | ||
| let alice_default_portfolio = PortfolioId::default_portfolio(alice.did); | ||
| let alice_holder = AssetHolder::from(alice_default_portfolio.clone()); |
772d3b7 to
ef5d7f8
Compare
9e991a7 to
a465f0a
Compare
| source: AssetHolder, | ||
| destination: AssetHolder, | ||
| ) -> DispatchResult { | ||
| let mut weight_meter = WeightMeter::max_limit_no_minimum(); |
There was a problem hiding this comment.
We shouldn't be using max_limit for weight meters. Lets try to improve this later.
| ensure!( | ||
| Self::skip_asset_holder_affirmation(&destination, &asset_id)?, | ||
| Error::<T>::ReceiverAffirmationRequired | ||
| ); |
There was a problem hiding this comment.
If the destination requires affirmations, we should then check if the caller is permissioned (owner/custodian or caller_account == Holder::account). Since they might be doing a force transfer to one of their own accounts/portfolios.
There was a problem hiding this comment.
Also need to make sure the benchmark transfers to a destination with required affirmations on (with the caller permissioned for dest).
| AssetPallet::<T>::skip_asset_holder_affirmation(&destination, nfts.asset_id())?, | ||
| Error::<T>::ReceiverAffirmationRequired | ||
| ); |
There was a problem hiding this comment.
Again should check if the caller is permissioned for the dest.
There was a problem hiding this comment.
The benchmarks need to cover this too.
| }: _(alice.origin.clone(), nfts.clone(), bob_holdings.clone(), alice_holdings.clone()) | ||
| verify { |
There was a problem hiding this comment.
The benchmarks for asset agent calls, we should be using an agent instead of the issuer (I think the cost is a little higher).
| // Only unfreeze tokens if there's not enough free_balance | ||
| let free_balance = sender_current_balance.saturating_sub(frozen_balance); | ||
| if transfer_value > free_balance { |
There was a problem hiding this comment.
This needs to use the holder's current balance, not the DID level balance.
changelog
new features
controller_transfer_to,freeze_partial_tokensandunfreeze_partial_tokens;