Source Code

;; Price Oracle Contract
;; Manages price feeds for collateral assets (STX and potentially other tokens)

;; Constants
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_UNAUTHORIZED (err u400))
(define-constant ERR_PRICE_TOO_OLD (err u401))
(define-constant ERR_INVALID_PRICE (err u402))
(define-constant ERR_ASSET_NOT_SUPPORTED (err u403))
(define-constant ERR_ORACLE_NOT_AUTHORIZED (err u404))
(define-constant ERR_PRICE_DEVIATION_TOO_HIGH (err u405))

;; Price staleness threshold (144 blocks = ~24 hours with 10min blocks)
(define-constant PRICE_STALENESS_THRESHOLD u144)

;; Maximum price deviation allowed between updates (10%)
(define-constant MAX_PRICE_DEVIATION u10)
(define-constant PRICE_DEVIATION_DENOMINATOR u100)

;; Price precision
(define-constant PRICE_PRECISION u1000000) ;; 6 decimals

;; Data Variables
(define-data-var oracle-admin principal CONTRACT_OWNER)
(define-data-var price-update-count uint u0)
(define-data-var emergency-pause bool false)

;; Data Maps
(define-map asset-prices
  { asset: (string-ascii 10) }
  {
    price: uint,
    last-update-block: uint,
    last-update-time: uint,
    decimals: uint,
    source: (string-ascii 20)
  }
)

(define-map authorized-oracles
  principal
  { authorized: bool, update-count: uint }
)

(define-map price-history
  { asset: (string-ascii 10), block: uint }
  { price: uint, source: (string-ascii 20) }
)

(define-map asset-config
  { asset: (string-ascii 10) }
  {
    supported: bool,
    min-price: uint,
    max-price: uint,
    decimals: uint
  }
)

;; Multi-source price aggregation
(define-map price-sources
  { asset: (string-ascii 10), source: (string-ascii 20) }
  {
    price: uint,
    weight: uint,
    last-update: uint,
    active: bool
  }
)

;; Read-only functions

(define-read-only (get-price (asset (string-ascii 10)))
  (match (map-get? asset-prices { asset: asset })
    price-data
      (let
        (
          (blocks-since-update (- block-height (get last-update-block price-data)))
        )
        ;; Check if price is too old
        (if (> blocks-since-update PRICE_STALENESS_THRESHOLD)
          ERR_PRICE_TOO_OLD
          (ok price-data)
        )
      )
    ERR_ASSET_NOT_SUPPORTED
  )
)

(define-read-only (get-latest-price (asset (string-ascii 10)))
  (match (get-price asset)
    price-data (ok (get price price-data))
    error (err error)
  )
)

(define-read-only (is-price-fresh (asset (string-ascii 10)))
  (match (map-get? asset-prices { asset: asset })
    price-data
      (let
        (
          (blocks-since-update (- block-height (get last-update-block price-data)))
        )
        (ok (<= blocks-since-update PRICE_STALENESS_THRESHOLD))
      )
    ERR_ASSET_NOT_SUPPORTED
  )
)

(define-read-only (get-asset-config (asset (string-ascii 10)))
  (map-get? asset-config { asset: asset })
)

(define-read-only (is-oracle-authorized (oracle principal))
  (default-to 
    { authorized: false, update-count: u0 }
    (map-get? authorized-oracles oracle)
  )
)

(define-read-only (get-price-at-block (asset (string-ascii 10)) (block uint))
  (map-get? price-history { asset: asset, block: block })
)

(define-read-only (calculate-twap 
  (asset (string-ascii 10)) 
  (start-block uint) 
  (end-block uint))
  ;; Time-Weighted Average Price calculation
  ;; This is a simplified version - production would need more sophisticated TWAP
  (match (map-get? asset-prices { asset: asset })
    current-price-data
      (let
        (
          (current-price (get price current-price-data))
        )
        ;; For now, return current price
        ;; Full implementation would aggregate historical prices
        (ok current-price)
      )
    ERR_ASSET_NOT_SUPPORTED
  )
)

