CIP112: `settleBatchImpl`: forward each authorizer's own context to its `Allocation_Settle`

Open
#82 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
haskell

Research direction

Start with packages/token/token-cip112-utils/daml/OpenZeppelin/Token/Cip112/Utils/Registry.daml and the testSettleBatchRedactsBatchMeta test in test/token-cip112-v1-test/daml/OpenZeppelin/Token/Cip112/V1/Test.daml. Read the existing settleBatchImpl behavior and its callers, then verify that a selector forwards only the matching authorizer's context and that the default forwards none. Done when the utility, README, version, changelog, TokenRules, and sandbox privacy test reflect the stated conditions.

Written by the indexing model from the issue text.

Description

Related M-01

Problem

settleBatchImpl sends every Allocation_Settle an empty ExtraArgs (audit 1 L06). That protects privacy, but it means a registry can't supply per-allocation context on the settlement path.
Every other workflow choice receives its submitter's extraArgs directly:

Allocation_Settle is the exception: settleBatchImpl builds its arguments, so it only ever sees the empty ExtraArgs. Any consumer rule that needs a contract at call time can't be enforced on settlement.

We hit this in examples/token/token-cip112-custom. Its compliance freeze is a frozen flag on MyHolding. Changing the flag re-creates the holding under a new id and orphans any pending transfer instruction or allocation that names it in lockedHoldingCids (audit M-01). The interim fix refuses to freeze a holding with a live lock, so funds in flight can't be frozen, and that is exactly when a sanctions freeze matters.

The fix is to keep the freeze in a per-account status contract, so holdings are never re-created. Without contract keys, MyHolding_Spend can't look that contract up, and a stored id would go stale on the next freeze. The caller therefore has to pass the current status id in the choice context. That works on every path except settlement, where the context is always empty.

Proposal

Give settleBatchImpl a selector that chooses which part of the batch context reaches each allocation:

settleBatchImpl
  : PinAllocation
  -> (AllocationV2.AllocationView -> ChoiceContext -> ChoiceContext)
  -> Party -> ContractId AllocationV2.SettlementFactory
  -> AllocationV2.SettlementFactory_SettleBatch
  -> Update AllocationV2.SettlementFactory_SettleBatchResult
  • meta stays empty.
  • The default TokenRules passes \_ _ -> emptyChoiceContext, so its behaviour doesn't change.
  • A registry that needs per-authorizer context passes a selector that keeps only the entries for allocView.allocation.authorizer.
Why it preserves L06

A party sees only the actions it is an informee of, plus everything nested under them. It never sees the action that contains them.

  • The batch context is on the SettlementFactory_SettleBatch exercise. Its informees are the admin, the executors and the choice observers. With settleBatchExtraObservers = [], which is what both TokenRules and MyRules use, no authorizer is an informee.
  • Each authorizer sees only its own Allocation_Settle. Under the selector, that node carries only the authorizer's own entries.
  • Other authorizers see only leaf nodes from A's settle. Where B is an informee inside A's settle (a received holding, a holdings-change event), B sees those nodes and what's under them, not the parent's arguments.

Two conditions, to be documented on settleBatchImpl:

  1. The batch's extra observers must stay empty. Upstream's public-asset default makes every leg party an observer of the batch.
  2. The selector must key on the allocation's authorizer.
How the example would use it
  • MyAccountStatus with admin; account; roles; frozen is signed by the admin and the compliance officer, and the officer freezes and unfreezes it. MyHolding loses frozen, MyHolding_Freeze and MyHolding_Unfreeze.
  • myOps takes a ChoiceContext. MyHolding_Spend fetches the status contract named there and refuses unless it is active, matches the holding's account and admin, and has frozen = False.
  • Freezing archives the old status contract, so the only live one says frozen. Passing the old id fails the fetch, and passing nothing fails. Holding ids never change, so pending contracts stay valid and complete once the account is unfrozen.
  • Every path passes its extraArgs.context. Settlement gets its context through the new selector, which forwards only the authorizer's status entry. A direct Allocation_Settle is checked the same way.
Tasks

token-cip112-utils (this issue)

  • Add the selector parameter to settleBatchImpl and rewrite its L06 comment to state both conditions.
  • Update the token-cip112-utils README, bump the version (breaking signature), and add a CHANGELOG.md entry.
  • Make TokenRules pass a selector that forwards nothing.
  • Extend testSettleBatchRedactsBatchMeta: with per-authorizer entries in the batch context, each Allocation_Settle carries only its own authorizer's entry.
  • Sandbox test: an authorizer's transaction tree contains neither the batch context nor another authorizer's entries.

Example (after the utils change)

  • Add MyAccountStatus, give myOps a context parameter, thread the context through all paths, and pass the per-authorizer selector in MyRules.
  • Revert the interim lock refusal (requireLockReleased, testLockedHoldingCannotBeFrozen, README notes).
  • Tests: freeze an account with a live locked transfer and allocation, check that every path fails, unfreeze, check that the workflow completes. Check that a stale status id is refused.
Dominant language
Haskell
Stars
5
Forks
0
Avg merge
1d 36m
Merged PRs (30d)
11

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from OpenZeppelin/canton-contracts

All issues in OpenZeppelin/canton-contracts

Similar issues

More Haskell issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.