Skip to content

Commit

Permalink
chore: re-introduce p2wh decoder (for posterity)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludo Galabru committed Oct 11, 2023
1 parent 5bf07fd commit 23b8232
Showing 1 changed file with 368 additions and 0 deletions.
368 changes: 368 additions & 0 deletions contracts/pyth-p2wh-decoder-v1.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
;; Title: pyth-p2wh-decoder-v1
;; Version: Developer Preview 1
;; Check for latest version: https://github.com/hirosystems/stacks-pyth-bridge#latest-version
;; Report an issue: https://github.com/hirosystems/stacks-pyth-bridge/issues

;;;; Traits

;;;; Constants

;; Price Feeds Ids (https://pyth.network/developers/price-feed-ids#pyth-evm-mainnet)
(define-constant STX_USD 0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17)
(define-constant BTC_USD 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
(define-constant ETH_USD 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace)

;; Generic error
(define-constant ERR_PANIC (err u0))
;; Unable to price feed magic bytes
(define-constant ERR_PF_PARSING_MAGIC_BYTES (err u2001))
;; Unable to parse major version
(define-constant ERR_PF_PARSING_VERSION_MAJ (err u2002))
;; Unable to parse minor version
(define-constant ERR_PF_PARSING_VERSION_MIN (err u2003))
;; Unable to parse trailing header size
(define-constant ERR_PF_PARSING_TRAILING_HEADER_SIZE (err u2004))
;; Unable to parse update type
(define-constant ERR_PF_PARSING_UPDATE_TYPE (err u2005))
;; Unable to parse payload ID (P2WH, PNAU, ...)
(define-constant ERR_PF_PARSING_PAYLOAD_ID (err u2006))
;; Unable to parse price attestation count
(define-constant ERR_P2WH_PARSING_ATTESTATION_COUNT (err u2007))
;; Unable to parse price attestation size
(define-constant ERR_P2WH_PARSING_ATTESTATION_SIZE (err u2008))
;; Unable to slice price attestation blobs
(define-constant ERR_P2WH_SLICING_ATTESTATION_BYTES (err u2009))
;; Unable to retrieve price feed
(define-constant ERR_PF_NOT_FOUND (err u2010))

;;;; Data vars
;;

;;;; Data maps
;;
(define-map prices
{ price-feed-id: (buff 32) }
{
price: int,
conf: uint,
expo: int,
attestation-time: uint,
ema-price: int,
ema-conf: uint,
status: uint,
publish-time: uint,
prev-publish-time: uint,
prev-price: int,
prev-conf: uint,
})

(define-data-var watched-price-feeds
(list 1024 (buff 32))
(list STX_USD BTC_USD ETH_USD))

;;;; Public functions
;;
(define-public (update-prices-feeds (vaas (list 4 (buff 2048))))
(let ((decoded-vaas (map parse-and-verify-vaa vaas))
(decoded-prices-attestations-batches (map parse-and-verify-price-attestations decoded-vaas))
(watched-prices-feeds (var-get watched-price-feeds))
(updated-prices-feeds (get updated-prices-feeds (fold process-prices-attestations-batch decoded-prices-attestations-batches { input: watched-prices-feeds, updated-prices-feeds: (list) }))))
(ok updated-prices-feeds)))

;;;; Read only functions
;;

(define-read-only (read-price-feed (price-feed-id (buff 32)))
(let ((price-feed-entry (unwrap! (map-get? prices { price-feed-id: price-feed-id }) ERR_PF_NOT_FOUND)))
(ok price-feed-entry)))

(define-read-only (parse-and-verify-price-attestations (pf-bytes (buff 2048)))
(let ((cursor-price-attestations-header (try! (parse-price-attestations-header pf-bytes)))
(cursor-attestations-count (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-price-attestations-header))
ERR_P2WH_PARSING_ATTESTATION_COUNT))
(cursor-attestation-size (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-attestations-count))
ERR_P2WH_PARSING_ATTESTATION_SIZE))
(attestations-bytes (unwrap! (slice? pf-bytes (get pos (get next cursor-attestation-size)) (+ (get pos (get next cursor-attestation-size)) (* (get value cursor-attestations-count) (get value cursor-attestation-size))))
ERR_P2WH_SLICING_ATTESTATION_BYTES))
(attestations-cues (get result (fold is-price-attestation-cue attestations-bytes { size: (get value cursor-attestation-size), cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u64)) })))
(encoded-price-attestations (get result (fold parse-price-attestation attestations-cues { attestations-bytes: attestations-bytes, result: (unwrap-panic (as-max-len? (list {
price-feed-id: (unwrap-panic (as-max-len? 0x u32)),
price: (unwrap-panic (as-max-len? 0x u8)),
conf: (unwrap-panic (as-max-len? 0x u8)),
expo: (unwrap-panic (as-max-len? 0x u4)),
ema-price: (unwrap-panic (as-max-len? 0x u8)),
ema-conf: (unwrap-panic (as-max-len? 0x u8)),
status: (unwrap-panic (as-max-len? 0x u1)),
attestation-time: (unwrap-panic (as-max-len? 0x u8)),
publish-time: (unwrap-panic (as-max-len? 0x u8)),
prev-publish-time: (unwrap-panic (as-max-len? 0x u8)),
prev-price: (unwrap-panic (as-max-len? 0x u8)),
prev-conf: (unwrap-panic (as-max-len? 0x u8)),
}) u64)) })))
(price-attestations (map decode-price-attestation (unwrap-panic (slice? encoded-price-attestations u1 (+ u1 (get value cursor-attestations-count)))))))
(ok {
attestations-count: (get value cursor-attestations-count),
attestation-size: (get value cursor-attestation-size),
price-attestations: price-attestations
})))

