/ synthesizer / program / src / resources / oracle.delta
oracle.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 Oracle Program
 18  //
 19  // Provides price feed functionality for the DELTA exchange, including:
 20  // - Chainlink-compatible price updates from authorized oracles
 21  // - TWAP (Time-Weighted Average Price) calculation
 22  // - Staleness checking for price data
 23  // - Multi-asset support (crypto, forex, commodities)
 24  //
 25  // Parameters:
 26  // - Deviation threshold: 0.5% (50 basis points)
 27  // - Heartbeat: 3600 seconds (1 hour max staleness)
 28  // - Price precision: 8 decimals (matching Chainlink standard)
 29  /**********************************************************************************************************************/
 30  
 31  program delta.oracle;
 32  
 33  /**********************************************************************************************************************/
 34  
 35  // The `price_feeds` mapping stores the latest price data for each asset.
 36  mapping price_feeds:
 37      // The key represents the asset ID (e.g., hash of "BTC/USD").
 38      key as field.public;
 39      // The value represents the price feed data.
 40      value as price_feed.public;
 41  
 42  // The `price_feed` struct contains the current price and metadata.
 43  struct price_feed:
 44      // The current price in 8 decimal precision (e.g., 50000_00000000 for $50,000).
 45      price as u128;
 46      // The timestamp of the last update (block height).
 47      timestamp as u32;
 48      // The round ID for this price update.
 49      round_id as u64;
 50      // Confidence/deviation indicator (basis points, 100 = 1%).
 51      confidence as u16;
 52  
 53  /**********************************************************************************************************************/
 54  
 55  // The `twap_data` mapping stores TWAP accumulator data for each asset.
 56  mapping twap_data:
 57      // The key represents the asset ID.
 58      key as field.public;
 59      // The value represents the TWAP accumulator.
 60      value as twap_accumulator.public;
 61  
 62  // The `twap_accumulator` struct tracks cumulative price data for TWAP calculation.
 63  struct twap_accumulator:
 64      // Cumulative price × time product.
 65      cumulative_price as u128;
 66      // Last update timestamp.
 67      last_timestamp as u32;
 68      // Last recorded price.
 69      last_price as u128;
 70  
 71  /**********************************************************************************************************************/
 72  
 73  // The `oracle_config` mapping stores configuration for each oracle/asset pair.
 74  mapping oracle_config:
 75      // The key represents the asset ID.
 76      key as field.public;
 77      // The value represents the oracle configuration.
 78      value as oracle_settings.public;
 79  
 80  // The `oracle_settings` struct defines oracle parameters per asset.
 81  struct oracle_settings:
 82      // Maximum staleness in seconds (default: 3600 = 1 hour).
 83      heartbeat as u32;
 84      // Deviation threshold in basis points (default: 50 = 0.5%).
 85      deviation_threshold as u16;
 86      // Whether the oracle is active.
 87      is_active as boolean;
 88      // Minimum number of sources required (for aggregation).
 89      min_sources as u8;
 90  
 91  /**********************************************************************************************************************/
 92  
 93  // The `authorized_oracles` mapping stores addresses permitted to update prices.
 94  mapping authorized_oracles:
 95      // The key represents the oracle address.
 96      key as address.public;
 97      // The value indicates if the oracle is authorized (true/false).
 98      value as boolean.public;
 99  
