/ synthesizer / program / src / resources / dividends.delta
dividends.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 Dividends Program
 18  //
 19  // Collects trading fees from exchange and perpetuals programs and distributes
 20  // them to staked DX holders at each epoch boundary.
 21  //
 22  // Revenue Sources:
 23  // - Spot trading fees from delta.exchange
 24  // - Perpetual trading fees from delta.perpetuals
 25  // - Liquidation proceeds from delta.perpetuals
 26  //
 27  // Distribution Model:
 28  // - Per-epoch distribution to staked DX holders
 29  // - Pro-rata based on staking weight
 30  // - Claimable anytime after distribution
 31  // - Unclaimed dividends accrue across epochs
 32  //
 33  // Fee Allocation:
 34  // - 70% to DX stakers (this program)
 35  // - 20% to Insurance Fund (retained by perpetuals)
 36  // - 10% to Operations (governance controlled)
 37  /**********************************************************************************************************************/
 38  
 39  program delta.dividends;
 40  
 41  /**********************************************************************************************************************/
 42  // CONSTANTS (represented as literals in code)
 43  /**********************************************************************************************************************/
 44  
 45  // STAKER_SHARE_BPS = 7000 (70%)
 46  // OPERATIONS_SHARE_BPS = 1000 (10%)
 47  // EPOCH_BLOCKS = 8640 (1 day at 10s blocks)
 48  // MIN_CLAIM_AMOUNT = 1000 (0.001 sAX in microcredits)
 49  
 50  /**********************************************************************************************************************/
 51  // STRUCTS
 52  /**********************************************************************************************************************/
 53  
 54  // Dividend pool state for an epoch.
 55  struct dividend_pool:
 56      // Epoch number.
 57      epoch as u64;
 58      // Total fees collected this epoch (in sAX microcredits).
 59      total_fees as u128;
 60      // Total staked DX at snapshot.
 61      total_staked as u128;
 62      // Dividend per DX (scaled by 1e18 for precision).
 63      dividend_per_dx as u128;
 64      // Block when epoch started.
 65      start_block as u32;
 66      // Block when epoch ended (0 if current).
 67      end_block as u32;
 68      // Whether distribution has been finalized.
 69      finalized as boolean;
 70  
 71  // User's dividend tracking.
 72  struct user_dividends:
 73      // User address.
 74      user as address;
 75      // Total claimed across all epochs.
 76      total_claimed as u128;
 77      // Last epoch claimed.
 78      last_claim_epoch as u64;
 79      // Pending (unclaimed) dividends.
 80      pending as u128;
 81  
 82  // Fee deposit record.
 83  struct fee_deposit:
 84      // Deposit ID.
 85      deposit_id as u64;
 86      // Source: 0 = exchange, 1 = perpetuals, 2 = liquidation.
 87      source as u8;
 88      // Amount in sAX microcredits.
 89      amount as u128;
 90      // Block when deposited.
 91      deposited_at as u32;
 92      // Epoch this deposit belongs to.
 93      epoch as u64;
 94  
 95  // Operations wallet for governance-controlled share.
 96  struct operations_wallet:
 97      // Wallet address (governance controlled).
 98      wallet as address;
 99      // Total collected.
