/ synthesizer / program / src / resources / sax.delta
sax.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 sAX (Synthetic AX) Program
 18  //
 19  // sAX is a synthetic representation of AX locked on the ALPHA chain.
 20  // It enables trading on DELTA while maintaining 1:1 backing with locked AX.
 21  //
 22  // Properties:
 23  // - 1:1 backing with Locked AX Pool on ALPHA
 24  // - Ephemeral (exists only while AX is locked)
 25  // - 4 decimals (matches AX: 1 AX = 10,000 microcredits)
 26  // - Non-mintable except through verified lock attestations
 27  // - Burnable only through unlock requests
 28  //
 29  // Cross-Chain Flow:
 30  // Lock (AX → sAX):
 31  //   1. User locks AX on ALPHA via alpha.credits::lock_for_sax()
 32  //   2. ALPHA block finalizes (3 blocks)
 33  //   3. IPC sends LockAttestation to DELTA runtime
 34  //   4. This program mints sAX upon verified attestation
 35  //
 36  // Unlock (sAX → AX):
 37  //   1. User calls burn_for_ax() on DELTA
 38  //   2. sAX is burned, unlock record created
 39  //   3. DELTA block finalizes (3 blocks)
 40  //   4. IPC sends UnlockAttestation to ALPHA runtime
 41  //   5. ALPHA releases AX from Locked Pool
 42  /**********************************************************************************************************************/
 43  
 44  program delta.sax;
 45  
 46  /**********************************************************************************************************************/
 47  // CONSTANTS (represented as literals in code)
 48  /**********************************************************************************************************************/
 49  
 50  // FINALITY_BLOCKS = 3
 51  // DECIMALS = 4 (1 sAX = 10,000 microcredits)
 52  // MIN_LOCK_AMOUNT = 10000 (1 sAX minimum)
 53  // MAX_PENDING_LOCKS = 1000 (per user)
 54  
 55  /**********************************************************************************************************************/
 56  // STRUCTS
 57  /**********************************************************************************************************************/
 58  
 59  // sAX balance record.
 60  struct sax_balance:
 61      // Owner address.
 62      owner as address;
 63      // Total balance in microcredits.
 64      balance as u128;
 65      // Amount pending unlock.
 66      pending_unlock as u128;
 67      // Last updated block.
 68      updated_at as u32;
 69  
 70  // Lock attestation from ALPHA chain.
 71  struct lock_attestation:
 72      // Lock ID (unique, from ALPHA).
 73      lock_id as u128;
 74      // User who locked AX.
 75      user as address;
 76      // Amount locked in microcredits.
 77      amount as u128;
 78      // ALPHA block where lock occurred.
 79      alpha_block as u64;
 80      // Merkle root of ALPHA state.
 81      merkle_root as field;
 82      // Status: 0 = pending, 1 = processed, 2 = failed.
 83      status as u8;
 84      // DELTA block when processed.
 85      processed_at as u32;
 86  
 87  // Unlock request for AX release.
 88  struct unlock_request:
 89      // Unlock ID (unique).
 90      unlock_id as u128;
 91      // User requesting unlock.
 92      user as address;
 93      // Amount to unlock in microcredits.
 94      amount as u128;
 95      // DELTA block when requested.
 96      requested_at as u32;
 97      // Status: 0 = pending, 1 = finalized, 2 = completed.
 98      status as u8;
 99      // DELTA block when finalized.
