Skip to content
This repository was archived by the owner on Jan 16, 2025. It is now read-only.

Commit 599bbda

Browse files
fix matching engine double order issue (#406)
1 parent 80f52e8 commit 599bbda

File tree

3 files changed

+49
-18
lines changed

3 files changed

+49
-18
lines changed

daml/Marketplace/Trading/Model.daml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ template Order
8686
executions : [Execution]
8787
remainingQuantity : Decimal
8888
collateral : TradeCollateral
89+
createdAt : Time
8990
where
9091
signatory operator, provider, customer
9192
observer case collateral of

daml/Marketplace/Trading/Service.daml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ template Service
6767
return details with optExchangeFee = Some newDepositCid
6868
None -> return details
6969

70+
createdAt <- getTime
71+
7072
case collateral of
7173
(Order.Collateral depositCid) -> do
7274
details <- detailsWithDepositedFees

triggers/daml/MatchingEngine.daml

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import DA.Optional
88
import DA.Foldable hiding (elem, null, length)
99
import DA.List
1010
import Daml.Trigger
11+
import DA.Map (Map)
12+
import DA.Map qualified as Map
1113

1214
import qualified Marketplace.Trading.Model as Order
1315
import qualified Marketplace.Trading.Service as Service
@@ -18,11 +20,16 @@ import qualified Marketplace.Trading.Matching.Service as Matching
1820
import Utils
1921
import DA.Finance.Asset (AssetDeposit)
2022

21-
type CurrentOrderId = Int
23+
type OrderMap = Map (Party, Text) (ContractPair Order.T)
2224

23-
handleMatching : Trigger CurrentOrderId
25+
data MatchingState = MatchingState with
26+
currentOrderId : Int
27+
remainingOrders : OrderMap
28+
deriving (Show, Eq)
29+
30+
handleMatching : Trigger MatchingState
2431
handleMatching = Trigger
25-
{ initialize = return 0
32+
{ initialize = return $ MatchingState with currentOrderId = 0; remainingOrders = mempty
2633
, updateState = \_ -> pure ()
2734
, rule = handleMatchingRule
2835
, registeredTemplates = RegisteredTemplates [ registeredTemplate @ListingService.Service
@@ -36,7 +43,12 @@ handleMatching = Trigger
3643
, heartbeat = None
3744
}
3845

39-
handleMatchingRule : Party -> TriggerA CurrentOrderId ()
46+
modifyRemainingOrders : (OrderMap -> OrderMap) -> TriggerA MatchingState ()
47+
modifyRemainingOrders fn = do
48+
state <- get
49+
put $ state with remainingOrders = fn state.remainingOrders
50+
51+
handleMatchingRule : Party -> TriggerA MatchingState ()
4052
handleMatchingRule party = do
4153
debug "Running matching rule..."
4254

@@ -46,7 +58,7 @@ handleMatchingRule party = do
4658
[(_, feeSchedule):xs] -> feeSchedule.currentFee.amount
4759
_ -> 0.0
4860

49-
-- Acknowledge all 'Order.Request' and update current ID
61+
-- Acknowledge all 'Order.Request' and update current ID
5062
orderRequests <- query @Service.CreateOrderRequest
5163
deposits <- query @AssetDeposit
5264
forA_ orderRequests \(cid,or) -> do
@@ -56,10 +68,10 @@ handleMatchingRule party = do
5668
if exchangeFeeAmount < currentFee
5769
then void $ emitExerciseCmd cid Service.RejectRequest with errorCode = 790; errorMessage = "Fee requirement not met"
5870
else do
59-
currentOrderId <- get
60-
emitExerciseCmd cid Service.AcknowledgeRequest with providerOrderId = show currentOrderId
71+
state <- get
72+
emitExerciseCmd cid Service.AcknowledgeRequest with providerOrderId = show $ state.currentOrderId
6173
debug "Acknowledging order"
62-
modify (+1)
74+
put $ state with currentOrderId = state.currentOrderId + 1
6375

6476
time <- getTime
6577
-- Acknowledge all 'Order.CancelRequest'
@@ -78,20 +90,34 @@ handleMatchingRule party = do
7890

7991
-- Check for matches on all 'Order'
8092
orders <- query @Order.T
81-
forA_ orders (handleOrder party orders)
93+
94+
state <- get
95+
let remainingOrders = Map.fromList $ map (\op -> (key op._2, op))
96+
$ filter (\(_,o) -> shouldProcess o) orders
97+
put $ state with remainingOrders
98+
matchOrders party $ sortOn (\(_,o) -> o.createdAt) $ Map.values remainingOrders
99+
100+
-- |Match all orders removing orders that are matched or do not have any match
101+
matchOrders : Party -> [ContractPair Order.T] -> TriggerA MatchingState ()
102+
matchOrders party [] = return ()
103+
matchOrders party (order::orders) = do
104+
matchOrder party orders order
105+
newOrders <- sortOn (\(_,o) -> o.createdAt) . Map.values . remainingOrders <$> get
106+
matchOrders party newOrders
107+
108+
-- |Order can be matched
109+
shouldProcess : Order.T -> Bool
110+
shouldProcess o = o.status `elem` [Order.PendingExecution, Order.PartiallyExecuted]
82111

83112
-- |Check for crossing orders. If found, fill both orders. Matches by price.
84-
handleOrder : Party -> [ContractPair Order.T] -> ContractPair Order.T -> TriggerA CurrentOrderId ()
85-
handleOrder party orders op@(orderCid, order) = do
113+
matchOrder : Party -> [ContractPair Order.T] -> ContractPair Order.T -> TriggerA MatchingState ()
114+
matchOrder party orders op@(orderCid, order) = do
86115
debug $ "Handling order: " <> show order
87-
88-
case order.status of
89-
Order.PendingExecution -> processOrder op
90-
Order.PartiallyExecuted -> processOrder op
91-
_ -> debug $ "Ignoring order in status: " <> show order.status
116+
processOrder op
117+
modifyRemainingOrders (Map.delete $ key order)
92118

93119
where
94-
processOrder : ContractPair Order.T -> TriggerA CurrentOrderId ()
120+
processOrder : ContractPair Order.T -> TriggerA MatchingState ()
95121
processOrder (orderCid, order) = do
96122
let oppositelimitOrders = sortOn (\(_,x) -> case x.details.orderType of
97123
Order.Limit price -> price
@@ -130,7 +156,7 @@ handleOrder party orders op@(orderCid, order) = do
130156
Order.Limit _ -> True
131157
_ -> False
132158

133-
fill : ContractPair Order.T -> ContractPair Order.T -> TriggerA CurrentOrderId ()
159+
fill : ContractPair Order.T -> ContractPair Order.T -> TriggerA MatchingState ()
134160
fill (aggressiveCid, aggressive) (passiveCid, passive) = do
135161
debug $ "Matching order: " <> show aggressive.details.id <> " to : " <> show passive.details.id
136162

@@ -149,4 +175,6 @@ handleOrder party orders op@(orderCid, order) = do
149175

150176
emitCommands [exerciseByKeyCmd @Matching.Service aggressive.provider Matching.MatchOrders with execution] [toAnyContractId aggressiveCid, toAnyContractId passiveCid]
151177

178+
modifyRemainingOrders (Map.delete $ key passive)
179+
152180
return ()

0 commit comments

Comments
 (0)