100      total_collected as u128;
101      // Last updated block.
102      updated_at as u32;
103  
104  // Epoch snapshot for staking weights.
105  struct epoch_snapshot:
106      // Epoch number.
107      epoch as u64;
108      // Total staked DX at snapshot.
109      total_staked as u128;
110      // Number of stakers.
111      staker_count as u64;
112      // Block when snapshot was taken.
113      snapshot_block as u32;
114  
115  // User stake snapshot for dividend calculation.
116  struct stake_snapshot:
117      // User address.
118      user as address;
119      // Epoch number.
120      epoch as u64;
121      // Staked DX at snapshot.
122      staked_dx as u128;
123      // Weight for dividend calculation.
124      weight as u128;
125  
126  // Claim record for audit trail.
127  struct claim_record:
128      // Claim ID.
129      claim_id as u64;
130      // User address.
131      user as address;
132      // Amount claimed in sAX.
133      amount as u128;
134      // Epoch range claimed (from).
135      from_epoch as u64;
136      // Epoch range claimed (to).
137      to_epoch as u64;
138      // Block when claimed.
139      claimed_at as u32;
140  
141  // Program configuration.
142  struct dividend_config:
143      // Staker share in basis points (7000 = 70%).
144      staker_share_bps as u16;
145      // Operations share in basis points (1000 = 10%).
146      operations_share_bps as u16;
147      // Minimum claim amount in microcredits.
148      min_claim_amount as u128;
149      // Blocks per epoch.
150      epoch_blocks as u32;
151      // Operations wallet address.
152      operations_wallet as address;
153      // Admin address for configuration.
154      admin as address;
155      // Whether the program is active.
156      active as boolean;
157  
158  /**********************************************************************************************************************/
159  // MAPPINGS
160  /**********************************************************************************************************************/
161  
162  // Current epoch number.
163  mapping current_epoch:
164      key as u8.public;
165      value as u64.public;
166  
167  // Dividend pools by epoch.
168  mapping dividend_pools:
169      key as u64.public;
170      value as dividend_pool.public;
171  
172  // User dividend tracking.
173  mapping user_dividend_info:
174      key as address.public;
175      value as user_dividends.public;
176  
177  // Fee deposit counter.
178  mapping deposit_counter:
179      key as u8.public;
180      value as u64.public;
181  
182  // Fee deposits by ID.
183  mapping fee_deposits:
184      key as u64.public;
185      value as fee_deposit.public;
186  
187  // Epoch snapshots.
188  mapping epoch_snapshots:
189      key as u64.public;
190      value as epoch_snapshot.public;
191  
192  // User stake snapshots (key = hash(user, epoch)).
193  mapping stake_snapshots:
194      key as field.public;
195      value as stake_snapshot.public;
196  
197  // Claim counter.
198  mapping claim_counter:
199      key as u8.public;
200      value as u64.public;
201  
202  // Claim records by ID.
203  mapping claim_records:
204      key as u64.public;
205      value as claim_record.public;
206  
207  // Program configuration.
208  mapping config:
209      key as u8.public;
210      value as dividend_config.public;
211  
212  // Operations wallet state.
213  mapping operations:
214      key as u8.public;
215      value as operations_wallet.public;
216  
217  // Total fees collected all time.
218  mapping total_stats:
219      key as u8.public;
220      value as u128.public;
221  
222  // Authorized fee depositors (exchange, perpetuals).
223  mapping authorized_depositors:
224      key as address.public;
225      value as boolean.public;
226  
227  /**********************************************************************************************************************/
228  // FUNCTIONS
229  /**********************************************************************************************************************/
230  
231  // Initialize the dividends program.
232  function initialize:
233      input r0 as address.public;  // Operations wallet address.
234      input r1 as address.public;  // Admin address.
235  
236      // Create default configuration.
237      // Staker share: 70%, Operations: 10% (20% stays in insurance fund).
238  
239      finalize initialize r0 r1 self.caller;
240  
241  finalize initialize:
242      input r0 as address.public;
243      input r1 as address.public;
244      input r2 as address.public;
245  
246      // Ensure not already initialized.
247      get.or_use config[0u8] dividend_config {
248          staker_share_bps: 0u16,
249          operations_share_bps: 0u16,
250          min_claim_amount: 0u128,
251          epoch_blocks: 0u32,
252          operations_wallet: r0,
253          admin: r1,
254          active: false
255      } into r3;
256      assert.eq r3.active false;
257  
258      // Create configuration.
259      cast 7000u16 1000u16 1000u128 8640u32 r0 r1 true into r4 as dividend_config;
260      set r4 into config[0u8];
261  
262      // Initialize epoch to 1.
263      set 1u64 into current_epoch[0u8];
264  
265      // Initialize operations wallet.
266      cast r0 0u128 0u32 into r5 as operations_wallet;
267      set r5 into operations[0u8];
268  
269      // Initialize counters.
270      set 0u64 into deposit_counter[0u8];
271      set 0u64 into claim_counter[0u8];
272      set 0u128 into total_stats[0u8];
273  
274  // Collect fees from authorized sources (exchange, perpetuals).
275  function collect_fees:
276      input r0 as u128.public;  // Amount in sAX microcredits.
277      input r1 as u8.public;    // Source: 0 = exchange, 1 = perpetuals, 2 = liquidation.
278  
279      // Only authorized depositors can call this.
280      // Amount must be positive.
281      assert.neq r0 0u128;
282  
283      finalize collect_fees r0 r1 self.caller;
284  
285  finalize collect_fees:
286      input r0 as u128.public;
287      input r1 as u8.public;
288      input r2 as address.public;
289  
290      // Verify caller is authorized depositor.
291      get.or_use authorized_depositors[r2] false into r3;
292      assert.eq r3 true;
293  
294      // Get current epoch.
295      get.or_use current_epoch[0u8] 1u64 into r4;
296  
297      // Get deposit counter.
298      get.or_use deposit_counter[0u8] 0u64 into r5;
299      add r5 1u64 into r6;
300      set r6 into deposit_counter[0u8];
301  
302      // Create fee deposit record.
303      cast r6 r1 r0 block.height r4 into r7 as fee_deposit;
304      set r7 into fee_deposits[r6];
305  
306      // Update current epoch pool.
307      get.or_use dividend_pools[r4] dividend_pool {
308          epoch: r4,
309          total_fees: 0u128,
310          total_staked: 0u128,
311          dividend_per_dx: 0u128,
312          start_block: block.height,
313          end_block: 0u32,
314          finalized: false
315      } into r8;
316  
317      // Calculate staker portion (70%).
318      mul r0 7000u128 into r9;
319      div r9 10000u128 into r10;
320  
321      // Add to pool.
322      add r8.total_fees r10 into r11;
323      cast r8.epoch r11 r8.total_staked r8.dividend_per_dx r8.start_block r8.end_block r8.finalized into r12 as dividend_pool;
324      set r12 into dividend_pools[r4];
325  
326      // Calculate operations portion (10%).
327      mul r0 1000u128 into r13;
328      div r13 10000u128 into r14;
329  
330      // Update operations wallet.
331      get.or_use operations[0u8] operations_wallet {
332          wallet: r2,
333          total_collected: 0u128,
334          updated_at: 0u32
335      } into r15;
336      add r15.total_collected r14 into r16;
337      cast r15.wallet r16 block.height into r17 as operations_wallet;
338      set r17 into operations[0u8];
339  
340      // Update total stats.
341      get.or_use total_stats[0u8] 0u128 into r18;
342      add r18 r0 into r19;
343      set r19 into total_stats[0u8];
344  
345  // Take epoch snapshot for staking weights.
346  // Called at the start of each epoch by the system.
347  function take_epoch_snapshot:
348      input r0 as u64.public;   // Epoch number.
349      input r1 as u128.public;  // Total staked DX.
350      input r2 as u64.public;   // Number of stakers.
351  
352      finalize take_epoch_snapshot r0 r1 r2;
353  
354  finalize take_epoch_snapshot:
355      input r0 as u64.public;
356      input r1 as u128.public;
357      input r2 as u64.public;
358  
359      // Create epoch snapshot.
360      cast r0 r1 r2 block.height into r3 as epoch_snapshot;
361      set r3 into epoch_snapshots[r0];
362  
363      // Update pool with staking total.
364      get.or_use dividend_pools[r0] dividend_pool {
365          epoch: r0,
366          total_fees: 0u128,
367          total_staked: 0u128,
368          dividend_per_dx: 0u128,
369          start_block: block.height,
370          end_block: 0u32,
371          finalized: false
372      } into r4;
373      cast r4.epoch r4.total_fees r1 r4.dividend_per_dx r4.start_block r4.end_block r4.finalized into r5 as dividend_pool;
374      set r5 into dividend_pools[r0];
375  
376  // Record user's stake for an epoch.
377  function record_stake:
378      input r0 as address.public;  // User address.
379      input r1 as u64.public;      // Epoch.
380      input r2 as u128.public;     // Staked DX amount.
381  
382      finalize record_stake r0 r1 r2;
383  
384  finalize record_stake:
385      input r0 as address.public;
386      input r1 as u64.public;
387      input r2 as u128.public;
388  
389      // Create snapshot key: hash(user, epoch).
390      hash.bhp256 r0 into r3 as field;
391      cast r1 into r4 as field;
392      add r3 r4 into r5;
393  
394      // Create stake snapshot.
395      cast r0 r1 r2 r2 into r6 as stake_snapshot;
396      set r6 into stake_snapshots[r5];
397  
398  // Finalize epoch and calculate dividends.
399  function finalize_epoch:
400      input r0 as u64.public;  // Epoch to finalize.
401  
402      finalize finalize_epoch r0;
403  
404  finalize finalize_epoch:
405      input r0 as u64.public;
406  
407      // Get pool for this epoch.
408      get dividend_pools[r0] into r1;
409      assert.eq r1.finalized false;
410  
411      // Ensure epoch has ended (next epoch started).
412      get current_epoch[0u8] into r2;
413      gt r2 r0 into r3;
414      assert.eq r3 true;
415  
416      // Calculate dividend per DX (scaled by 1e18).
417      // dividend_per_dx = total_fees * 1e18 / total_staked
418      gt r1.total_staked 0u128 into r4;
419      ternary r4 r1.total_staked 1u128 into r5;
420  
421      mul r1.total_fees 1000000000000000000u128 into r6;
422      div r6 r5 into r7;
423  
424      // Mark pool as finalized.
425      cast r1.epoch r1.total_fees r1.total_staked r7 r1.start_block block.height true into r8 as dividend_pool;
426      set r8 into dividend_pools[r0];
427  
428  // Claim pending dividends.
429  function claim_dividends:
430      input r0 as u64.public;  // From epoch.
431      input r1 as u64.public;  // To epoch (inclusive).
432  
433      finalize claim_dividends r0 r1 self.caller;
434  
435  finalize claim_dividends:
436      input r0 as u64.public;
437      input r1 as u64.public;
438      input r2 as address.public;
439  
440      // Get current epoch.
441      get current_epoch[0u8] into r3;
442  
443      // Ensure to_epoch is finalized (less than current).
444      lt r1 r3 into r4;
445      assert.eq r4 true;
446  
447      // Get user's dividend info.
448      get.or_use user_dividend_info[r2] user_dividends {
449          user: r2,
450          total_claimed: 0u128,
451          last_claim_epoch: 0u64,
452          pending: 0u128
453      } into r5;
454  
455      // From epoch must be after last claimed epoch.
456      gte r0 r5.last_claim_epoch into r6;
457      assert.eq r6 true;
458  
459      // Calculate total claimable (simplified - in practice would loop through epochs).
460      // For demonstration, we'll claim for a single epoch.
461      // Real implementation would iterate or use accumulator pattern.
462  
463      // Get stake snapshot for from_epoch.
464      hash.bhp256 r2 into r7 as field;
465      cast r0 into r8 as field;
466      add r7 r8 into r9;
467      get.or_use stake_snapshots[r9] stake_snapshot {
468          user: r2,
469          epoch: r0,
470          staked_dx: 0u128,
471          weight: 0u128
472      } into r10;
473  
474      // Get pool for from_epoch.
475      get dividend_pools[r0] into r11;
476      assert.eq r11.finalized true;
477  
478      // Calculate user's share.
479      // user_dividend = (staked_dx * dividend_per_dx) / 1e18
480      mul r10.staked_dx r11.dividend_per_dx into r12;
481      div r12 1000000000000000000u128 into r13;
482  
483      // Check minimum claim amount.
484      get config[0u8] into r14;
485      gte r13 r14.min_claim_amount into r15;
486      assert.eq r15 true;
487  
488      // Update user's dividend info.
489      add r5.total_claimed r13 into r16;
490      add r5.pending 0u128 into r17;  // Clear pending after claim.
491      cast r2 r16 r1 r17 into r18 as user_dividends;
492      set r18 into user_dividend_info[r2];
493  
494      // Create claim record.
495      get claim_counter[0u8] into r19;
496      add r19 1u64 into r20;
497      set r20 into claim_counter[0u8];
498  
499      cast r20 r2 r13 r0 r1 block.height into r21 as claim_record;
500      set r21 into claim_records[r20];
501  
502      // Note: Actual sAX transfer would be handled by calling delta.sax::transfer.
503  
504  // Get pending dividends for a user.
505  function get_pending:
506      input r0 as address.public;  // User address.
507  
508      // Read-only operation, returns pending amount.
509      finalize get_pending r0;
510  
511  finalize get_pending:
512      input r0 as address.public;
513  
514      // Get user info.
515      get.or_use user_dividend_info[r0] user_dividends {
516          user: r0,
517          total_claimed: 0u128,
518          last_claim_epoch: 0u64,
519          pending: 0u128
520      } into r1;
521  
522      // Note: Actual pending calculation would iterate unclaimed epochs.
523      // This is a simplified read operation.
524  
525  // Start new epoch (called by system at epoch boundary).
526  function start_new_epoch:
527      input r0 as u64.public;  // New epoch number.
528  
529      finalize start_new_epoch r0;
530  
531  finalize start_new_epoch:
532      input r0 as u64.public;
533  
534      // Get current epoch.
535      get current_epoch[0u8] into r1;
536  
537      // New epoch must be current + 1.
538      add r1 1u64 into r2;
539      assert.eq r0 r2;
540  
541      // Finalize previous epoch if not already.
542      get.or_use dividend_pools[r1] dividend_pool {
543          epoch: r1,
544          total_fees: 0u128,
545          total_staked: 0u128,
546          dividend_per_dx: 0u128,
547          start_block: 0u32,
548          end_block: 0u32,
549          finalized: false
550      } into r3;
551  
552      // Mark previous pool end block.
553      cast r3.epoch r3.total_fees r3.total_staked r3.dividend_per_dx r3.start_block block.height r3.finalized into r4 as dividend_pool;
554      set r4 into dividend_pools[r1];
555  
556      // Set new current epoch.
557      set r0 into current_epoch[0u8];
558  
559      // Initialize new pool.
560      cast r0 0u128 0u128 0u128 block.height 0u32 false into r5 as dividend_pool;
561      set r5 into dividend_pools[r0];
562  
563  // Authorize a fee depositor (admin only).
564  function authorize_depositor:
565      input r0 as address.public;  // Depositor address.
566      input r1 as boolean.public;  // Authorized status.
567  
568      finalize authorize_depositor r0 r1 self.caller;
569  
570  finalize authorize_depositor:
571      input r0 as address.public;
572      input r1 as boolean.public;
573      input r2 as address.public;
574  
575      // Verify admin.
576      get config[0u8] into r3;
577      assert.eq r2 r3.admin;
578  
579      // Set authorization.
580      set r1 into authorized_depositors[r0];
581  
582  // Withdraw operations funds (operations wallet only).
583  function withdraw_operations:
584      input r0 as u128.public;  // Amount to withdraw.
585  
586      finalize withdraw_operations r0 self.caller;
587  
588  finalize withdraw_operations:
589      input r0 as u128.public;
590      input r1 as address.public;
591  
592      // Get operations wallet.
593      get operations[0u8] into r2;
594  
595      // Verify caller is operations wallet.
596      assert.eq r1 r2.wallet;
597  
598      // Verify sufficient balance.
599      gte r2.total_collected r0 into r3;
600      assert.eq r3 true;
601  
602      // Update balance.
603      sub r2.total_collected r0 into r4;
604      cast r2.wallet r4 block.height into r5 as operations_wallet;
605      set r5 into operations[0u8];
606  
607      // Note: Actual sAX transfer would be handled by calling delta.sax::transfer.
608  
609  // Update configuration (admin only).
610  function update_config:
611      input r0 as u16.public;     // New staker share BPS.
612      input r1 as u16.public;     // New operations share BPS.
613      input r2 as u128.public;    // New min claim amount.
614      input r3 as address.public; // New operations wallet.
615  
616      // Validate shares don't exceed 80% (20% stays in insurance fund).
617      add r0 r1 into r4;
618      lte r4 8000u16 into r5;
619      assert.eq r5 true;
620  
621      finalize update_config r0 r1 r2 r3 self.caller;
622  
623  finalize update_config:
624      input r0 as u16.public;
625      input r1 as u16.public;
626      input r2 as u128.public;
627      input r3 as address.public;
628      input r4 as address.public;
629  
630      // Verify admin.
631      get config[0u8] into r5;
632      assert.eq r4 r5.admin;
633  
634      // Update configuration.
635      cast r0 r1 r2 r5.epoch_blocks r3 r5.admin r5.active into r6 as dividend_config;
636      set r6 into config[0u8];
637  
638      // Update operations wallet address.
639      get operations[0u8] into r7;
640      cast r3 r7.total_collected block.height into r8 as operations_wallet;
641      set r8 into operations[0u8];
642  
643  // Get dividend pool info for an epoch.
644  function get_pool:
645      input r0 as u64.public;  // Epoch number.
646  
647      finalize get_pool r0;
648  
649  finalize get_pool:
650      input r0 as u64.public;
651  
652      // Read-only operation.
653      get dividend_pools[r0] into r1;
654  
655  // Get epoch snapshot.
656  function get_snapshot:
657      input r0 as u64.public;  // Epoch number.
658  
659      finalize get_snapshot r0;
660  
661  finalize get_snapshot:
662      input r0 as u64.public;
663  
664      // Read-only operation.
665      get epoch_snapshots[r0] into r1;