/ contracts / delta / exchange / src / main.adl
main.adl
  1  // Decentralized Exchange Contract for Delta Chain
  2  // Order book based spot trading
  3  //
  4  // Target: Delta (account-based model)
  5  
  6  program exchange.adl;
  7  
  8  // Order side
  9  enum OrderSide {
 10      Buy,
 11      Sell,
 12  }
 13  
 14  // Order status
 15  enum OrderStatus {
 16      Open,
 17      Partial,
 18      Filled,
 19      Cancelled,
 20  }
 21  
 22  // Storage mappings
 23  mapping orders: field => Order;
 24  mapping user_orders: address => field;  // Latest order per user
 25  mapping pair_best_bid: field => u64;    // Best bid price per pair
 26  mapping pair_best_ask: field => u64;    // Best ask price per pair
 27  mapping balances: field => u64;         // user+token hash => balance
 28  mapping fee_collected: field => u64;    // token => total fees
 29  
 30  struct Order {
 31      id: field,
 32      owner: address,
 33      pair: field,           // Hash of base/quote tokens
 34      side: OrderSide,
 35      price: u64,            // Scaled by 1e8
 36      amount: u64,           // Base token amount
 37      filled: u64,           // Amount filled
 38      status: OrderStatus,
 39      created_at: u64,
 40  }
 41  
 42  struct Trade {
 43      maker_order: field,
 44      taker_order: field,
 45      price: u64,
 46      amount: u64,
 47      timestamp: u64,
 48  }
 49  
 50  // Configuration
 51  const MAKER_FEE_BPS: u16 = 10u16;   // 0.1%
 52  const TAKER_FEE_BPS: u16 = 30u16;   // 0.3%
 53  const PRICE_PRECISION: u64 = 100_000_000u64;  // 1e8
 54  const MIN_ORDER_SIZE: u64 = 1_000_000u64;     // 0.01 tokens
 55  
 56  // Deposit tokens to exchange
 57  transition deposit(
 58      public token: field,
 59      public amount: u64,
 60  ) {
 61      let balance_key = BHP256::hash_to_field(self.caller, token);
 62      let current = balances.get_or_use(balance_key, 0u64);
 63      balances.set(balance_key, current + amount);
 64  }
 65  
 66  // Withdraw tokens from exchange
 67  transition withdraw(
 68      public token: field,
 69      public amount: u64,
 70  ) {
 71      let balance_key = BHP256::hash_to_field(self.caller, token);
 72      let current = balances.get(balance_key);
 73      assert(current >= amount);
 74      balances.set(balance_key, current - amount);
 75  }
 76  
 77  // Place limit order
 78  transition place_order(
 79      public pair: field,
 80      public side: OrderSide,
 81      public price: u64,
 82      public amount: u64,
 83  ) -> field {
 84      assert(amount >= MIN_ORDER_SIZE);
 85      assert(price > 0u64);
 86  
 87      // Generate order ID
 88      let order_id = BHP256::hash_to_field(self.caller, pair, block.height);
 89  
 90      let order = Order {
 91          id: order_id,
 92          owner: self.caller,
 93          pair: pair,
 94          side: side,
 95          price: price,
 96          amount: amount,
 97          filled: 0u64,
 98          status: OrderStatus::Open,
 99          created_at: block.height,
100      };
101  
102      orders.set(order_id, order);
103      user_orders.set(self.caller, order_id);
104  
105      // Update best bid/ask
106      if side == OrderSide::Buy {
107          let best_bid = pair_best_bid.get_or_use(pair, 0u64);
108          if price > best_bid {
109              pair_best_bid.set(pair, price);
110          }
111      } else {
112          let best_ask = pair_best_ask.get_or_use(pair, u64::MAX);
113          if price < best_ask {
114              pair_best_ask.set(pair, price);
115          }
116      }
117  
118      return order_id;
119  }
120  
121  // Cancel order
122  transition cancel_order(
123      public order_id: field,
124  ) {
125      let order = orders.get(order_id);
126      assert(order.owner == self.caller);
127      assert(order.status == OrderStatus::Open || order.status == OrderStatus::Partial);
128  
129      let cancelled = Order {
130          id: order.id,
131          owner: order.owner,
132          pair: order.pair,
133          side: order.side,
134          price: order.price,
135          amount: order.amount,
136          filled: order.filled,
137          status: OrderStatus::Cancelled,
138          created_at: order.created_at,
139      };
140  
141      orders.set(order_id, cancelled);
142  }
143  
144  // Match orders (called by matching engine)
145  transition match_orders(
146      public maker_order_id: field,
147      public taker_order_id: field,
148      public fill_amount: u64,
149  ) {
150      let maker = orders.get(maker_order_id);
151      let taker = orders.get(taker_order_id);
152  
153      // Validate match
154      assert(maker.pair == taker.pair);
155      assert(maker.side != taker.side);
156      assert(maker.status == OrderStatus::Open || maker.status == OrderStatus::Partial);
157      assert(taker.status == OrderStatus::Open || taker.status == OrderStatus::Partial);
158  
159      // Price check
160      if taker.side == OrderSide::Buy {
161          assert(taker.price >= maker.price);
162      } else {
163          assert(maker.price >= taker.price);
164      }
165  
166      // Calculate fill amounts
167      let maker_remaining = maker.amount - maker.filled;
168      let taker_remaining = taker.amount - taker.filled;
169      let actual_fill = fill_amount;
170      if actual_fill > maker_remaining {
171          actual_fill = maker_remaining;
172      }
173      if actual_fill > taker_remaining {
174          actual_fill = taker_remaining;
175      }
176  
177      // Calculate fees
178      let trade_value = (actual_fill * maker.price) / PRICE_PRECISION;
179      let maker_fee = (trade_value * MAKER_FEE_BPS as u64) / 10000u64;
180      let taker_fee = (trade_value * TAKER_FEE_BPS as u64) / 10000u64;
181  
182      // Update maker order
183      let maker_new_filled = maker.filled + actual_fill;
184      let maker_status = maker_new_filled == maker.amount
185          ? OrderStatus::Filled
186          : OrderStatus::Partial;
187  
188      let updated_maker = Order {
189          id: maker.id,
190          owner: maker.owner,
191          pair: maker.pair,
192          side: maker.side,
193          price: maker.price,
194          amount: maker.amount,
195          filled: maker_new_filled,
196          status: maker_status,
197          created_at: maker.created_at,
198      };
199      orders.set(maker_order_id, updated_maker);
200  
201      // Update taker order
202      let taker_new_filled = taker.filled + actual_fill;
203      let taker_status = taker_new_filled == taker.amount
204          ? OrderStatus::Filled
205          : OrderStatus::Partial;
206  
207      let updated_taker = Order {
208          id: taker.id,
209          owner: taker.owner,
210          pair: taker.pair,
211          side: taker.side,
212          price: taker.price,
213          amount: taker.amount,
214          filled: taker_new_filled,
215          status: taker_status,
216          created_at: taker.created_at,
217      };
218      orders.set(taker_order_id, updated_taker);
219  }
220  
221  // Get order info
222  transition get_order(
223      public order_id: field,
224  ) -> Order {
225      return orders.get(order_id);
226  }
227  
228  // Get user balance
229  transition get_balance(
230      public user: address,
231      public token: field,
232  ) -> u64 {
233      let balance_key = BHP256::hash_to_field(user, token);
234      return balances.get_or_use(balance_key, 0u64);
235  }