perpetuals.delta
1 // Copyright (c) 2025 ALPHA/DELTA Network 2 // This file is part of the DeltaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 /**********************************************************************************************************************/ 17 // DELTA Perpetuals Program 18 // 19 // Provides leveraged perpetual futures trading: 20 // - Leverage limits: 21 // - Fiat pairs (EUR, GBP, JPY): 50x maximum 22 // - Major crypto (BTC, ETH): 20x maximum 23 // - Alternative crypto: 10x maximum 24 // - Funding rate settlement every 8 hours 25 // - Funding calculation: (Mark Price - Index Price) / Index Price × Funding Interval 26 // - Maintenance margin rates: 27 // - Fiat: 1.5% 28 // - Major crypto: 3% 29 // - Alt crypto: 5% 30 // - Liquidation engine with insurance fund and ADL (Auto-Deleveraging) 31 /**********************************************************************************************************************/ 32 33 program delta.perpetuals; 34 35 /**********************************************************************************************************************/ 36 // STRUCTS 37 /**********************************************************************************************************************/ 38 39 // A perpetual futures position. 40 struct position: 41 // Position ID (unique). 42 position_id as u128; 43 // Trader's address. 44 trader as address; 45 // Market ID (e.g., "BTC-PERP"). 46 market_id as field; 47 // Position side: 0 = Long, 1 = Short. 48 side as u8; 49 // Position size in base currency. 50 size as u128; 51 // Average entry price (8 decimals). 52 entry_price as u128; 53 // Current margin deposited (in sAX). 54 margin as u128; 55 // Leverage used (1-50x, stored as 10x = 100). 56 leverage as u16; 57 // Accumulated funding (positive = paid, negative = received). 58 accumulated_funding as i128; 59 // Block height when position was opened. 60 opened_at as u32; 61 // Block height of last modification. 62 updated_at as u32; 63 // Status: 0 = Open, 1 = Closed, 2 = Liquidated. 64 status as u8; 65 66 // Perpetual market configuration. 67 struct perp_market: 68 // Underlying asset ID. 69 underlying as field; 70 // Maximum allowed leverage (10x = 100, 50x = 500). 71 max_leverage as u16; 72 // Initial margin requirement in basis points (e.g., 500 = 5%). 73 initial_margin_bps as u16; 74 // Maintenance margin requirement in basis points. 75 maintenance_margin_bps as u16; 76 // Asset class: 0 = Fiat, 1 = Major Crypto, 2 = Alt Crypto. 77 asset_class as u8; 78 // Whether market is active. 79 is_active as boolean; 80 // Minimum position size. 81 min_position_size as u128; 82 83 // Funding rate data. 84 struct funding_rate: 85 // Current funding rate in basis points (can be negative). 86 rate_bps as i16; 87 // Mark price (TWAP of order book). 88 mark_price as u128; 89 // Index price (oracle price). 90 index_price as u128; 91 // Block height of last settlement. 92 last_settlement_block as u32; 93 // Accumulated funding since launch. 94 cumulative_funding as i128; 95 96 // Insurance fund state. 97 struct insurance_fund: 98 // Total balance in sAX. 99 balance as u128; 100 // Total liquidations covered. 101 liquidations_covered as u64; 102 // Total shortfall covered. 103 shortfall_covered as u128; 104 105 /**********************************************************************************************************************/ 106 // MAPPINGS 107 /**********************************************************************************************************************/ 108 109 // Positions by position ID. 110 mapping positions: 111 key as u128.public; 112 value as position.public; 113 114 // User's active position per market (user_address_hash + market_id -> position_id). 115 mapping user_positions: 116 key as field.public; 117 value as u128.public; 118 119 // Market configurations. 120 mapping perp_markets: 121 key as field.public; 122 value as perp_market.public; 123 124 // Funding rates per market. 125 mapping funding_rates: 126 key as field.public; 127 value as funding_rate.public; 128 129 // Insurance fund state. 130 mapping insurance: 131 key as u8.public; 132 value as insurance_fund.public; 133 134 // Open interest per market (long side). 135 mapping open_interest_long: 136 key as field.public; 137 value as u128.public; 138 139 // Open interest per market (short side). 140 mapping open_interest_short: 141 key as field.public; 142 value as u128.public; 143 144 // Position counter. 145 mapping counters: 146 key as u8.public; 147 value as u128.public; 148 149 // Liquidation counter. 150 mapping liquidation_counter: 151 key as u8.public; 152 value as u64.public; 153 154 // Admin address. 155 mapping admin: 156 key as u8.public; 157 value as address.public; 158 159 /**********************************************************************************************************************/ 160 // FUNCTIONS 161 /**********************************************************************************************************************/ 162 163 // Open a new perpetual position. 164 function open_position: 165 // Input the market ID. 166 input r0 as field.public; 167 // Input the side (0 = Long, 1 = Short). 168 input r1 as u8.public; 169 // Input the leverage (e.g., 100 = 10x, 500 = 50x). 170 input r2 as u16.public; 171 // Input the margin amount in sAX. 172 input r3 as u128.public; 173 174 // Validate side. 175 lte r1 1u8 into r4; 176 assert.eq r4 true; 177 178 // Validate leverage (at least 1x = 10). 179 gte r2 10u16 into r5; 180 assert.eq r5 true; 181 182 // Validate margin is positive. 183 gt r3 0u128 into r6; 184 assert.eq r6 true; 185 186 // Execute position opening. 187 async open_position self.caller r0 r1 r2 r3 into r7; 188 output r7 as delta.perpetuals/open_position.future; 189 190 finalize open_position: 191 // Input the caller (trader). 192 input r0 as address.public; 193 // Input the market ID. 194 input r1 as field.public; 195 // Input the side. 196 input r2 as u8.public; 197 // Input the leverage. 198 input r3 as u16.public; 199 // Input the margin. 200 input r4 as u128.public; 201 202 // Get market config. 203 get perp_markets[r1] into r5; 204 205 // Ensure market is active. 206 assert.eq r5.is_active true; 207 208 // Validate leverage against market max. 209 lte r3 r5.max_leverage into r6; 210 assert.eq r6 true; 211 212 // Calculate position size: margin × leverage / 10. 213 cast r3 into r7 as u128; 214 mul r4 r7 into r8; 215 div r8 10u128 into r9; // Position size (notional) 216 217 // Check minimum position size. 218 gte r9 r5.min_position_size into r10; 219 assert.eq r10 true; 220 221 // Get current oracle price (from delta.oracle via external call). 222 // For now, we'll use a placeholder - in production this would call delta.oracle::get_price. 223 // Assume price is passed or fetched externally. 224 // Using a constant for demonstration. 225 // In production: get delta.oracle/price_feeds[r1] into current_price; 226 227 // Get next position ID. 228 get.or_use counters[0u8] 1u128 into r11; 229 230 // Calculate entry price (would be from oracle). 231 // Placeholder: using 50000_00000000 (50,000 with 8 decimals). 232 // In production, this comes from the matching engine or oracle. 233 234 // Construct the position. 235 cast r11 r0 r1 r2 r9 50000_00000000u128 r4 r3 0i128 block.height block.height 0u8 into r12 as position; 236 237 // Store the position. 238 set r12 into positions[r11]; 239 240 // Increment position counter. 241 add r11 1u128 into r13; 242 set r13 into counters[0u8]; 243 244 // Update open interest. 245 is.eq r2 0u8 into r14; // Is long? 246 branch.eq r14 true to update_long_oi; 247 248 // Update short OI. 249 get.or_use open_interest_short[r1] 0u128 into r15; 250 add r15 r9 into r16; 251 set r16 into open_interest_short[r1]; 252 branch.eq true true to end; 253 254 position update_long_oi; 255 get.or_use open_interest_long[r1] 0u128 into r17; 256 add r17 r9 into r18; 257 set r18 into open_interest_long[r1]; 258 259 position end; 260 261 /**********************************************************************************************************************/ 262 263 // Close a position (fully or partially). 264 function close_position: 265 // Input the position ID. 266 input r0 as u128.public; 267 // Input the amount to close (0 = close all). 268 input r1 as u128.public; 269 270 async close_position self.caller r0 r1 into r2; 271 output r2 as delta.perpetuals/close_position.future; 272 273 finalize close_position: 274 // Input the caller. 275 input r0 as address.public; 276 // Input the position ID. 277 input r1 as u128.public; 278 // Input the close amount. 279 input r2 as u128.public; 280 281 // Get the position. 282 get positions[r1] into r3; 283 284 // Verify caller owns the position. 285 assert.eq r0 r3.trader; 286 287 // Verify position is open. 288 assert.eq r3.status 0u8; 289 290 // Determine close amount. 291 is.eq r2 0u128 into r4; 292 ternary r4 r3.size r2 into r5; // Close all if 0, else close specified 293 294 // Ensure close amount doesn't exceed position size. 295 lte r5 r3.size into r6; 296 assert.eq r6 true; 297 298 // Calculate remaining size. 299 sub r3.size r5 into r7; 300 301 // Calculate PnL (simplified - would need current price). 302 // PnL = (current_price - entry_price) × size for longs 303 // PnL = (entry_price - current_price) × size for shorts 304 // Placeholder: using entry_price as current_price (no PnL). 305 306 // Update open interest. 307 is.eq r3.side 0u8 into r8; 308 branch.eq r8 true to update_long_oi_close; 309 310 // Update short OI. 311 get open_interest_short[r3.market_id] into r9; 312 sub r9 r5 into r10; 313 set r10 into open_interest_short[r3.market_id]; 314 branch.eq true true to update_position; 315 316 position update_long_oi_close; 317 get open_interest_long[r3.market_id] into r11; 318 sub r11 r5 into r12; 319 set r12 into open_interest_long[r3.market_id]; 320 321 position update_position; 322 323 // Update or close position. 324 is.eq r7 0u128 into r13; // Is fully closed? 325 ternary r13 1u8 0u8 into r14; // Status: 1 = Closed, 0 = Open 326 327 // Calculate new margin (proportional). 328 mul r3.margin r7 into r15; 329 div r15 r3.size into r16; 330 331 cast r3.position_id r3.trader r3.market_id r3.side r7 r3.entry_price r16 r3.leverage r3.accumulated_funding r3.opened_at block.height r14 into r17 as position; 332 set r17 into positions[r1]; 333 334 // TODO: Return margin + PnL to user. 335 336 /**********************************************************************************************************************/ 337 338 // Add margin to an existing position. 339 function add_margin: 340 // Input the position ID. 341 input r0 as u128.public; 342 // Input the amount to add. 343 input r1 as u128.public; 344 345 // Validate amount. 346 gt r1 0u128 into r2; 347 assert.eq r2 true; 348 349 async add_margin self.caller r0 r1 into r3; 350 output r3 as delta.perpetuals/add_margin.future; 351 352 finalize add_margin: 353 // Input the caller. 354 input r0 as address.public; 355 // Input the position ID. 356 input r1 as u128.public; 357 // Input the amount. 358 input r2 as u128.public; 359 360 // Get the position. 361 get positions[r1] into r3; 362 363 // Verify caller owns the position. 364 assert.eq r0 r3.trader; 365 366 // Verify position is open. 367 assert.eq r3.status 0u8; 368 369 // Add margin. 370 add r3.margin r2 into r4; 371 372 // Update position. 373 cast r3.position_id r3.trader r3.market_id r3.side r3.size r3.entry_price r4 r3.leverage r3.accumulated_funding r3.opened_at block.height r3.status into r5 as position; 374 set r5 into positions[r1]; 375 376 // TODO: Transfer margin from user's balance. 377 378 /**********************************************************************************************************************/ 379 380 // Settle funding rate for a market. Called periodically (every 8 hours). 381 function settle_funding: 382 // Input the market ID. 383 input r0 as field.public; 384 385 async settle_funding r0 into r1; 386 output r1 as delta.perpetuals/settle_funding.future; 387 388 finalize settle_funding: 389 // Input the market ID. 390 input r0 as field.public; 391 392 // Get current funding rate. 393 cast 0i16 0u128 0u128 0u32 0i128 into r1 as funding_rate; 394 get.or_use funding_rates[r0] r1 into r2; 395 396 // Check if enough time has passed (8 hours = ~2880 blocks at 10s). 397 sub block.height r2.last_settlement_block into r3; 398 gte r3 2880u32 into r4; 399 assert.eq r4 true; 400 401 // Get oracle price (index price). 402 // In production: call delta.oracle::get_price(r0). 403 // Placeholder value. 404 405 // Calculate funding rate. 406 // rate = (mark_price - index_price) / index_price 407 // For now, using a simplified calculation. 408 // In production, mark_price comes from TWAP of order book. 409 410 // Get open interest to calculate funding payments. 411 get.or_use open_interest_long[r0] 0u128 into r5; 412 get.or_use open_interest_short[r0] 0u128 into r6; 413 414 // Funding is paid from longs to shorts when rate is positive, 415 // and from shorts to longs when rate is negative. 416 417 // Update cumulative funding. 418 cast r2.rate_bps into r7 as i128; 419 add r2.cumulative_funding r7 into r8; 420 421 // Store updated funding rate. 422 cast r2.rate_bps r2.mark_price r2.index_price block.height r8 into r9 as funding_rate; 423 set r9 into funding_rates[r0]; 424 425 // Note: Individual position funding is calculated on-demand 426 // when positions are modified or closed. 427 428 /**********************************************************************************************************************/ 429 430 // Liquidate an undercollateralized position. 431 function liquidate: 432 // Input the position ID to liquidate. 433 input r0 as u128.public; 434 435 async liquidate self.caller r0 into r1; 436 output r1 as delta.perpetuals/liquidate.future; 437 438 finalize liquidate: 439 // Input the liquidator (caller). 440 input r0 as address.public; 441 // Input the position ID. 442 input r1 as u128.public; 443 444 // Get the position. 445 get positions[r1] into r2; 446 447 // Verify position is open. 448 assert.eq r2.status 0u8; 449 450 // Get market config for maintenance margin. 451 get perp_markets[r2.market_id] into r3; 452 453 // Calculate maintenance margin required. 454 // maintenance_margin = position_size × entry_price × maintenance_margin_bps / 10000. 455 mul r2.size r2.entry_price into r4; 456 div r4 100000000u128 into r5; // Adjust for decimals 457 cast r3.maintenance_margin_bps into r6 as u128; 458 mul r5 r6 into r7; 459 div r7 10000u128 into r8; // Required margin 460 461 // Get current margin ratio. 462 // In production, would calculate PnL and adjust margin. 463 // For now, checking if margin < maintenance_margin. 464 lt r2.margin r8 into r9; 465 assert.eq r9 true; // Position must be liquidatable 466 467 // Mark position as liquidated. 468 cast r2.position_id r2.trader r2.market_id r2.side 0u128 r2.entry_price 0u128 r2.leverage r2.accumulated_funding r2.opened_at block.height 2u8 into r10 as position; 469 set r10 into positions[r1]; 470 471 // Update open interest. 472 is.eq r2.side 0u8 into r11; 473 branch.eq r11 true to update_long_oi_liq; 474 475 get open_interest_short[r2.market_id] into r12; 476 sub r12 r2.size into r13; 477 set r13 into open_interest_short[r2.market_id]; 478 branch.eq true true to update_insurance; 479 480 position update_long_oi_liq; 481 get open_interest_long[r2.market_id] into r14; 482 sub r14 r2.size into r15; 483 set r15 into open_interest_long[r2.market_id]; 484 485 position update_insurance; 486 487 // Update insurance fund and liquidation count. 488 cast 0u128 0u64 0u128 into r16 as insurance_fund; 489 get.or_use insurance[0u8] r16 into r17; 490 491 add r17.liquidations_covered 1u64 into r18; 492 493 // In production: calculate shortfall and use insurance fund. 494 // For now, just increment counter. 495 cast r17.balance r18 r17.shortfall_covered into r19 as insurance_fund; 496 set r19 into insurance[0u8]; 497 498 // TODO: Pay liquidation bonus to liquidator. 499 500 /**********************************************************************************************************************/ 501 502 // Register a new perpetual market (admin only). 503 function register_perp_market: 504 // Input the market ID. 505 input r0 as field.public; 506 // Input the underlying asset. 507 input r1 as field.public; 508 // Input the max leverage. 509 input r2 as u16.public; 510 // Input the initial margin bps. 511 input r3 as u16.public; 512 // Input the maintenance margin bps. 513 input r4 as u16.public; 514 // Input the asset class. 515 input r5 as u8.public; 516 // Input the minimum position size. 517 input r6 as u128.public; 518 519 async register_perp_market self.caller r0 r1 r2 r3 r4 r5 r6 into r7; 520 output r7 as delta.perpetuals/register_perp_market.future; 521 522 finalize register_perp_market: 523 // Input the caller. 524 input r0 as address.public; 525 // Input the market ID. 526 input r1 as field.public; 527 // Input the underlying. 528 input r2 as field.public; 529 // Input the max leverage. 530 input r3 as u16.public; 531 // Input the initial margin bps. 532 input r4 as u16.public; 533 // Input the maintenance margin bps. 534 input r5 as u16.public; 535 // Input the asset class. 536 input r6 as u8.public; 537 // Input the min position size. 538 input r7 as u128.public; 539 540 // Verify caller is admin. 541 get admin[0u8] into r8; 542 assert.eq r0 r8; 543 544 // Construct market config. 545 cast r2 r3 r4 r5 r6 true r7 into r9 as perp_market; 546 547 // Store market. 548 set r9 into perp_markets[r1]; 549 550 // Initialize funding rate. 551 cast 0i16 0u128 0u128 block.height 0i128 into r10 as funding_rate; 552 set r10 into funding_rates[r1]; 553 554 /**********************************************************************************************************************/ 555 556 // Initialize the perpetuals contract. 557 function initialize: 558 // Input the admin address. 559 input r0 as address.public; 560 561 async initialize self.caller r0 into r1; 562 output r1 as delta.perpetuals/initialize.future; 563 564 finalize initialize: 565 // Input the caller. 566 input r0 as address.public; 567 // Input the admin. 568 input r1 as address.public; 569 570 // Check if already initialized. 571 contains admin[0u8] into r2; 572 assert.eq r2 false; 573 574 // Set admin. 575 set r1 into admin[0u8]; 576 577 // Initialize counters. 578 set 1u128 into counters[0u8]; 579 580 // Initialize insurance fund. 581 cast 0u128 0u64 0u128 into r3 as insurance_fund; 582 set r3 into insurance[0u8]; 583 584 /**********************************************************************************************************************/ 585 586 // Contribute to insurance fund. 587 function contribute_insurance: 588 // Input the amount to contribute. 589 input r0 as u128.public; 590 591 // Validate amount. 592 gt r0 0u128 into r1; 593 assert.eq r1 true; 594 595 async contribute_insurance self.caller r0 into r2; 596 output r2 as delta.perpetuals/contribute_insurance.future; 597 598 finalize contribute_insurance: 599 // Input the caller. 600 input r0 as address.public; 601 // Input the amount. 602 input r1 as u128.public; 603 604 // Get current insurance fund. 605 cast 0u128 0u64 0u128 into r2 as insurance_fund; 606 get.or_use insurance[0u8] r2 into r3; 607 608 // Add contribution. 609 add r3.balance r1 into r4; 610 611 cast r4 r3.liquidations_covered r3.shortfall_covered into r5 as insurance_fund; 612 set r5 into insurance[0u8]; 613 614 // TODO: Transfer funds from contributor. 615 616 /**********************************************************************************************************************/ 617 618 // Get funding rate for a market. 619 function get_funding_rate: 620 // Input the market ID. 621 input r0 as field.public; 622 623 async get_funding_rate r0 into r1; 624 output r1 as delta.perpetuals/get_funding_rate.future; 625 626 finalize get_funding_rate: 627 // Input the market ID. 628 input r0 as field.public; 629 630 // Get funding rate (fails if market doesn't exist). 631 get funding_rates[r0] into r1; 632 633 // Note: ADL doesn't support return values from finalize. 634 // Clients query funding_rates mapping directly. 635 636 /**********************************************************************************************************************/