(define-read-only (parse-price-attestations-header (pf-bytes (buff 2048)))
(let ((cursor-magic (unwrap! (contract-call? .hk-cursor-v1 read-buff-4 { bytes: pf-bytes, pos: u0 })
ERR_PF_PARSING_MAGIC_BYTES))
;; Todo: check magic bytes
(cursor-version-maj (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-magic))
ERR_PF_PARSING_VERSION_MAJ))
;; Todo: check major version
(cursor-version-min (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-version-maj))
ERR_PF_PARSING_VERSION_MIN))
;; Todo: check minor version
(cursor-header-size (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-version-min))
ERR_PF_PARSING_TRAILING_HEADER_SIZE))
;; Todo: check minor version
(cursor-payload-id (unwrap! (contract-call? .hk-cursor-v1 read-u8 (get next cursor-header-size))
ERR_PF_PARSING_PAYLOAD_ID)))
(ok {
value: {
magic: (get value cursor-magic),
version-maj: (get value cursor-version-maj),
version-min: (get value cursor-version-min),
header-size: (get value cursor-header-size),
payload-id: (get value cursor-payload-id)
},
next: {
bytes: pf-bytes,
pos: (+ (get pos (get next cursor-payload-id))
(- (get value cursor-header-size) u1))
}
})))

;;;; Private functions
;;
(define-private (parse-and-verify-vaa (vaa-bytes (buff 2048)))
(let ((vaa (unwrap-panic (contract-call? .wormhole-core-dev-preview-1 parse-and-verify-vaa vaa-bytes)))) ;; try!
(get payload vaa)))