100  /**********************************************************************************************************************/
101  
102  // The `admin` mapping stores the oracle admin address.
103  // Key: ax1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd (sentinel)
104  mapping admin:
105      key as address.public;
106      value as address.public;
107  
108  /**********************************************************************************************************************/
109  
110  // Update price for an asset. Only authorized oracles can call this.
111  function update_price:
112      // Input the asset ID.
113      input r0 as field.public;
114      // Input the new price (8 decimal precision).
115      input r1 as u128.public;
116      // Input the round ID.
117      input r2 as u64.public;
118      // Input the confidence (basis points).
119      input r3 as u16.public;
120  
121      // Ensure confidence is within valid range (0-10000 basis points = 0-100%).
122      lte r3 10000u16 into r4;
123      assert.eq r4 true;
124  
125      // Ensure price is non-zero.
126      gt r1 0u128 into r5;
127      assert.eq r5 true;
128  
129      // Execute the price update.
130      async update_price self.caller r0 r1 r2 r3 into r6;
131      // Output the finalize future.
132      output r6 as delta.oracle/update_price.future;
133  
134  finalize update_price:
135      // Input the caller (oracle address).
136      input r0 as address.public;
137      // Input the asset ID.
138      input r1 as field.public;
139      // Input the price.
140      input r2 as u128.public;
141      // Input the round ID.
142      input r3 as u64.public;
143      // Input the confidence.
144      input r4 as u16.public;
145  
146      // Verify the caller is an authorized oracle.
147      get authorized_oracles[r0] into r5;
148      assert.eq r5 true;
149  
150      // Get the current price feed, or create default.
151      cast 0u128 0u32 0u64 0u16 into r6 as price_feed;
152      get.or_use price_feeds[r1] r6 into r7;
153  
154      // Ensure round ID is increasing (prevents replay).
155      gt r3 r7.round_id into r8;
156      assert.eq r8 true;
157  
158      // Construct the updated price feed.
159      cast r2 block.height r3 r4 into r9 as price_feed;
160  
161      // Update TWAP accumulator.
162      cast 0u128 0u32 0u128 into r10 as twap_accumulator;
163      get.or_use twap_data[r1] r10 into r11;
164  
165      // Calculate time elapsed since last update.
166      sub block.height r11.last_timestamp into r12;
167  
168      // Add to cumulative price: last_price × time_elapsed.
169      cast r12 into r13 as u128;
170      mul r11.last_price r13 into r14;
171      add r11.cumulative_price r14 into r15;
172  
173      // Construct updated TWAP accumulator.
174      cast r15 block.height r2 into r16 as twap_accumulator;
175  
176      // Store the updated price feed.
177      set r9 into price_feeds[r1];
178      // Store the updated TWAP accumulator.
179      set r16 into twap_data[r1];
180  
181  /**********************************************************************************************************************/
182  
183  // Get the latest price for an asset (view function via finalize).
184  function get_price:
185      // Input the asset ID.
186      input r0 as field.public;
187  
188      // Retrieve the price.
189      async get_price r0 into r1;
190      output r1 as delta.oracle/get_price.future;
191  
192  finalize get_price:
193      // Input the asset ID.
194      input r0 as field.public;
195  
196      // Get the price feed (fails if not found).
197      get price_feeds[r0] into r1;
198  
199      // Get oracle settings for staleness check.
200      cast 3600u32 50u16 true 1u8 into r2 as oracle_settings;
201      get.or_use oracle_config[r0] r2 into r3;
202  
203      // Check if the price is stale (block.height - timestamp > heartbeat).
204      sub block.height r1.timestamp into r4;
205      lte r4 r3.heartbeat into r5;
206      // Assert price is not stale.
207      assert.eq r5 true;
208  
209  /**********************************************************************************************************************/
210  
211  // Check if a price is stale.
212  function is_stale:
213      // Input the asset ID.
214      input r0 as field.public;
215  
216      // Check staleness.
217      async is_stale r0 into r1;
218      output r1 as delta.oracle/is_stale.future;
219  
220  finalize is_stale:
221      // Input the asset ID.
222      input r0 as field.public;
223  
224      // Get the price feed.
225      cast 0u128 0u32 0u64 0u16 into r1 as price_feed;
226      get.or_use price_feeds[r0] r1 into r2;
227  
228      // Get oracle settings.
229      cast 3600u32 50u16 true 1u8 into r3 as oracle_settings;
230      get.or_use oracle_config[r0] r3 into r4;
231  
232      // Calculate age.
233      sub block.height r2.timestamp into r5;
234      // Price is stale if age > heartbeat.
235      gt r5 r4.heartbeat into r6;
236  
237      // Note: Result is in r6, but ADL doesn't have return values from finalize.
238      // Calling contracts must query price_feeds directly and check timestamp.
239  
240  /**********************************************************************************************************************/
241  
242  // Calculate TWAP over a specified duration (in blocks).
243  function get_twap:
244      // Input the asset ID.
245      input r0 as field.public;
246      // Input the duration in blocks.
247      input r1 as u32.public;
248  
249      // Ensure duration is positive.
250      gt r1 0u32 into r2;
251      assert.eq r2 true;
252  
253      // Calculate TWAP.
254      async get_twap r0 r1 into r3;
255      output r3 as delta.oracle/get_twap.future;
256  
257  finalize get_twap:
258      // Input the asset ID.
259      input r0 as field.public;
260      // Input the duration.
261      input r1 as u32.public;
262  
263      // Get TWAP accumulator.
264      get twap_data[r0] into r2;
265  
266      // Get current price feed.
267      get price_feeds[r0] into r3;
268  
269      // Calculate time since last update.
270      sub block.height r2.last_timestamp into r4;
271  
272      // Add pending accumulation.
273      cast r4 into r5 as u128;
274      mul r3.price r5 into r6;
275      add r2.cumulative_price r6 into r7;
276  
277      // TWAP = cumulative_price / duration.
278      // Note: This is a simplified version. Full implementation would track
279      // historical snapshots for accurate period-based TWAP.
280      cast r1 into r8 as u128;
281      div r7 r8 into r9;
282  
283      // Result is in r9 - contracts must query this value.
284  
285  /**********************************************************************************************************************/
286  
287  // Authorize a new oracle address. Only admin can call.
288  function authorize_oracle:
289      // Input the oracle address to authorize.
290      input r0 as address.public;
291  
292      async authorize_oracle self.caller r0 into r1;
293      output r1 as delta.oracle/authorize_oracle.future;
294  
295  finalize authorize_oracle:
296      // Input the caller.
297      input r0 as address.public;
298      // Input the oracle to authorize.
299      input r1 as address.public;
300  
301      // Verify caller is admin.
302      get admin[dx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd] into r2;
303      assert.eq r0 r2;
304  
305      // Authorize the oracle.
306      set true into authorized_oracles[r1];
307  
308  /**********************************************************************************************************************/
309  
310  // Revoke oracle authorization. Only admin can call.
311  function revoke_oracle:
312      // Input the oracle address to revoke.
313      input r0 as address.public;
314  
315      async revoke_oracle self.caller r0 into r1;
316      output r1 as delta.oracle/revoke_oracle.future;
317  
318  finalize revoke_oracle:
319      // Input the caller.
320      input r0 as address.public;
321      // Input the oracle to revoke.
322      input r1 as address.public;
323  
324      // Verify caller is admin.
325      get admin[dx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd] into r2;
326      assert.eq r0 r2;
327  
328      // Revoke the oracle.
329      set false into authorized_oracles[r1];
330  
331  /**********************************************************************************************************************/
332  
333  // Configure oracle settings for an asset. Only admin can call.
334  function configure_asset:
335      // Input the asset ID.
336      input r0 as field.public;
337      // Input the heartbeat (max staleness in blocks).
338      input r1 as u32.public;
339      // Input the deviation threshold (basis points).
340      input r2 as u16.public;
341      // Input the minimum sources required.
342      input r3 as u8.public;
343  
344      // Validate heartbeat is reasonable (1 block to 1 day ~= 28800 blocks at 3s).
345      gte r1 1u32 into r4;
346      lte r1 28800u32 into r5;
347      and r4 r5 into r6;
348      assert.eq r6 true;
349  
350      // Validate deviation threshold (0-100%).
351      lte r2 10000u16 into r7;
352      assert.eq r7 true;
353  
354      async configure_asset self.caller r0 r1 r2 r3 into r8;
355      output r8 as delta.oracle/configure_asset.future;
356  
357  finalize configure_asset:
358      // Input the caller.
359      input r0 as address.public;
360      // Input the asset ID.
361      input r1 as field.public;
362      // Input the heartbeat.
363      input r2 as u32.public;
364      // Input the deviation threshold.
365      input r3 as u16.public;
366      // Input the minimum sources.
367      input r4 as u8.public;
368  
369      // Verify caller is admin.
370      get admin[dx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd] into r5;
371      assert.eq r0 r5;
372  
373      // Construct and store the oracle settings.
374      cast r2 r3 true r4 into r6 as oracle_settings;
375      set r6 into oracle_config[r1];
376  
377  /**********************************************************************************************************************/
378  
379  // Initialize the oracle with an admin address. Can only be called once.
380  function initialize:
381      // Input the admin address.
382      input r0 as address.public;
383  
384      async initialize self.caller r0 into r1;
385      output r1 as delta.oracle/initialize.future;
386  
387  finalize initialize:
388      // Input the caller.
389      input r0 as address.public;
390      // Input the admin address.
391      input r1 as address.public;
392  
393      // Check if already initialized.
394      contains admin[dx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd] into r2;
395      // Fail if already initialized.
396      assert.eq r2 false;
397  
398      // Set the admin.
399      set r1 into admin[dx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3qtczd];
400  
401  /**********************************************************************************************************************/