44module Splice.AmuletAllocation (
55 AmuletAllocation(..),
66 allocationToTwoStepTransfer,
7- allocationSender,
87) where
98
9+ import DA.Assert((===), (=/=))
1010import DA.Text as Text
1111import DA.TextMap qualified as TextMap
12- import DA.List (dedupSort)
12+ import DA.List ((\\), dedupSort)
13+ import DA.Optional(fromSome)
1314
1415import Splice.Api.Token.MetadataV1
1516import Splice.Api.Token.HoldingV2
@@ -26,21 +27,32 @@ template AmuletAllocation
2627 with
2728 lockedAmulet : ContractId LockedAmulet -- ^ Locked amulet that holds the funds for the allocation
2829 allocation : AllocationSpecification
30+ sender : Party
31+ admin : Party
2932 where
30- signatory allocationInstrumentAdmin allocation, allocationSender allocation
33+ signatory admin, sender
3134 observer allocation.settlement.executor
3235
33- -- Only allow a single sender.
34- ensure all (\(_,tl) -> tl.sender == allocationSender allocation) (TextMap.toList allocation.transferLegs)
36+ ensure
37+ all
38+ (\(_,tl) -> (
39+ -- Sender needs to appear as sender or receiver of each leg.
40+ sender `elem` [tl.sender, tl.receiver])
41+ -- Only one admin Id allowed.
42+ && tl.instrumentId.admin == admin)
43+ (TextMap.toList allocation.transferLegs)
3544
3645 interface instance Allocation for AmuletAllocation where
3746 view = AllocationView with
3847 allocation
3948 holdingCids = [toInterfaceContractId lockedAmulet]
4049 meta = emptyMetadata
41- transferExtraAuth = [allocationSender allocation]
50+ senders = [sender]
51+ requiredReceiverAuth = (defaultAllocationControllers allocation) \\ (sender::allocationControllers allocation)
4252
43- allocation_executeTransferImpl _self Allocation_ExecuteTransfer{..} = transferAmuletAllocation this extraArgs
53+ allocation_executeTransferImpl self Allocation_ExecuteTransfer{..} = case extraAuth of
54+ [] -> transferAmuletAllocation this extraArgs
55+ ea -> collectAuthAndSettle (fromInterfaceContractId self) extraArgs ea []
4456
4557 allocation_withdrawImpl _self Allocation_Withdraw{..} = do
4658 senderHoldingCids <- unlockAmuletAllocation this extraArgs
@@ -56,7 +68,12 @@ template AmuletAllocation
5668 senderHoldingCids
5769 meta = emptyMetadata
5870
59- allocation_executeAuthorizeIncomingImpl = error "unimplemented"
71+ allocation_executeAuthorizeIncomingImpl _self Allocation_AuthorizeIncoming{..} = do
72+ cid <- create AmuletAllocationTransferAuthorization with
73+ allocation
74+ receiver = sender
75+ admin
76+ return $ toInterfaceContractId cid
6077
6178 interface instance AllocationV1.Allocation for AmuletAllocation where
6279 view = allocation_view_v2_to_v1 (view (toInterface @Allocation this))
@@ -65,46 +82,53 @@ template AmuletAllocation
6582 AllocationV1.allocation_withdrawImpl = allocation_v1_withdrawImpl (toInterface @Allocation this)
6683 AllocationV1.allocation_cancelImpl = allocation_v1_cancelImpl (toInterface @Allocation this)
6784
68- allocationInstrumentAdmin : AllocationSpecification -> Party
69- allocationInstrumentAdmin AllocationSpecification{..} =
70- let tl::_ = (TextMap.toList transferLegs)
71- in tl._2.instrumentId.admin
85+ choice AmuletAllocation_InternalSettleWithExtraAuth : Allocation_ExecuteTransferResult
86+ with
87+ extraArgs : ExtraArgs
88+ extraControllers : [Party]
89+ controller extraControllers ++ allocationControllers allocation
90+ do transferAmuletAllocation this extraArgs
7291
73- -- Amulet only supports a single sender!
74- allocationSender : AllocationSpecification -> Party
75- allocationSender AllocationSpecification{..} =
76- let tl::_ = (TextMap.toList transferLegs)
77- in tl._2.sender
7892
79- allocationInstrumentReceivers : AllocationSpecification -> [Party]
80- allocationInstrumentReceivers AllocationSpecification{..} =
93+
94+ allocationReceivers : AllocationSpecification -> [Party]
95+ allocationReceivers AllocationSpecification{..} =
8196 dedupSort $ map ((.receiver) . snd) (TextMap.toList transferLegs)
8297
8398-- Allocation usage
8499-------------------
85100
86- allocationToTwoStepTransfer : AllocationSpecification -> TwoStepTransfer
87- allocationToTwoStepTransfer allocation =
101+ allocationToTwoStepTransfer : Party -> Party -> AllocationSpecification -> TwoStepTransfer
102+ allocationToTwoStepTransfer sender admin allocation =
88103 TwoStepTransfer with
89- dso = transferLeg.instrumentId.admin
90- sender = transferLeg.sender
91- receiver = transferLeg.receiver
92- amount = transferLeg.amount
104+ dso = admin
105+ sender = sender
106+ outputs
93107 provider = allocation.settlement.executor
94108 transferBefore = allocation.settlement.settleBefore
95109 transferBeforeDeadline = "allocation.settlement.settleBefore"
96110 allowFeaturing = True
97111 lockContext = Text.implode
98112 -- We don't show more context to avoid bloating the response here.
99- ["allocation for transfer leg ", show transferLegId, " to ", show transferLeg.receiver ]
113+ ["allocation for settlement ", allocation.settlement.settlementRef.id ]
100114 where
101- (transferLegId, transferLeg) = case TextMap.toList allocation.transferLegs of
102- [tl] -> tl
103- _ -> error "Only one leg supported" -- TODO.
115+ senderLegs = filter (\tl -> tl.sender == sender) $ map snd (TextMap.toList allocation.transferLegs)
116+ outputs = map (\tl -> (tl.receiver, tl.amount)) senderLegs
117+
118+ collectAuthAndSettle : ContractId AmuletAllocation -> ExtraArgs -> [ContractId AllocationTransferAuthorization] -> [Party] -> Update Allocation_ExecuteTransferResult
119+ collectAuthAndSettle allocationCid extraArgs extraAuth extraControllers = do
120+ case extraAuth of
121+ eaCid::eas -> exercise (fromInterfaceContractId @AmuletAllocationTransferAuthorization eaCid) AmuletAllocationTransferAuthorization_AuthorizeTransfer with
122+ extraAuth = eas
123+ extraControllers
124+ ..
125+ [] -> exercise allocationCid AmuletAllocation_InternalSettleWithExtraAuth with
126+ extraArgs
127+ extraControllers
104128
105129transferAmuletAllocation : AmuletAllocation -> ExtraArgs -> Update Allocation_ExecuteTransferResult
106130transferAmuletAllocation amuletAllocation extraArgs = do
107- let twoStepTransfer = allocationToTwoStepTransfer amuletAllocation.allocation
131+ let twoStepTransfer = allocationToTwoStepTransfer amuletAllocation.sender amuletAllocation.admin amuletAllocation. allocation
108132 (senderHoldingCids, receiverHoldingCids, meta) <-
109133 executeTwoStepTransfer twoStepTransfer amuletAllocation.lockedAmulet extraArgs
110134 pure Allocation_ExecuteTransferResult
@@ -115,5 +139,40 @@ transferAmuletAllocation amuletAllocation extraArgs = do
115139
116140unlockAmuletAllocation : AmuletAllocation -> ExtraArgs -> Update [ContractId Holding]
117141unlockAmuletAllocation amuletAllocation extraArgs = do
118- let twoStepTransfer = allocationToTwoStepTransfer amuletAllocation.allocation
142+ let twoStepTransfer = allocationToTwoStepTransfer amuletAllocation.sender amuletAllocation.admin amuletAllocation. allocation
119143 abortTwoStepTransfer twoStepTransfer amuletAllocation.lockedAmulet extraArgs
144+
145+ template AmuletAllocationTransferAuthorization
146+ with
147+ allocation : AllocationSpecification
148+ receiver : Party
149+ admin : Party
150+ where
151+ signatory admin, receiver, allocation.settlement.executor
152+
153+ interface instance AllocationTransferAuthorization for AmuletAllocationTransferAuthorization where
154+ view = AllocationTransferAuthorizationView with
155+ allocation
156+ receiver
157+ admin
158+
159+ choice AmuletAllocationTransferAuthorization_AuthorizeTransfer : Allocation_ExecuteTransferResult
160+ with
161+ allocationCid : ContractId AmuletAllocation
162+ extraArgs : ExtraArgs
163+ extraAuth : [ContractId AllocationTransferAuthorization]
164+ extraControllers : [Party]
165+ controller extraControllers ++ allocation.settlement.executor :: fromSome (allocation.settlement.controllerOverride) -- should never be None if this is called.
166+ do
167+ -- Validate that the receiver is only a receiver on a matching settlement
168+ amuletAllocation <- fetch allocationCid
169+ let allocation' = amuletAllocation.allocation
170+ allocation === allocation'
171+ admin === amuletAllocation.admin
172+ receiver =/= amuletAllocation.sender
173+ let receivers = allocationReceivers allocation
174+ assertMsg ("Receiver " <> show receiver <> " not found in receivers " <> show receivers)
175+ (receiver `elem` receivers)
176+ -- Transfer with added authority.
177+ collectAuthAndSettle allocationCid extraArgs extraAuth (receiver::extraControllers)
178+
0 commit comments