(define-private (parse-price-feed-header (pf-bytes (buff 2048)))
(let ((cursor-magic (unwrap! (contract-call? .hk-cursor-v1 read-buff-4 { bytes: pf-bytes, pos: u0 })
ERR_PF_PARSING_MAGIC_BYTES))
;; Todo: check magic bytes
(cursor-version-maj (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-magic))
ERR_PF_PARSING_VERSION_MAJ))
;; Todo: check major version
(cursor-version-min (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-version-maj))
ERR_PF_PARSING_VERSION_MIN))
;; Todo: check minor version
(cursor-trailing-header-size (unwrap! (contract-call? .hk-cursor-v1 read-u16 (get next cursor-version-min))
ERR_PF_PARSING_TRAILING_HEADER_SIZE))
;; We use another offset for the trailing header and in the end add the
;; offset by trailingHeaderSize to skip the future headers.
(cursor-udpate-type (unwrap! (contract-call? .hk-cursor-v1 read-u16 { bytes: pf-bytes, pos: (+ (get pos (get next cursor-trailing-header-size))
(get value cursor-trailing-header-size)) })
ERR_PF_PARSING_UPDATE_TYPE)))
(ok {
value: {
magic: (get value cursor-magic),
version-maj: (get value cursor-version-maj),
version-min: (get value cursor-version-min),
trailing-header-size: (get value cursor-trailing-header-size),
},
next: (get next cursor-udpate-type)
})))

(define-private (is-price-attestation-cue
(entry (buff 1))
(acc { cursor: uint, result: (list 64 uint), size: uint }))
(if (and
(is-eq u0 (mod (get cursor acc) (get size acc)))
(> (get cursor acc) u0) )
{
cursor: (+ u1 (get cursor acc)),
result: (unwrap-panic (as-max-len? (append (get result acc) (get cursor acc)) u64)),
size: (get size acc)
}
{
cursor: (+ u1 (get cursor acc)),
result: (get result acc),
size: (get size acc)
}))

(define-private (parse-price-attestation
(entry uint)
(acc {
attestations-bytes: (buff 2048),
result: (list 64 {
price-feed-id: (buff 32),
price: (buff 8),
conf: (buff 8),
expo: (buff 4),
ema-price: (buff 8),
ema-conf: (buff 8),
status: (buff 1),
attestation-time: (buff 8),
publish-time: (buff 8),
prev-publish-time: (buff 8),
prev-price: (buff 8),
prev-conf: (buff 8),
})}))
(let ((cursor-obsolete-1 (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-32 { bytes: (get attestations-bytes acc), pos: entry })))
(cursor-price-feed-id (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-32 (get next cursor-obsolete-1))))
(cursor-price (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-price-feed-id))))
(cursor-conf (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-price))))
(cursor-expo (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-4 (get next cursor-conf))))
(cursor-ema-price (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-expo))))
(cursor-ema-conf (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-ema-price))))
(cursor-status (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-1 (get next cursor-ema-conf))))
(cursor-obsolete-2 (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-4 (get next cursor-status))))
(cursor-obsolete-3 (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-4 (get next cursor-obsolete-2))))
(cursor-attestation-time (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-obsolete-3))))
(cursor-publish-time (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-attestation-time))))
(cursor-prev-publish-time (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-publish-time))))
(cursor-prev-price (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-prev-publish-time))))
(cursor-prev-conf (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-8 (get next cursor-prev-price))))
)
{
attestations-bytes: (get attestations-bytes acc),
result: (unwrap-panic (as-max-len? (append (get result acc) {
price-feed-id: (get value cursor-price-feed-id),
price: (get value cursor-price),
conf: (get value cursor-conf),
expo: (get value cursor-expo),
ema-price: (get value cursor-ema-price),
ema-conf: (get value cursor-ema-conf),
status: (get value cursor-status),
attestation-time: (get value cursor-attestation-time),
publish-time: (get value cursor-publish-time),
prev-publish-time: (get value cursor-prev-publish-time),
prev-price: (get value cursor-prev-price),
prev-conf: (get value cursor-prev-conf),
}) u64))
}))

