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 }