CIP112: `settleBatchImpl`: forward each authorizer's own context to its `Allocation_Settle`
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
- Domain
- backend-api-design, databases
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:
- the receiver's on
TransferInstruction_Accept - the authorizer's on
AllocationFactory_Allocate - the executors' or admin's on
Allocation_Cancel.
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
metastays empty.- The default
TokenRulespasses\_ _ -> 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_SettleBatchexercise. Its informees are the admin, the executors and the choice observers. WithsettleBatchExtraObservers = [], which is what bothTokenRulesandMyRulesuse, 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:
- The batch's extra observers must stay empty. Upstream's public-asset default makes every leg party an observer of the batch.
- The selector must key on the allocation's authorizer.
How the example would use it
MyAccountStatus with admin; account; roles; frozenis signed by the admin and the compliance officer, and the officer freezes and unfreezes it.MyHoldinglosesfrozen,MyHolding_FreezeandMyHolding_Unfreeze.myOpstakes aChoiceContext.MyHolding_Spendfetches the status contract named there and refuses unless it is active, matches the holding's account and admin, and hasfrozen = 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 directAllocation_Settleis checked the same way.
Tasks
token-cip112-utils (this issue)
- Add the selector parameter to
settleBatchImpland rewrite its L06 comment to state both conditions. - Update the
token-cip112-utilsREADME, bump the version (breaking signature), and add aCHANGELOG.mdentry. - Make
TokenRulespass a selector that forwards nothing. - Extend
testSettleBatchRedactsBatchMeta: with per-authorizer entries in the batch context, eachAllocation_Settlecarries 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, givemyOpsa context parameter, thread the context through all paths, and pass the per-authorizer selector inMyRules. - 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
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from OpenZeppelin/canton-contracts
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
OpenZeppelin/canton-contracts#92 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
OpenZeppelin/canton-contracts#88 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
OpenZeppelin/canton-contracts#86 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
OpenZeppelin/canton-contracts#96 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
OpenZeppelin/canton-contracts#95 ·
Maintainers usually reply within 1 day
All issues in OpenZeppelin/canton-contracts
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 75/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
IntersectMBO/cardano-ledger#6276 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
hasura/graphql-engine#10954 ·
Maintainers usually reply within 1 day