(define-read-only (get-price-deviation (old-price uint) (new-price uint))
  (if (is-eq old-price u0)
    u0
    (let
      (
        (difference (if (> new-price old-price)
          (- new-price old-price)
          (- old-price new-price)))
        (deviation (/ (* difference PRICE_DEVIATION_DENOMINATOR) old-price))
      )
      deviation
    )
  )
)

(define-read-only (is-emergency-paused)
  (var-get emergency-pause)
)

;; Public functions

(define-public (update-price 
  (asset (string-ascii 10)) 
  (new-price uint)
  (source (string-ascii 20)))
  (begin
    (asserts! (not (var-get emergency-pause)) ERR_UNAUTHORIZED)
    
    ;; Check authorization
    (let
      (
        (oracle-info (is-oracle-authorized tx-sender))
      )
      (asserts! (get authorized oracle-info) ERR_ORACLE_NOT_AUTHORIZED)
    )
    
    ;; Validate asset is supported
    (let
      (
        (config (unwrap! (get-asset-config asset) ERR_ASSET_NOT_SUPPORTED))
      )
      (asserts! (get supported config) ERR_ASSET_NOT_SUPPORTED)
      
      ;; Validate price is within bounds
      (asserts! (>= new-price (get min-price config)) ERR_INVALID_PRICE)
      (asserts! (<= new-price (get max-price config)) ERR_INVALID_PRICE)
      
      ;; Check price deviation if previous price exists
      (match (map-get? asset-prices { asset: asset })
        old-price-data
          (let
            (
              (old-price (get price old-price-data))
              (deviation (get-price-deviation old-price new-price))
            )
            (asserts! (<= deviation MAX_PRICE_DEVIATION) ERR_PRICE_DEVIATION_TOO_HIGH)
            true
          )
        true ;; No previous price, allow any valid price
      )
      
      ;; Update price
      (map-set asset-prices { asset: asset }
        {
          price: new-price,
          last-update-block: block-height,
          last-update-time: burn-block-height,
          decimals: (get decimals config),
          source: source
        }
      )
      
      ;; Store in history
      (map-set price-history { asset: asset, block: block-height }
        { price: new-price, source: source }
      )
      
      ;; Update oracle stats
      (let
        (
          (oracle-info (is-oracle-authorized tx-sender))
        )
        (map-set authorized-oracles tx-sender
          (merge oracle-info { update-count: (+ (get update-count oracle-info) u1) })
        )
      )
      
      ;; Increment global counter
      (var-set price-update-count (+ (var-get price-update-count) u1))
      
      (ok true)
    )
  )
)

(define-public (update-multi-source-price
  (asset (string-ascii 10))
  (source (string-ascii 20))
  (price uint)
  (weight uint))
  (begin
    (asserts! (not (var-get emergency-pause)) ERR_UNAUTHORIZED)
    
    ;; Check authorization
    (let
      (
        (oracle-info (is-oracle-authorized tx-sender))
      )
      (asserts! (get authorized oracle-info) ERR_ORACLE_NOT_AUTHORIZED)
    )
    
    ;; Update source price
    (map-set price-sources { asset: asset, source: source }
      {
        price: price,
        weight: weight,
        last-update: block-height,
        active: true
      }
    )
    
    (ok true)
  )
)

;; Admin functions

(define-public (add-supported-asset
  (asset (string-ascii 10))
  (min-price uint)
  (max-price uint)
  (decimals uint))
  (begin
    (asserts! (is-eq tx-sender (var-get oracle-admin)) ERR_UNAUTHORIZED)
    
    (map-set asset-config { asset: asset }
      {
        supported: true,
        min-price: min-price,
        max-price: max-price,
        decimals: decimals
      }
    )
    
    (ok true)
  )
)

(define-public (authorize-oracle (oracle principal))
  (begin
    (asserts! (is-eq tx-sender (var-get oracle-admin)) ERR_UNAUTHORIZED)
    
    (map-set authorized-oracles oracle
      { authorized: true, update-count: u0 }
    )
    
    (ok true)
  )
)