100      finalized_at as u32;
101  
102  // Pending lock for user.
103  struct pending_lock:
104      // Lock ID.
105      lock_id as u128;
106      // Amount.
107      amount as u128;
108      // ALPHA block.
109      alpha_block as u64;
110  
111  // Cross-chain statistics.
112  struct cross_chain_stats:
113      // Total AX locked (on ALPHA side).
114      total_locked as u128;
115      // Total sAX in circulation.
116      total_sax as u128;
117      // Total lock operations.
118      lock_count as u64;
119      // Total unlock operations.
120      unlock_count as u64;
121      // Last sync block.
122      last_sync_block as u32;
123  
124  // Program configuration.
125  struct sax_config:
126      // Minimum lock amount.
127      min_lock_amount as u128;
128      // Finality blocks required.
129      finality_blocks as u32;
130      // Whether minting is paused.
131      mint_paused as boolean;
132      // Whether burning is paused.
133      burn_paused as boolean;
134      // Admin address.
135      admin as address;
136      // System address (for IPC attestations).
137      system as address;
138  
139  // Transfer record.
140  struct transfer_record:
141      // Transfer ID.
142      transfer_id as u64;
143      // From address.
144      from as address;
145      // To address.
146      to as address;
147      // Amount.
148      amount as u128;
149      // Block when transferred.
150      transferred_at as u32;
151  
152  /**********************************************************************************************************************/
153  // MAPPINGS
154  /**********************************************************************************************************************/
155  
156  // User balances.
157  mapping balances:
158      key as address.public;
159      value as sax_balance.public;
160  
161  // Lock attestations by ID.
162  mapping lock_attestations:
163      key as u128.public;
164      value as lock_attestation.public;
165  
166  // Unlock requests by ID.
167  mapping unlock_requests:
168      key as u128.public;
169      value as unlock_request.public;
170  
171  // Lock ID counter.
172  mapping lock_counter:
173      key as u8.public;
174      value as u128.public;
175  
176  // Unlock ID counter.
177  mapping unlock_counter:
178      key as u8.public;
179      value as u128.public;
180  
181  // Transfer counter.
182  mapping transfer_counter:
183      key as u8.public;
184      value as u64.public;
185  
186  // Transfer records.
187  mapping transfers:
188      key as u64.public;
189      value as transfer_record.public;
190  
191  // Cross-chain statistics.
192  mapping stats:
193      key as u8.public;
194      value as cross_chain_stats.public;
195  
196  // Program configuration.
197  mapping config:
198      key as u8.public;
199      value as sax_config.public;
200  
201  // Processed lock IDs (to prevent replay).
202  mapping processed_locks:
203      key as u128.public;
204      value as boolean.public;
205  
206  // User pending unlock amounts.
207  mapping pending_unlocks:
208      key as address.public;
209      value as u128.public;
210  
211  // Allowances for delegated transfers.
212  mapping allowances:
213      key as field.public;  // hash(owner, spender)
214      value as u128.public;
215  
216  // Total supply.
217  mapping total_supply:
218      key as u8.public;
219      value as u128.public;
220  
221  /**********************************************************************************************************************/
222  // FUNCTIONS
223  /**********************************************************************************************************************/
224  
225  // Initialize the sAX program.
226  function initialize:
227      input r0 as address.public;  // Admin address.
228      input r1 as address.public;  // System address (for IPC).
229  
230      finalize initialize r0 r1 self.caller;
231  
232  finalize initialize:
233      input r0 as address.public;
234      input r1 as address.public;
235      input r2 as address.public;
236  
237      // Ensure not already initialized.
238      get.or_use config[0u8] sax_config {
239          min_lock_amount: 0u128,
240          finality_blocks: 0u32,
241          mint_paused: false,
242          burn_paused: false,
243          admin: r0,
244          system: r1
245      } into r3;
246      assert.eq r3.min_lock_amount 0u128;
247  
248      // Create configuration.
249      cast 10000u128 3u32 false false r0 r1 into r4 as sax_config;
250      set r4 into config[0u8];
251  
252      // Initialize counters.
253      set 0u128 into lock_counter[0u8];
254      set 0u128 into unlock_counter[0u8];
255      set 0u64 into transfer_counter[0u8];
256      set 0u128 into total_supply[0u8];
257  
258      // Initialize stats.
259      cast 0u128 0u128 0u64 0u64 0u32 into r5 as cross_chain_stats;
260      set r5 into stats[0u8];
261  
262  // Process lock attestation from ALPHA chain (system only).
263  // Called by adnet runtime when LockAttestation IPC message is received.
264  function process_lock:
265      input r0 as u128.public;  // Lock ID from ALPHA.
266      input r1 as address.public;  // User address.
267      input r2 as u128.public;  // Amount in microcredits.
268      input r3 as u64.public;   // ALPHA block number.
269      input r4 as field.public; // Merkle root (for verification).
270  
271      finalize process_lock r0 r1 r2 r3 r4 self.caller;
272  
273  finalize process_lock:
274      input r0 as u128.public;
275      input r1 as address.public;
276      input r2 as u128.public;
277      input r3 as u64.public;
278      input r4 as field.public;
279      input r5 as address.public;
280  
281      // Verify caller is system.
282      get config[0u8] into r6;
283      assert.eq r5 r6.system;
284  
285      // Check minting is not paused.
286      assert.eq r6.mint_paused false;
287  
288      // Verify amount meets minimum.
289      gte r2 r6.min_lock_amount into r7;
290      assert.eq r7 true;
291  
292      // Ensure lock hasn't been processed (prevent replay).
293      get.or_use processed_locks[r0] false into r8;
294      assert.eq r8 false;
295  
296      // Mark lock as processed.
297      set true into processed_locks[r0];
298  
299      // Create lock attestation record.
300      cast r0 r1 r2 r3 r4 1u8 block.height into r9 as lock_attestation;
301      set r9 into lock_attestations[r0];
302  
303      // Mint sAX to user.
304      get.or_use balances[r1] sax_balance {
305          owner: r1,
306          balance: 0u128,
307          pending_unlock: 0u128,
308          updated_at: 0u32
309      } into r10;
310  
311      add r10.balance r2 into r11;
312      cast r1 r11 r10.pending_unlock block.height into r12 as sax_balance;
313      set r12 into balances[r1];
314  
315      // Update total supply.
316      get.or_use total_supply[0u8] 0u128 into r13;
317      add r13 r2 into r14;
318      set r14 into total_supply[0u8];
319  
320      // Update stats.
321      get.or_use stats[0u8] cross_chain_stats {
322          total_locked: 0u128,
323          total_sax: 0u128,
324          lock_count: 0u64,
325          unlock_count: 0u64,
326          last_sync_block: 0u32
327      } into r15;
328  
329      add r15.total_locked r2 into r16;
330      add r15.total_sax r2 into r17;
331      add r15.lock_count 1u64 into r18;
332      cast r16 r17 r18 r15.unlock_count block.height into r19 as cross_chain_stats;
333      set r19 into stats[0u8];
334  
335  // Request to burn sAX and unlock AX on ALPHA.
336  function burn_for_ax:
337      input r0 as u128.public;  // Amount to burn.
338  
339      finalize burn_for_ax r0 self.caller;
340  
341  finalize burn_for_ax:
342      input r0 as u128.public;
343      input r1 as address.public;
344  
345      // Get config.
346      get config[0u8] into r2;
347  
348      // Check burning is not paused.
349      assert.eq r2.burn_paused false;
350  
351      // Verify amount meets minimum.
352      gte r0 r2.min_lock_amount into r3;
353      assert.eq r3 true;
354  
355      // Get user balance.
356      get balances[r1] into r4;
357  
358      // Verify sufficient balance.
359      gte r4.balance r0 into r5;
360      assert.eq r5 true;
361  
362      // Deduct from balance and add to pending unlock.
363      sub r4.balance r0 into r6;
364      add r4.pending_unlock r0 into r7;
365      cast r1 r6 r7 block.height into r8 as sax_balance;
366      set r8 into balances[r1];
367  
368      // Create unlock request.
369      get.or_use unlock_counter[0u8] 0u128 into r9;
370      add r9 1u128 into r10;
371      set r10 into unlock_counter[0u8];
372  
373      cast r10 r1 r0 block.height 0u8 0u32 into r11 as unlock_request;
374      set r11 into unlock_requests[r10];
375  
376      // Update pending unlocks for user.
377      get.or_use pending_unlocks[r1] 0u128 into r12;
378      add r12 r0 into r13;
379      set r13 into pending_unlocks[r1];
380  
381      // Note: IPC will send UnlockAttestation to ALPHA after finality.
382  
383  // Finalize unlock (called by system after DELTA finality).
384  function finalize_unlock:
385      input r0 as u128.public;  // Unlock ID.
386  
387      finalize finalize_unlock r0 self.caller;
388  
389  finalize finalize_unlock:
390      input r0 as u128.public;
391      input r1 as address.public;
392  
393      // Verify caller is system.
394      get config[0u8] into r2;
395      assert.eq r1 r2.system;
396  
397      // Get unlock request.
398      get unlock_requests[r0] into r3;
399  
400      // Must be pending.
401      assert.eq r3.status 0u8;
402  
403      // Verify finality (3 blocks).
404      add r3.requested_at r2.finality_blocks into r4;
405      gte block.height r4 into r5;
406      assert.eq r5 true;
407  
408      // Update status to finalized.
409      cast r3.unlock_id r3.user r3.amount r3.requested_at 1u8 block.height into r6 as unlock_request;
410      set r6 into unlock_requests[r0];
411  
412      // Reduce total supply (sAX effectively burned).
413      get total_supply[0u8] into r7;
414      sub r7 r3.amount into r8;
415      set r8 into total_supply[0u8];
416  
417      // Update stats.
418      get stats[0u8] into r9;
419      sub r9.total_sax r3.amount into r10;
420      add r9.unlock_count 1u64 into r11;
421      cast r9.total_locked r10 r9.lock_count r11 block.height into r12 as cross_chain_stats;
422      set r12 into stats[0u8];
423  
424      // Clear user's pending unlock.
425      get pending_unlocks[r3.user] into r13;
426      sub r13 r3.amount into r14;
427      set r14 into pending_unlocks[r3.user];
428  
429      // Clear pending from balance.
430      get balances[r3.user] into r15;
431      sub r15.pending_unlock r3.amount into r16;
432      cast r3.user r15.balance r16 block.height into r17 as sax_balance;
433      set r17 into balances[r3.user];
434  
435  // Mark unlock as completed (after AX released on ALPHA).
436  function complete_unlock:
437      input r0 as u128.public;  // Unlock ID.
438  
439      finalize complete_unlock r0 self.caller;
440  
441  finalize complete_unlock:
442      input r0 as u128.public;
443      input r1 as address.public;
444  
445      // Verify caller is system.
446      get config[0u8] into r2;
447      assert.eq r1 r2.system;
448  
449      // Get unlock request.
450      get unlock_requests[r0] into r3;
451  
452      // Must be finalized.
453      assert.eq r3.status 1u8;
454  
455      // Update status to completed.
456      cast r3.unlock_id r3.user r3.amount r3.requested_at 2u8 r3.finalized_at into r4 as unlock_request;
457      set r4 into unlock_requests[r0];
458  
459      // Reduce total locked (AX released on ALPHA side).
460      get stats[0u8] into r5;
461      sub r5.total_locked r3.amount into r6;
462      cast r6 r5.total_sax r5.lock_count r5.unlock_count r5.last_sync_block into r7 as cross_chain_stats;
463      set r7 into stats[0u8];
464  
465  // Transfer sAX to another address.
466  function transfer:
467      input r0 as address.public;  // To address.
468      input r1 as u128.public;     // Amount.
469  
470      finalize transfer r0 r1 self.caller;
471  
472  finalize transfer:
473      input r0 as address.public;
474      input r1 as u128.public;
475      input r2 as address.public;
476  
477      // Get sender balance.
478      get balances[r2] into r3;
479  
480      // Verify sufficient balance (excluding pending unlock).
481      sub r3.balance r3.pending_unlock into r4;
482      gte r4 r1 into r5;
483      assert.eq r5 true;
484  
485      // Deduct from sender.
486      sub r3.balance r1 into r6;
487      cast r2 r6 r3.pending_unlock block.height into r7 as sax_balance;
488      set r7 into balances[r2];
489  
490      // Add to recipient.
491      get.or_use balances[r0] sax_balance {
492          owner: r0,
493          balance: 0u128,
494          pending_unlock: 0u128,
495          updated_at: 0u32
496      } into r8;
497  
498      add r8.balance r1 into r9;
499      cast r0 r9 r8.pending_unlock block.height into r10 as sax_balance;
500      set r10 into balances[r0];
501  
502      // Create transfer record.
503      get transfer_counter[0u8] into r11;
504      add r11 1u64 into r12;
505      set r12 into transfer_counter[0u8];
506  
507      cast r12 r2 r0 r1 block.height into r13 as transfer_record;
508      set r13 into transfers[r12];
509  
510  // Approve allowance for delegated transfer.
511  function approve:
512      input r0 as address.public;  // Spender.
513      input r1 as u128.public;     // Amount.
514  
515      finalize approve r0 r1 self.caller;
516  
517  finalize approve:
518      input r0 as address.public;
519      input r1 as u128.public;
520      input r2 as address.public;
521  
522      // Create allowance key: hash(owner, spender).
523      hash.bhp256 r2 into r3 as field;
524      hash.bhp256 r0 into r4 as field;
525      add r3 r4 into r5;
526  
527      set r1 into allowances[r5];
528  
529  // Transfer from allowance.
530  function transfer_from:
531      input r0 as address.public;  // From address.
532      input r1 as address.public;  // To address.
533      input r2 as u128.public;     // Amount.
534  
535      finalize transfer_from r0 r1 r2 self.caller;
536  
537  finalize transfer_from:
538      input r0 as address.public;
539      input r1 as address.public;
540      input r2 as u128.public;
541      input r3 as address.public;
542  
543      // Check allowance.
544      hash.bhp256 r0 into r4 as field;
545      hash.bhp256 r3 into r5 as field;
546      add r4 r5 into r6;
547  
548      get allowances[r6] into r7;
549      gte r7 r2 into r8;
550      assert.eq r8 true;
551  
552      // Deduct allowance.
553      sub r7 r2 into r9;
554      set r9 into allowances[r6];
555  
556      // Get from balance.
557      get balances[r0] into r10;
558  
559      // Verify sufficient balance.
560      sub r10.balance r10.pending_unlock into r11;
561      gte r11 r2 into r12;
562      assert.eq r12 true;
563  
564      // Deduct from sender.
565      sub r10.balance r2 into r13;
566      cast r0 r13 r10.pending_unlock block.height into r14 as sax_balance;
567      set r14 into balances[r0];
568  
569      // Add to recipient.
570      get.or_use balances[r1] sax_balance {
571          owner: r1,
572          balance: 0u128,
573          pending_unlock: 0u128,
574          updated_at: 0u32
575      } into r15;
576  
577      add r15.balance r2 into r16;
578      cast r1 r16 r15.pending_unlock block.height into r17 as sax_balance;
579      set r17 into balances[r1];
580  
581  // Pause minting (admin only, emergency).
582  function pause_mint:
583      input r0 as boolean.public;  // Pause state.
584  
585      finalize pause_mint r0 self.caller;
586  
587  finalize pause_mint:
588      input r0 as boolean.public;
589      input r1 as address.public;
590  
591      // Verify admin.
592      get config[0u8] into r2;
593      assert.eq r1 r2.admin;
594  
595      // Update config.
596      cast r2.min_lock_amount r2.finality_blocks r0 r2.burn_paused r2.admin r2.system into r3 as sax_config;
597      set r3 into config[0u8];
598  
599  // Pause burning (admin only, emergency).
600  function pause_burn:
601      input r0 as boolean.public;  // Pause state.
602  
603      finalize pause_burn r0 self.caller;
604  
605  finalize pause_burn:
606      input r0 as boolean.public;
607      input r1 as address.public;
608  
609      // Verify admin.
610      get config[0u8] into r2;
611      assert.eq r1 r2.admin;
612  
613      // Update config.
614      cast r2.min_lock_amount r2.finality_blocks r2.mint_paused r0 r2.admin r2.system into r3 as sax_config;
615      set r3 into config[0u8];
616  
617  // Get balance of an address.
618  function balance_of:
619      input r0 as address.public;
620  
621      finalize balance_of r0;
622  
623  finalize balance_of:
624      input r0 as address.public;
625  
626      // Read-only operation.
627      get.or_use balances[r0] sax_balance {
628          owner: r0,
629          balance: 0u128,
630          pending_unlock: 0u128,
631          updated_at: 0u32
632      } into r1;
633  
634  // Get total supply.
635  function get_total_supply:
636      finalize get_total_supply;
637  
638  finalize get_total_supply:
639      // Read-only operation.
640      get total_supply[0u8] into r0;
641  
642  // Get cross-chain statistics.
643  function get_stats:
644      finalize get_stats;
645  
646  finalize get_stats:
647      // Read-only operation.
648      get stats[0u8] into r0;