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 /**********************************************************************************************************************/