@@ -16,10 +16,7 @@ contract StablecoinExchange is IStablecoinExchange {
1616 /// @notice Price scaling factor (5 decimal places for 0.1 bps precision)
1717 uint32 public constant PRICE_SCALE = 100_000 ;
1818
19- enum Side {
20- Bid,
21- Ask
22- }
19+ event PairCreated (bytes32 indexed key , address indexed base , address indexed quote );
2320
2421 /// @notice Represents a price level in the orderbook with a doubly-linked list of orders
2522 /// @dev Orders are maintained in FIFO order at each tick level
@@ -39,7 +36,7 @@ contract StablecoinExchange is IStablecoinExchange {
3936 /// Orderbook key
4037 bytes32 bookKey;
4138 /// Bid or ask indicator
42- Side side ;
39+ bool isBid ;
4340 /// Price tick
4441 int16 tick;
4542 /// Original order amount
@@ -80,7 +77,6 @@ contract StablecoinExchange is IStablecoinExchange {
8077 /*//////////////////////////////////////////////////////////////
8178 STORAGE
8279 //////////////////////////////////////////////////////////////*/
83- LinkingUSD public immutable linkingToken;
8480
8581 /// Mapping of pair key to orderbook
8682 mapping (bytes32 pairKey = > Orderbook orderbook ) internal books;
@@ -92,17 +88,9 @@ contract StablecoinExchange is IStablecoinExchange {
9288 mapping (address user = > mapping (address token = > uint128 balance )) internal balances;
9389
9490 /// Last processed order ID
95- uint128 nextOrderId ;
91+ uint128 public activeOrderId ;
9692 /// Latest pending order ID
97- uint128 pendingOrderId;
98-
99- /*//////////////////////////////////////////////////////////////
100- CONSTRUCTOR
101- //////////////////////////////////////////////////////////////*/
102-
103- constructor (address admin ) {
104- linkingToken = new LinkingUSD (admin);
105- }
93+ uint128 public pendingOrderId;
10694
10795 /*//////////////////////////////////////////////////////////////
10896 Functions
@@ -145,8 +133,6 @@ contract StablecoinExchange is IStablecoinExchange {
145133 }
146134
147135 /// @notice Generate deterministic key for token pair
148- /// @param tokenA First token address
149- /// @param tokenB Second token address
150136 /// @return key Deterministic pair key
151137 function pairKey (address tokenA , address tokenB ) public pure returns (bytes32 key ) {
152138 (tokenA, tokenB) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
@@ -169,6 +155,8 @@ contract StablecoinExchange is IStablecoinExchange {
169155
170156 book.bestBidTick = type (int16 ).min;
171157 book.bestAskTick = type (int16 ).max;
158+
159+ emit PairCreated (key, base, quote);
172160 }
173161
174162 /// @notice Internal function to place order in pending queue
@@ -185,6 +173,7 @@ contract StablecoinExchange is IStablecoinExchange {
185173 address base ,
186174 address quote ,
187175 uint128 amount ,
176+ address maker ,
188177 bool isBid ,
189178 int16 tick ,
190179 bool isFlip ,
@@ -214,7 +203,7 @@ contract StablecoinExchange is IStablecoinExchange {
214203 // For bids, escrow quote tokens based on price
215204 escrowToken = quote;
216205 uint32 price = tickToPrice (tick);
217- escrowAmount = ( amount * price) / PRICE_SCALE;
206+ escrowAmount = uint128 (( uint256 ( amount) * uint256 ( price)) / uint256 ( PRICE_SCALE)) ;
218207 } else {
219208 // For asks, escrow base tokens
220209 escrowToken = base;
@@ -230,12 +219,13 @@ contract StablecoinExchange is IStablecoinExchange {
230219 ITIP20 (escrowToken).transferFrom (msg .sender , address (this ), escrowAmount - userBalance);
231220 }
232221
233- orderId = ++ pendingOrderId;
222+ orderId = pendingOrderId + 1 ;
223+ ++ pendingOrderId;
234224
235225 orders[orderId] = Order ({
236226 maker: msg .sender ,
237227 bookKey: key,
238- side: isBid ? Side.Bid : Side.Ask ,
228+ isBid: isBid ,
239229 tick: tick,
240230 amount: amount,
241231 remaining: amount,
@@ -245,26 +235,26 @@ contract StablecoinExchange is IStablecoinExchange {
245235 flipTick: flipTick
246236 });
247237
238+ emit OrderPlaced (orderId, maker, base, amount, isBid, tick);
248239 return orderId;
249240 }
250241
251242 /// @notice Place a limit order on the orderbook
252- /// @param base Base token address
243+ /// @param token Token address (system determines base/quote pairing)
253244 /// @param amount Order amount in base token
254245 /// @param isBid True for buy orders, false for sell orders
255246 /// @param tick Price tick for the order
256247 /// @return orderId The assigned order ID
257- function place (address base , uint128 amount , bool isBid , int16 tick )
248+ function place (address token , uint128 amount , bool isBid , int16 tick )
258249 external
259250 returns (uint128 orderId )
260251 {
261- address quote = ITIP20 (base).quoteToken ();
262- orderId = _placeOrder (base, quote, amount, isBid, tick, false , 0 );
263- emit OrderPlaced (orderId, msg .sender , base, amount, isBid, tick);
252+ address quote = ITIP20 (token).quoteToken ();
253+ orderId = _placeOrder (token, quote, amount, msg .sender , isBid, tick, false , 0 );
264254 }
265255
266256 /// @notice Place a flip order that auto-flips when filled
267- /// @param token Token address (base token)
257+ /// @param token Token address
268258 /// @param amount Order amount in base token
269259 /// @param isBid True for bid (buy), false for ask (sell)
270260 /// @param tick Price tick for the order
@@ -275,7 +265,7 @@ contract StablecoinExchange is IStablecoinExchange {
275265 returns (uint128 orderId )
276266 {
277267 address quote = ITIP20 (token).quoteToken ();
278- orderId = _placeOrder (token, quote, amount, isBid, tick, true , flipTick);
268+ orderId = _placeOrder (token, quote, amount, msg . sender , isBid, tick, true , flipTick);
279269 emit FlipOrderPlaced (orderId, msg .sender , token, amount, isBid, tick, flipTick);
280270 }
281271
@@ -285,62 +275,86 @@ contract StablecoinExchange is IStablecoinExchange {
285275 require (order.maker == msg .sender , "UNAUTHORIZED " );
286276
287277 Orderbook storage book = books[order.bookKey];
288- address token = order.side == Side.Bid ? book.quote : book.base;
278+ address token = order.isBid ? book.quote : book.base;
289279
290280 // If the order is pending, delete it from storage without adjusting the orderbook
291- if (orderId > nextOrderId) {
292- // Credit remaining tokens to user's withdrawable balance
293- balances[order.maker][token] += order.remaining;
281+ if (orderId > activeOrderId) {
282+ // Credit escrow amount to user's withdrawable balance
283+ uint128 escrowAmount;
284+ if (order.isBid) {
285+ // For bids, escrow quote tokens based on price
286+ uint32 price = tickToPrice (order.tick);
287+ escrowAmount =
288+ uint128 ((uint256 (order.remaining) * uint256 (price)) / uint256 (PRICE_SCALE));
289+ } else {
290+ // For asks, escrow base tokens
291+ escrowAmount = order.remaining;
292+ }
293+ balances[order.maker][token] += escrowAmount;
294294
295295 delete orders[orderId];
296296 emit OrderCancelled (orderId);
297297 return ;
298- }
299-
300- bool isBid = order.side == Side.Bid;
301- TickLevel storage level = isBid ? book.bids[order.tick] : book.asks[order.tick];
302-
303- if (order.prev != 0 ) {
304- orders[order.prev].next = order.next;
305298 } else {
306- level.head = order.next ;
307- }
299+ bool isBid = order.isBid ;
300+ TickLevel storage level = isBid ? book.bids[order.tick] : book.asks[order.tick];
308301
309- if (order.next != 0 ) {
310- orders[order.next].prev = order.prev ;
311- } else {
312- level.tail = order.prev ;
313- }
302+ if (order.prev != 0 ) {
303+ orders[order.prev].next = order.next ;
304+ } else {
305+ level.head = order.next ;
306+ }
314307
315- // Decrement total liquidity
316- level.totalLiquidity -= order.remaining;
308+ if (order.next != 0 ) {
309+ orders[order.next].prev = order.prev;
310+ } else {
311+ level.tail = order.prev;
312+ }
317313
318- if (level.head == 0 ) {
319- _clearTickBit (order.bookKey, order.tick, isBid);
320- }
314+ // Decrement total liquidity
315+ level.totalLiquidity -= order.remaining;
316+
317+ if (level.head == 0 ) {
318+ _clearTickBit (order.bookKey, order.tick, isBid);
319+ }
321320
322- // Credit remaining tokens to user's withdrawable balance
323- balances[order.maker][token] += order.remaining;
321+ // Credit escrow amount to user's withdrawable balance
322+ uint128 escrowAmount;
323+ if (order.isBid) {
324+ // For bids, escrow quote tokens based on price
325+ uint32 price = tickToPrice (order.tick);
326+ escrowAmount =
327+ uint128 ((uint256 (order.remaining) * uint256 (price)) / uint256 (PRICE_SCALE));
328+ } else {
329+ // For asks, escrow base tokens
330+ escrowAmount = order.remaining;
331+ }
332+ balances[order.maker][token] += escrowAmount;
324333
325- delete orders[orderId];
334+ delete orders[orderId];
326335
327- emit OrderCancelled (orderId);
336+ emit OrderCancelled (orderId);
337+ }
328338 }
329339
330340 // TODO: it might be nice to create some ISystem Tx interface that is used
331341 // for contracts that are executed by the protocol at the end of the block.
332342 // This makes it easy to distinguish when the protocol is responsible for calling a function
333343 // TODO: natspec
334344 function executeBlock () external {
335- while (nextOrderId < pendingOrderId) {
336- uint128 orderId = ++ nextOrderId;
345+ require (msg .sender == address (0 ), "Only system tx " );
346+
347+ uint128 orderId = activeOrderId;
348+ uint128 pendingId = pendingOrderId;
349+
350+ for (orderId = orderId; orderId <= pendingId; orderId++ ) {
337351 Order storage order = orders[orderId];
338352
339353 // If the order is already canceled, skip
340354 if (order.maker == address (0 )) continue ;
341355
342356 Orderbook storage book = books[order.bookKey];
343- bool isBid = order.side == Side.Bid ;
357+ bool isBid = order.isBid ;
344358 TickLevel storage level = isBid ? book.bids[order.tick] : book.asks[order.tick];
345359
346360 uint128 prevTail = level.tail;
@@ -368,6 +382,9 @@ contract StablecoinExchange is IStablecoinExchange {
368382 // Increment total liquidity for this tick level
369383 level.totalLiquidity += order.remaining;
370384 }
385+
386+ // Update activeOrderId to last processed order
387+ activeOrderId = orderId - 1 ;
371388 }
372389
373390 /// @notice Withdraw tokens from exchange balance
@@ -388,6 +405,25 @@ contract StablecoinExchange is IStablecoinExchange {
388405 return balances[user][token];
389406 }
390407
408+ /// @notice Get tick level information
409+ /// @param base Base token in pair
410+ /// @param tick Price tick
411+ /// @param isBid boolean to indicate bid/ask
412+ /// @return head First order ID tick
413+ /// @return tail Last order ID tick
414+ /// @return totalLiquidity Total liquidity at tick
415+ function getTickLevel (address base , int16 tick , bool isBid )
416+ external
417+ view
418+ returns (uint128 head , uint128 tail , uint128 totalLiquidity )
419+ {
420+ address quote = ITIP20 (base).quoteToken ();
421+ bytes32 key = pairKey (base, quote);
422+ Orderbook storage book = books[key];
423+ TickLevel memory level = isBid ? book.bids[tick] : book.asks[tick];
424+ return (level.head, level.tail, level.totalLiquidity);
425+ }
426+
391427 /// @notice Quote the cost to buy a specific amount of tokens
392428 /// @param tokenIn Token to spend
393429 /// @param tokenOut Token to buy
@@ -414,15 +450,18 @@ contract StablecoinExchange is IStablecoinExchange {
414450 internal
415451 returns (uint128 nextOrderAtTick )
416452 {
453+ // NOTE: This can be much more optimized but since this is only a reference contract, readability was prioritized
417454 Order storage order = orders[orderId];
418455 Orderbook storage book = books[order.bookKey];
419- bool isBid = order.side == Side.Bid ;
456+ bool isBid = order.isBid ;
420457 TickLevel storage level = isBid ? book.bids[order.tick] : book.asks[order.tick];
421458
422459 // Fill the order
423460 order.remaining -= fillAmount;
424461 level.totalLiquidity -= fillAmount;
425462
463+ emit OrderFilled (orderId, order.maker, msg .sender , fillAmount, order.remaining > 0 );
464+
426465 // Credit maker with appropriate tokens
427466 if (isBid) {
428467 // Bid order: maker gets base tokens
@@ -451,7 +490,20 @@ contract StablecoinExchange is IStablecoinExchange {
451490 level.tail = order.prev;
452491 }
453492
454- // TODO: if flip order, place order at flip tick
493+ // If flip order, place order at flip tick on opposite side
494+ if (order.isFlip) {
495+ _placeOrder (
496+ book.base,
497+ book.quote,
498+ order.amount,
499+ order.maker,
500+ ! order.isBid,
501+ order.flipTick,
502+ true ,
503+ order.tick
504+ );
505+ }
506+
455507 delete orders[orderId];
456508
457509 // Check if tick is exhausted and return 0 if so
0 commit comments