(define-public (revoke-oracle (oracle principal))
  (begin
    (asserts! (is-eq tx-sender (var-get oracle-admin)) ERR_UNAUTHORIZED)
    
    (map-set authorized-oracles oracle
      { authorized: false, update-count: u0 }
    )
    
    (ok true)
  )
)

(define-public (set-emergency-pause (paused bool))
  (begin
    (asserts! (is-eq tx-sender (var-get oracle-admin)) ERR_UNAUTHORIZED)
    (var-set emergency-pause paused)
    (ok paused)
  )
)

(define-public (transfer-admin (new-admin principal))
  (begin
    (asserts! (is-eq tx-sender (var-get oracle-admin)) ERR_UNAUTHORIZED)
    (var-set oracle-admin new-admin)
    (ok true)
  )
)

;; Helper functions for price conversion

(define-read-only (convert-price-decimals 
  (price uint) 
  (from-decimals uint) 
  (to-decimals uint))
  (if (is-eq from-decimals to-decimals)
    price
    (if (> from-decimals to-decimals)
      ;; Reduce decimals - use direct division for common cases
      (let ((diff (- from-decimals to-decimals)))
        (if (is-eq diff u1)
          (/ price u10)
          (if (is-eq diff u2)
            (/ price u100)
            (if (is-eq diff u3)
              (/ price u1000)
              (if (is-eq diff u4)
                (/ price u10000)
                (if (is-eq diff u5)
                  (/ price u100000)
                  (if (is-eq diff u6)
                    (/ price u1000000)
                    (/ price u1000000) ;; Default to 6 decimals
                  )
                )
              )
            )
          )
        )
      )
      ;; Increase decimals
      (let ((diff (- to-decimals from-decimals)))
        (if (is-eq diff u1)
          (* price u10)
          (if (is-eq diff u2)
            (* price u100)
            (if (is-eq diff u3)
              (* price u1000)
              (if (is-eq diff u4)
                (* price u10000)
                (if (is-eq diff u5)
                  (* price u100000)
                  (if (is-eq diff u6)
                    (* price u1000000)
                    (* price u1000000) ;; Default to 6 decimals
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)

;; Initialize with default configuration
(begin
  ;; Authorize contract owner as oracle
  (map-set authorized-oracles CONTRACT_OWNER
    { authorized: true, update-count: u0 }
  )
  
  ;; Configure STX as supported asset
  (map-set asset-config { asset: "STX" }
    {
      supported: true,
      min-price: u100000,      ;; Min $0.10
      max-price: u100000000,   ;; Max $100
      decimals: u6
    }
  )
  
  ;; Set initial STX price ($1.00)
  (map-set asset-prices { asset: "STX" }
    {
      price: u1000000,
      last-update-block: u0,
      last-update-time: u0,
      decimals: u6,
      source: "INIT"
    }
  )
)

Functions (17)

FunctionAccessArgs
get-priceread-onlyasset: (string-ascii 10
get-latest-priceread-onlyasset: (string-ascii 10
is-price-freshread-onlyasset: (string-ascii 10
get-asset-configread-onlyasset: (string-ascii 10
is-oracle-authorizedread-onlyoracle: principal
get-price-at-blockread-onlyasset: (string-ascii 10
calculate-twapread-onlyasset: (string-ascii 10
get-price-deviationread-onlyold-price: uint, new-price: uint
is-emergency-pausedread-only
update-pricepublicasset: (string-ascii 10
update-multi-source-pricepublicasset: (string-ascii 10
add-supported-assetpublicasset: (string-ascii 10
authorize-oraclepublicoracle: principal
revoke-oraclepublicoracle: principal
set-emergency-pausepublicpaused: bool
transfer-adminpublicnew-admin: principal
convert-price-decimalsread-onlyprice: uint, from-decimals: uint, to-decimals: uint