(define-private (decode-price-attestation
(entry {
price-feed-id: (buff 32),
price: (buff 8),
conf: (buff 8),
expo: (buff 4),
ema-price: (buff 8),
ema-conf: (buff 8),
status: (buff 1),
attestation-time: (buff 8),
publish-time: (buff 8),
prev-publish-time: (buff 8),
prev-price: (buff 8),
prev-conf: (buff 8)
}))
{
price-feed-id: (get price-feed-id entry),
price: (buff-to-int-be (get price entry)),
conf: (buff-to-uint-be (get conf entry)),
expo: (buff-to-int-be (get expo entry)),
ema-price: (buff-to-int-be (get ema-price entry)),
ema-conf: (buff-to-uint-be (get ema-conf entry)),
status: (buff-to-uint-be (get status entry)),
attestation-time: (buff-to-uint-be (get attestation-time entry)),
publish-time: (buff-to-uint-be (get publish-time entry)),
prev-publish-time: (buff-to-uint-be (get prev-publish-time entry)),
prev-price: (buff-to-int-be (get prev-price entry)),
prev-conf: (buff-to-uint-be (get prev-conf entry)),
})

(define-private (process-prices-attestations-batch
(entry (response
{
attestation-size: uint,
attestations-count: uint,
price-attestations: (list 254 {
price-feed-id: (buff 32),
price: int,
conf: uint,
expo: int,
ema-price: int,
ema-conf: uint,
status: uint,
attestation-time: uint,
publish-time: uint,
prev-publish-time: uint,
prev-price: int,
prev-conf: uint,
})
}
uint))
(acc {
input: (list 2048 (buff 32)),
updated-prices-feeds: (list 2048 (buff 32))
}))
(let ((batch (unwrap-panic entry))
(updated-prices-feeds (get updated-prices-feeds (fold process-price-attestation (get price-attestations batch) acc))))
{
input: (get input acc),
updated-prices-feeds: updated-prices-feeds
}))

(define-private (process-price-attestation
(entry {
price-feed-id: (buff 32),
price: int,
conf: uint,
expo: int,
ema-price: int,
ema-conf: uint,
status: uint,
attestation-time: uint,
publish-time: uint,
prev-publish-time: uint,
prev-price: int,
prev-conf: uint,
})
(acc { input: (list 2048 (buff 32)), updated-prices-feeds: (list 2048 (buff 32)) }))
(match (index-of? (get input acc) (get price-feed-id entry))
index (begin
;; Update Price Feed
(map-set prices
{
price-feed-id: (get price-feed-id entry),
}
{
price: (get price entry),
conf: (get conf entry),
expo: (get expo entry),
ema-price: (get ema-price entry),
ema-conf: (get ema-conf entry),
status: (get status entry),
attestation-time: (get attestation-time entry),
publish-time: (get publish-time entry),
prev-publish-time: (get prev-publish-time entry),
prev-price: (get prev-price entry),
prev-conf: (get prev-conf entry),
})
;; TODO: check timestamps
;; Emit event
(print { type: "price-feed", action: "updated", data: entry })
{
input: (get input acc),
updated-prices-feeds: (unwrap-panic (as-max-len? (append (get updated-prices-feeds acc) (get price-feed-id entry)) u2048))
})
acc))

(define-private (parse-and-verify-price-feed (pf-bytes (buff 2048)))
(let ((cursor-price-feed-header (try! (parse-price-feed-header pf-bytes)))
;; Todo: check minor version
(cursor-trailing-header-size (unwrap! (contract-call? .hk-cursor-v1 read-u8 (get next cursor-price-feed-header))
ERR_PF_PARSING_TRAILING_HEADER_SIZE))
(cursor-udpate-type (unwrap! (contract-call? .hk-cursor-v1 read-u8 (get next cursor-trailing-header-size))
ERR_PF_PARSING_VERSION_MIN)))
;; (cursor-guardian-set (unwrap! (contract-call? .hk-cursor-v1 read-u32 (get next cursor-version))
;; ERR_PARSING_VAA_GUARDIAN_SET))
;; (cursor-signatures (fold
;; batch-read-signatures
;; (list u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0)
;; {
;; next: (get next cursor-signatures-len),
;; value: (list),
;; iter: (get value cursor-signatures-len)
;; }))
(ok {
price-feed-header: (get value cursor-price-feed-header),
udpate-type: (get value cursor-udpate-type),
})))

0 comments on commit 23b8232

Please sign in to comment.