diff --git a/README.md b/README.md index 22c8e67..ef39677 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -This is a readme file +# High Level diff --git a/high-level_contracts/.gitattributes b/high-level_contracts/.gitattributes new file mode 100644 index 0000000..da6a065 --- /dev/null +++ b/high-level_contracts/.gitattributes @@ -0,0 +1,3 @@ +tests/** linguist-vendored +vitest.config.js linguist-vendored +* text=lf diff --git a/high-level_contracts/.gitignore b/high-level_contracts/.gitignore new file mode 100644 index 0000000..76c2842 --- /dev/null +++ b/high-level_contracts/.gitignore @@ -0,0 +1,13 @@ + +**/settings/Mainnet.toml +**/settings/Testnet.toml +.cache/** +history.txt + +logs +*.log +npm-debug.log* +coverage +*.info +costs-reports.json +node_modules diff --git a/high-level_contracts/.vscode/settings.json b/high-level_contracts/.vscode/settings.json new file mode 100644 index 0000000..3062519 --- /dev/null +++ b/high-level_contracts/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "files.eol": "\n" +} diff --git a/high-level_contracts/.vscode/tasks.json b/high-level_contracts/.vscode/tasks.json new file mode 100644 index 0000000..4dec0ff --- /dev/null +++ b/high-level_contracts/.vscode/tasks.json @@ -0,0 +1,19 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "check contracts", + "group": "test", + "type": "shell", + "command": "clarinet check" + }, + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [], + "label": "npm test" + } + ] +} diff --git a/high-level_contracts/Clarinet.toml b/high-level_contracts/Clarinet.toml new file mode 100644 index 0000000..b6c3106 --- /dev/null +++ b/high-level_contracts/Clarinet.toml @@ -0,0 +1,34 @@ +[project] +name = 'high-level_contracts' +description = '' +authors = [] +telemetry = true +cache_dir = '.\.cache' +requirements = [] +[contracts.prediction-market-bets] +path = 'contracts/prediction-market-bets.clar' +clarity_version = 2 +epoch = 2.5 + +[contracts.prediction-market-core] +path = 'contracts/prediction-market-core.clar' +clarity_version = 2 +epoch = 2.5 + +[contracts.prediction-market-payout] +path = 'contracts/prediction-market-payout.clar' +clarity_version = 2 +epoch = 2.5 + +[contracts.prediction-market_markets] +path = 'contracts/prediction-market_markets.clar' +clarity_version = 2 +epoch = 2.5 +[repl.analysis] +passes = ['check_checker'] + +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/high-level_contracts/contracts/prediction-market-bets.clar b/high-level_contracts/contracts/prediction-market-bets.clar new file mode 100644 index 0000000..401119a --- /dev/null +++ b/high-level_contracts/contracts/prediction-market-bets.clar @@ -0,0 +1,168 @@ +;; Prediction Market Betting Contract + +;; Constants +(define-constant CONTRACT_OWNER tx-sender) +(define-constant ERR_UNAUTHORIZED (err u100)) +(define-constant ERR_INVALID_MARKET (err u101)) +(define-constant ERR_INVALID_BET_AMOUNT (err u102)) +(define-constant ERR_MARKET_CLOSED (err u103)) +(define-constant ERR_INSUFFICIENT_BALANCE (err u104)) +(define-constant ERR_MARKET_NOT_RESOLVED (err u105)) + +;; Data Variables +(define-data-var total-bets uint u0) + +;; Maps +(define-map markets + { market-id: uint } + { + creator: principal, + description: (string-ascii 256), + options: (list 10 (string-ascii 64)), + total-pool: uint, + is-open: bool, + winning-option: (optional uint) + } +) + +(define-map bets + { market-id: uint, better: principal } + { + amount: uint, + option: uint + } +) + +(define-map user-balances principal uint) + +;; Private Functions +(define-private (transfer-stx (amount uint) (sender principal) (recipient principal)) + (stx-transfer? amount sender recipient) +) + +;; Public Functions +(define-public (create-market (description (string-ascii 256)) (options (list 10 (string-ascii 64)))) + (let ((market-id (var-get total-bets))) + (begin + (asserts! (> (len options) u0) ERR_INVALID_MARKET) + (map-set markets + { market-id: market-id } + { + creator: tx-sender, + description: description, + options: options, + total-pool: u0, + is-open: true, + winning-option: none + } + ) + (var-set total-bets (+ market-id u1)) + (ok market-id) + ) + ) +) + +(define-public (place-bet (market-id uint) (amount uint) (option uint)) + (let ( + (market (unwrap! (map-get? markets { market-id: market-id }) ERR_INVALID_MARKET)) + (user-balance (default-to u0 (map-get? user-balances tx-sender))) + ) + (begin + (asserts! (get is-open market) ERR_MARKET_CLOSED) + (asserts! (>= user-balance amount) ERR_INSUFFICIENT_BALANCE) + (asserts! (> amount u0) ERR_INVALID_BET_AMOUNT) + (asserts! (< option (len (get options market))) ERR_INVALID_BET_AMOUNT) + + (map-set bets + { market-id: market-id, better: tx-sender } + { amount: amount, option: option } + ) + (map-set markets + { market-id: market-id } + (merge market { total-pool: (+ (get total-pool market) amount) }) + ) + (map-set user-balances + tx-sender + (- user-balance amount) + ) + (ok true) + ) + ) +) + +(define-public (resolve-market (market-id uint) (winning-option uint)) + (let ((market (unwrap! (map-get? markets { market-id: market-id }) ERR_INVALID_MARKET))) + (begin + (asserts! (is-eq (get creator market) tx-sender) ERR_UNAUTHORIZED) + (asserts! (get is-open market) ERR_MARKET_CLOSED) + (asserts! (< winning-option (len (get options market))) ERR_INVALID_BET_AMOUNT) + + (map-set markets + { market-id: market-id } + (merge market { + is-open: false, + winning-option: (some winning-option) + }) + ) + (ok true) + ) + ) +) + +(define-public (claim-winnings (market-id uint)) + (let ( + (market (unwrap! (map-get? markets { market-id: market-id }) ERR_INVALID_MARKET)) + (bet (unwrap! (map-get? bets { market-id: market-id, better: tx-sender }) ERR_INVALID_BET_AMOUNT)) + (winning-option (unwrap! (get winning-option market) ERR_MARKET_NOT_RESOLVED)) + ) + (begin + (asserts! (not (get is-open market)) ERR_MARKET_NOT_RESOLVED) + (asserts! (is-eq (get option bet) winning-option) ERR_INVALID_BET_AMOUNT) + + (let ( + (payout (/ (* (get amount bet) (get total-pool market)) (get amount bet))) + ) + (map-delete bets { market-id: market-id, better: tx-sender }) + (map-set user-balances + tx-sender + (+ (default-to u0 (map-get? user-balances tx-sender)) payout) + ) + (ok payout) + ) + ) + ) +) + +(define-public (deposit-funds (amount uint)) + (let ((current-balance (default-to u0 (map-get? user-balances tx-sender)))) + (begin + (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) + (map-set user-balances tx-sender (+ current-balance amount)) + (ok true) + ) + ) +) + +(define-public (withdraw-funds (amount uint)) + (let ((current-balance (default-to u0 (map-get? user-balances tx-sender)))) + (begin + (asserts! (>= current-balance amount) ERR_INSUFFICIENT_BALANCE) + (try! (as-contract (stx-transfer? amount (as-contract tx-sender) tx-sender))) + (map-set user-balances tx-sender (- current-balance amount)) + (ok true) + ) + ) +) + +;; Read-only Functions +(define-read-only (get-market (market-id uint)) + (map-get? markets { market-id: market-id }) +) + +(define-read-only (get-bet (market-id uint) (better principal)) + (map-get? bets { market-id: market-id, better: better }) +) + +(define-read-only (get-balance (user principal)) + (default-to u0 (map-get? user-balances user)) +) diff --git a/high-level_contracts/contracts/prediction-market-core.clar b/high-level_contracts/contracts/prediction-market-core.clar new file mode 100644 index 0000000..c971f59 --- /dev/null +++ b/high-level_contracts/contracts/prediction-market-core.clar @@ -0,0 +1,245 @@ +;; prediction-market-core +;; Core functionality for prediction markets + +;; Constants +(define-constant contract-owner tx-sender) +(define-constant err-unauthorized (err u100)) +(define-constant err-already-initialized (err u101)) +(define-constant err-not-initialized (err u102)) +(define-constant err-invalid-params (err u103)) +(define-constant err-market-closed (err u104)) +(define-constant err-already-resolved (err u105)) +(define-constant err-insufficient-balance (err u106)) +(define-constant err-no-payout (err u107)) +(define-constant err-market-not-found (err u108)) +(define-constant err-too-early (err u109)) + +;; Configuration constants +(define-constant min-bet u1000000) ;; 1 STX minimum bet +(define-constant market-creation-fee u1000000) ;; 1 STX to create market +(define-constant fee-percentage u5) ;; 0.5% +(define-constant fee-denominator u1000) + +;; Data variables +(define-data-var contract-initialized bool false) +(define-data-var total-markets uint u0) +(define-data-var accumulated-fees uint u0) + +;; Data maps +(define-map markets uint { + creator: principal, + description: (string-utf8 256), + resolution-time: uint, + resolved: bool, + outcome: (optional bool), + total-yes-amount: uint, + total-no-amount: uint, + creation-time: uint, + active: bool +}) + +(define-map market-bets { market-id: uint, better: principal } { + yes-amount: uint, + no-amount: uint, + claimed: bool +}) + +;; Private functions +(define-private (is-contract-owner) + (is-eq tx-sender contract-owner)) + +(define-private (calculate-fee (amount uint)) + (/ (* amount fee-percentage) fee-denominator)) + +(define-private (validate-market-exists (market-id uint)) + (match (map-get? markets market-id) + market (ok market) + err-market-not-found)) + +(define-private (validate-market-active (market-id uint)) + (match (map-get? markets market-id) + market (if (get active market) + (ok market) + err-market-closed) + err-market-not-found)) + +;; Read-only functions +(define-read-only (get-market (market-id uint)) + (map-get? markets market-id)) + +(define-read-only (get-market-bet (market-id uint) (better principal)) + (map-get? market-bets { market-id: market-id, better: better })) + +(define-read-only (get-accumulated-fees) + (var-get accumulated-fees)) + +(define-read-only (get-total-markets) + (var-get total-markets)) + +(define-read-only (is-market-active (market-id uint)) + (match (map-get? markets market-id) + market (get active market) + false)) + +;; Public functions +(define-public (initialize-contract) + (begin + (asserts! (is-contract-owner) err-unauthorized) + (asserts! (not (var-get contract-initialized)) err-already-initialized) + (var-set contract-initialized true) + (ok true))) + +(define-public (create-market (description (string-utf8 256)) (resolution-time uint)) + (let ( + (market-id (+ (var-get total-markets) u1)) + (current-time (unwrap! (get-block-info? time u0) err-invalid-params)) + ) + ;; Validations + (asserts! (var-get contract-initialized) err-not-initialized) + (asserts! (> (len description) u0) err-invalid-params) + (asserts! (> resolution-time current-time) err-invalid-params) + + ;; Charge market creation fee + (try! (stx-transfer? market-creation-fee tx-sender (as-contract tx-sender))) + + ;; Create market + (map-set markets market-id { + creator: tx-sender, + description: description, + resolution-time: resolution-time, + resolved: false, + outcome: none, + total-yes-amount: u0, + total-no-amount: u0, + creation-time: current-time, + active: true + }) + + (var-set total-markets market-id) + (ok market-id))) + +(define-public (place-bet (market-id uint) (bet-yes bool) (amount uint)) + (let ( + (market (unwrap! (validate-market-active market-id) err-market-closed)) + (current-time (unwrap! (get-block-info? time u0) err-invalid-params)) + ) + ;; Validations + (asserts! (>= amount min-bet) err-invalid-params) + (asserts! (not (get resolved market)) err-market-closed) + (asserts! (< current-time (get resolution-time market)) err-market-closed) + + ;; Process bet + (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) + + ;; Update market bets + (match (map-get? market-bets {market-id: market-id, better: tx-sender}) + prev-bet + (map-set market-bets + {market-id: market-id, better: tx-sender} + { + yes-amount: (if bet-yes (+ (get yes-amount prev-bet) amount) (get yes-amount prev-bet)), + no-amount: (if bet-yes (get no-amount prev-bet) (+ (get no-amount prev-bet) amount)), + claimed: false + }) + ;; No previous bet + (map-set market-bets + {market-id: market-id, better: tx-sender} + { + yes-amount: (if bet-yes amount u0), + no-amount: (if bet-yes u0 amount), + claimed: false + })) + + ;; Update market totals + (map-set markets market-id + (merge market { + total-yes-amount: (if bet-yes (+ (get total-yes-amount market) amount) (get total-yes-amount market)), + total-no-amount: (if bet-yes (get total-no-amount market) (+ (get total-no-amount market) amount)) + })) + + (ok true))) + +(define-public (resolve-market (market-id uint) (outcome bool)) + (let ((market (unwrap! (validate-market-exists market-id) err-market-not-found))) + ;; Validations + (asserts! (is-eq (get creator market) tx-sender) err-unauthorized) + (asserts! (not (get resolved market)) err-already-resolved) + (asserts! (>= (unwrap! (get-block-info? time u0) err-invalid-params) + (get resolution-time market)) + err-too-early) + + ;; Update market + (map-set markets market-id + (merge market { + resolved: true, + outcome: (some outcome) + })) + + (ok true))) + +(define-public (claim-payout (market-id uint)) + (let ( + (market (unwrap! (validate-market-exists market-id) err-market-not-found)) + (bet (unwrap! (map-get? market-bets {market-id: market-id, better: tx-sender}) err-no-payout)) + ) + ;; Validations + (asserts! (get resolved market) err-market-closed) + (asserts! (not (get claimed bet)) err-no-payout) + + (match (get outcome market) + outcome (let ( + (won-amount (if outcome + (get yes-amount bet) + (get no-amount bet))) + (total-pot (+ (get total-yes-amount market) (get total-no-amount market))) + (payout (if (> won-amount u0) + (/ (* won-amount total-pot) + (if outcome + (get total-yes-amount market) + (get total-no-amount market))) + u0)) + (fee (calculate-fee payout)) + ) + ;; Update bet to claimed + (map-set market-bets + {market-id: market-id, better: tx-sender} + (merge bet {claimed: true})) + + ;; Transfer payout minus fee + (if (> payout u0) + (begin + (var-set accumulated-fees (+ (var-get accumulated-fees) fee)) + (as-contract + (try! (stx-transfer? (- payout fee) tx-sender tx-sender))) + (ok payout)) + (ok u0))) + err-market-not-found)) +) +(define-public (withdraw-fees) + (let ((fees (var-get accumulated-fees))) + (asserts! (is-contract-owner) err-unauthorized) + (asserts! (> fees u0) err-no-payout) + + (var-set accumulated-fees u0) + (as-contract + (try! (stx-transfer? fees contract-owner tx-sender))) + (ok fees))) + +;; Emergency functions +(define-public (pause-market (market-id uint)) + (begin + (asserts! (is-contract-owner) err-unauthorized) + (try! (validate-market-exists market-id)) + (map-set markets market-id + (merge (unwrap-panic (get-market market-id)) + {active: false})) + (ok true))) + +(define-public (resume-market (market-id uint)) + (begin + (asserts! (is-contract-owner) err-unauthorized) + (try! (validate-market-exists market-id)) + (map-set markets market-id + (merge (unwrap-panic (get-market market-id)) + {active: true})) + (ok true))) \ No newline at end of file diff --git a/high-level_contracts/contracts/prediction-market-payout.clar b/high-level_contracts/contracts/prediction-market-payout.clar new file mode 100644 index 0000000..750a625 --- /dev/null +++ b/high-level_contracts/contracts/prediction-market-payout.clar @@ -0,0 +1,145 @@ + +;; title: prediction-market-payout +;; version: +;; summary: +;; description: + +;; Prediction Market Payout Management Contract + +;; Constants +(define-constant CONTRACT_OWNER tx-sender) +(define-constant ERR_UNAUTHORIZED (err u100)) +(define-constant ERR_INVALID_MARKET (err u101)) +(define-constant ERR_MARKET_NOT_RESOLVED (err u102)) +(define-constant ERR_ALREADY_CLAIMED (err u103)) +(define-constant ERR_NO_PAYOUT (err u104)) +(define-constant ERR_INSUFFICIENT_BALANCE (err u105)) +(define-constant ERR_INVALID_AMOUNT (err u106)) + +;; Fee percentage (0.5%) +(define-constant FEE_PERCENTAGE u5) +(define-constant FEE_DENOMINATOR u1000) + +;; Data Variables +(define-data-var total-fees uint u0) + +;; Maps +(define-map market-payouts + { market-id: uint } + { + total-pool: uint, + winning-option: uint, + is-resolved: bool + } +) + +(define-map user-bets + { market-id: uint, user: principal } + { + amount: uint, + option: uint, + claimed: bool + } +) + +(define-map user-balances principal uint) + +;; Private Functions +(define-private (calculate-fee (amount uint)) + (/ (* amount FEE_PERCENTAGE) FEE_DENOMINATOR) +) + +(define-private (transfer-stx (amount uint) (sender principal) (recipient principal)) + (stx-transfer? amount sender recipient) +) + +;; Public Functions +(define-public (resolve-market (market-id uint) (winning-option uint) (total-pool uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED) + (map-set market-payouts + { market-id: market-id } + { + total-pool: total-pool, + winning-option: winning-option, + is-resolved: true + } + ) + (ok true) + ) +) + +(define-public (claim-payout (market-id uint)) + (let ( + (market (unwrap! (map-get? market-payouts { market-id: market-id }) ERR_INVALID_MARKET)) + (bet (unwrap! (map-get? user-bets { market-id: market-id, user: tx-sender }) ERR_NO_PAYOUT)) + ) + (begin + (asserts! (get is-resolved market) ERR_MARKET_NOT_RESOLVED) + (asserts! (not (get claimed bet)) ERR_ALREADY_CLAIMED) + (asserts! (is-eq (get option bet) (get winning-option market)) ERR_NO_PAYOUT) + + (let ( + (payout-amount (/ (* (get amount bet) (get total-pool market)) (get amount bet))) + (fee-amount (calculate-fee payout-amount)) + (net-payout (- payout-amount fee-amount)) + ) + (map-set user-bets + { market-id: market-id, user: tx-sender } + (merge bet { claimed: true }) + ) + (map-set user-balances + tx-sender + (+ (default-to u0 (map-get? user-balances tx-sender)) net-payout) + ) + (var-set total-fees (+ (var-get total-fees) fee-amount)) + (ok net-payout) + ) + ) + ) +) + +(define-public (withdraw-balance (amount uint)) + (let ( + (user-balance (default-to u0 (map-get? user-balances tx-sender))) + ) + (begin + (asserts! (>= user-balance amount) ERR_INSUFFICIENT_BALANCE) + (asserts! (> amount u0) ERR_INVALID_AMOUNT) + (try! (as-contract (stx-transfer? amount (as-contract tx-sender) tx-sender))) + (map-set user-balances + tx-sender + (- user-balance amount) + ) + (ok amount) + ) + ) +) + +(define-public (withdraw-fees) + (begin + (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED) + (let ((fee-amount (var-get total-fees))) + (var-set total-fees u0) + (try! (as-contract (stx-transfer? fee-amount (as-contract tx-sender) tx-sender))) + (ok fee-amount) + ) + ) +) + +;; Read-only Functions +(define-read-only (get-market-payout (market-id uint)) + (map-get? market-payouts { market-id: market-id }) +) + +(define-read-only (get-user-bet (market-id uint) (user principal)) + (map-get? user-bets { market-id: market-id, user: user }) +) + +(define-read-only (get-user-balance (user principal)) + (default-to u0 (map-get? user-balances user)) +) + +(define-read-only (get-total-fees) + (var-get total-fees) +) \ No newline at end of file diff --git a/high-level_contracts/contracts/prediction-market_markets.clar b/high-level_contracts/contracts/prediction-market_markets.clar new file mode 100644 index 0000000..58071e8 --- /dev/null +++ b/high-level_contracts/contracts/prediction-market_markets.clar @@ -0,0 +1,243 @@ +;; prediction-market-markets.clar +;; Market-specific functionality for prediction markets + +;; Define the trait we're importing from core contract +(define-trait prediction-market-trait + ( + ;; Format: (function-name (param-types) return-type) + (create-market ((string-utf8 500) uint) (response uint uint)) + ) +) + + +;; Constants for market management +(define-constant contract-owner tx-sender) +(define-constant err-unauthorized (err u200)) +(define-constant err-invalid-market (err u201)) +(define-constant err-invalid-resolution-time (err u202)) +(define-constant err-market-already-resolved (err u203)) +(define-constant err-market-not-resolved (err u204)) +(define-constant err-invalid-status (err u205)) +(define-constant err-market-closed (err u206)) +(define-constant err-invalid-description (err u207)) + +;; Market status enumeration +(define-data-var market-status-map (string-ascii 20) "active") + +;; Market Categories +(define-map market-categories uint (string-ascii 20)) +(define-map category-markets (string-ascii 20) (list 500 uint)) + +;; Market metadata +(define-map market-metadata uint { + title: (string-utf8 100), + description: (string-utf8 500), + category: (string-ascii 20), + tags: (list 10 (string-ascii 20)), + additional-info-url: (optional (string-utf8 256)), + resolution-rules: (string-utf8 500) +}) + +;; Market timestamps +(define-map market-timestamps uint { + created-at: uint, + updated-at: uint, + resolved-at: (optional uint), + last-bet-at: (optional uint) +}) + +;; Market statistics +(define-map market-stats uint { + total-participants: uint, + total-volume: uint, + yes-volume: uint, + no-volume: uint, + average-bet-size: uint +}) + +;; Private functions +(define-private (validate-market-metadata + (title (string-utf8 100)) + (description (string-utf8 500)) + (category (string-ascii 20)) + (tags (list 10 (string-ascii 20))) + (resolution-rules (string-utf8 500))) + (begin + (asserts! (> (len title) u0) err-invalid-description) + (asserts! (> (len description) u0) err-invalid-description) + (asserts! (> (len category) u0) err-invalid-description) + (asserts! (> (len resolution-rules) u0) err-invalid-description) + (ok true))) + +(define-private (validate-resolution-time (resolution-time uint)) + (let ((current-time (unwrap! (get-block-info? time u0) err-invalid-resolution-time))) + (asserts! (> resolution-time current-time) err-invalid-resolution-time) + (ok true))) + +(define-private (initialize-market-stats (market-id uint)) + (map-set market-stats market-id { + total-participants: u0, + total-volume: u0, + yes-volume: u0, + no-volume: u0, + average-bet-size: u0 + })) + +(define-private (add-to-category (category (string-ascii 20)) (market-id uint)) + (let ((current-markets (default-to (list) (map-get? category-markets category)))) + (ok (map-set category-markets + category + (unwrap! (as-max-len? (append current-markets market-id) u500) + err-invalid-market)))) +) + +;; Public functions +(define-public (create-market-extended ( + core-contract ) + (title (string-utf8 100)) + (description (string-utf8 500)) + (resolution-time uint) + (category (string-ascii 20)) + (tags (list 10 (string-ascii 20))) + (additional-info-url (optional (string-utf8 256))) + (resolution-rules (string-utf8 500))) + (let ((current-time (unwrap! (get-block-info? time u0) err-invalid-market))) + ;; Validate inputs + (try! (validate-market-metadata title description category tags resolution-rules)) + (try! (validate-resolution-time resolution-time)) + + ;; Create market in core contract + (let ((market-id (unwrap! (contract-call? core-contract create-market description resolution-time) + err-invalid-market))) + ;; Set market metadata + (map-set market-metadata market-id { + title: title, + description: description, + category: category, + tags: tags, + additional-info-url: additional-info-url, + resolution-rules: resolution-rules + }) + + ;; Set market timestamps + (map-set market-timestamps market-id { + created-at: current-time, + updated-at: current-time, + resolved-at: none, + last-bet-at: none + }) + + ;; Initialize market statistics + (initialize-market-stats market-id) + + ;; Set market category and update category index + (map-set market-categories market-id category) + (try! (add-to-category category market-id)) + + (ok market-id)))) + +;; Market update functions +(define-public (update-market-metadata ( + market-id uint) + (title (string-utf8 100)) + (description (string-utf8 500)) + (category (string-ascii 20)) + (tags (list 10 (string-ascii 20))) + (additional-info-url (optional (string-utf8 256))) + (resolution-rules (string-utf8 500))) + (let ((current-time (unwrap! (get-block-info? time u0) err-invalid-market)) + (old-category (unwrap! (map-get? market-categories market-id) err-invalid-market))) + ;; Validate inputs + (try! (validate-market-metadata title description category tags resolution-rules)) + + ;; Update metadata + (map-set market-metadata market-id { + title: title, + description: description, + category: category, + tags: tags, + additional-info-url: additional-info-url, + resolution-rules: resolution-rules + }) + + ;; Update timestamp + (map-set market-timestamps market-id + (merge (unwrap! (map-get? market-timestamps market-id) err-invalid-market) + {updated-at: current-time})) + + ;; Update category if changed + (if (not (is-eq category old-category)) + (begin + (map-set market-categories market-id category) + (try! (add-to-category category market-id))) + true) + + (ok true))) + +;; Market statistics functions +(define-public (update-market-stats ( + market-id uint) + (bet-amount uint) + (is-yes-bet bool)) + (let ((stats (unwrap! (map-get? market-stats market-id) err-invalid-market)) + (current-time (unwrap! (get-block-info? time u0) err-invalid-market))) + + ;; Update statistics + (map-set market-stats market-id { + total-participants: (+ (get total-participants stats) u1), + total-volume: (+ (get total-volume stats) bet-amount), + yes-volume: (if is-yes-bet (+ (get yes-volume stats) bet-amount) (get yes-volume stats)), + no-volume: (if is-yes-bet (get no-volume stats) (+ (get no-volume stats) bet-amount)), + average-bet-size: (/ (+ (get total-volume stats) bet-amount) + (+ (get total-participants stats) u1)) + }) + + ;; Update last bet timestamp + (map-set market-timestamps market-id + (merge (unwrap! (map-get? market-timestamps market-id) err-invalid-market) + {last-bet-at: (some current-time)})) + + (ok true))) + +;; Read-only functions +(define-read-only (get-market-details (market-id uint)) + (ok { + metadata: (map-get? market-metadata market-id), + timestamps: (map-get? market-timestamps market-id), + stats: (map-get? market-stats market-id), + category: (map-get? market-categories market-id) + })) + +(define-read-only (get-markets-by-category (category (string-ascii 20))) + (match (map-get? category-markets category) + market-list (ok market-list) + (ok (list)))) + +(define-read-only (get-market-status) + (var-get market-status-map)) + +(define-read-only (get-market-metadata (market-id uint)) + (map-get? market-metadata market-id)) + +(define-read-only (get-market-timestamps (market-id uint)) + (map-get? market-timestamps market-id)) + +(define-read-only (get-market-stats (market-id uint)) + (map-get? market-stats market-id)) + +;; Market resolution timestamp functions +(define-public (record-market-resolution (market-id uint)) + (let ((current-time (unwrap! (get-block-info? time u0) err-invalid-market))) + (map-set market-timestamps market-id + (merge (unwrap! (map-get? market-timestamps market-id) err-invalid-market) + {resolved-at: (some current-time)})) + (ok true))) + +;; Category management functions +(define-read-only (get-market-category (market-id uint)) + (map-get? market-categories market-id)) + +(define-read-only (get-category-market-count (category (string-ascii 20))) + (match (map-get? category-markets category) + market-list (ok (len market-list)) + (ok u0))) \ No newline at end of file diff --git a/high-level_contracts/package.json b/high-level_contracts/package.json new file mode 100644 index 0000000..71264b1 --- /dev/null +++ b/high-level_contracts/package.json @@ -0,0 +1,24 @@ + +{ + "name": "high-level_contracts-tests", + "version": "1.0.0", + "description": "Run unit tests on this project.", + "type": "module", + "private": true, + "scripts": { + "test": "vitest run", + "test:report": "vitest run -- --coverage --costs", + "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\"" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@hirosystems/clarinet-sdk": "^2.3.2", + "@stacks/transactions": "^6.12.0", + "chokidar-cli": "^3.0.0", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1", + "vitest-environment-clarinet": "^2.0.0" + } +} diff --git a/high-level_contracts/settings/Devnet.toml b/high-level_contracts/settings/Devnet.toml new file mode 100644 index 0000000..b303e95 --- /dev/null +++ b/high-level_contracts/settings/Devnet.toml @@ -0,0 +1,156 @@ +[network] +name = "devnet" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +balance = 100_000_000_000_000 +# secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 +# stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +# btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + +[accounts.wallet_1] +mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" +balance = 100_000_000_000_000 +# secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801 +# stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 +# btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC + +[accounts.wallet_2] +mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital" +balance = 100_000_000_000_000 +# secret_key: 530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101 +# stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG +# btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + +[accounts.wallet_3] +mnemonic = "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high" +balance = 100_000_000_000_000 +# secret_key: d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901 +# stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC +# btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + +[accounts.wallet_4] +mnemonic = "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin" +balance = 100_000_000_000_000 +# secret_key: f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 +# stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND +# btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8 + +[accounts.wallet_5] +mnemonic = "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase" +balance = 100_000_000_000_000 +# secret_key: 3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 +# stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB +# btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx + +[accounts.wallet_6] +mnemonic = "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy" +balance = 100_000_000_000_000 +# secret_key: 7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 +# stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 +# btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt + +[accounts.wallet_7] +mnemonic = "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow" +balance = 100_000_000_000_000 +# secret_key: b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401 +# stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ +# btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7 + +[accounts.wallet_8] +mnemonic = "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune" +balance = 100_000_000_000_000 +# secret_key: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 +# stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +# btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw + +[accounts.faucet] +mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +balance = 100_000_000_000_000 +# secret_key: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801 +# stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 +# btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + +[devnet] +disable_stacks_explorer = false +disable_stacks_api = false +# disable_postgres = false +# disable_subnet_api = false +# disable_bitcoin_explorer = true +# working_dir = "tmp/devnet" +# stacks_node_events_observers = ["host.docker.internal:8002"] +# miner_mnemonic = "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce" +# miner_derivation_path = "m/44'/5757'/0'/0/0" +# faucet_mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +# faucet_derivation_path = "m/44'/5757'/0'/0/0" +# orchestrator_port = 20445 +# bitcoin_node_p2p_port = 18444 +# bitcoin_node_rpc_port = 18443 +# bitcoin_node_username = "devnet" +# bitcoin_node_password = "devnet" +# bitcoin_controller_block_time = 30_000 +# stacks_node_rpc_port = 20443 +# stacks_node_p2p_port = 20444 +# stacks_api_port = 3999 +# stacks_api_events_port = 3700 +# bitcoin_explorer_port = 8001 +# stacks_explorer_port = 8000 +# postgres_port = 5432 +# postgres_username = "postgres" +# postgres_password = "postgres" +# postgres_database = "postgres" +# bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:26.0" +# stacks_node_image_url = "quay.io/hirosystems/stacks-node:devnet-2.5" +# stacks_signer_image_url = "quay.io/hirosystems/stacks-signer:devnet-2.5" +# stacks_api_image_url = "hirosystems/stacks-blockchain-api:master" +# stacks_explorer_image_url = "hirosystems/explorer:latest" +# bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet" +# postgres_image_url = "postgres:alpine" +# enable_subnet_node = true +# subnet_node_image_url = "hirosystems/stacks-subnets:0.8.1" +# subnet_leader_mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +# subnet_leader_derivation_path = "m/44'/5757'/0'/0/0" +# subnet_contract_id = "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1" +# subnet_node_rpc_port = 30443 +# subnet_node_p2p_port = 30444 +# subnet_events_ingestion_port = 30445 +# subnet_node_events_observers = ["host.docker.internal:8002"] +# subnet_api_image_url = "hirosystems/stacks-blockchain-api:master" +# subnet_api_postgres_database = "subnet_api" + +# epoch_2_0 = 100 +# epoch_2_05 = 100 +# epoch_2_1 = 101 +# epoch_2_2 = 102 +# epoch_2_3 = 103 +# epoch_2_4 = 104 +# epoch_2_5 = 108 +# epoch_3_0 = 144 + + +# Send some stacking orders +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_1" +slots = 2 +btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_2" +slots = 2 +btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_3" +slots = 2 +btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" + diff --git a/high-level_contracts/tests/prediction-market-bets.test.ts b/high-level_contracts/tests/prediction-market-bets.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/high-level_contracts/tests/prediction-market-bets.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); diff --git a/high-level_contracts/tests/prediction-market-core.test.ts b/high-level_contracts/tests/prediction-market-core.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/high-level_contracts/tests/prediction-market-core.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); diff --git a/high-level_contracts/tests/prediction-market-payout.test.ts b/high-level_contracts/tests/prediction-market-payout.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/high-level_contracts/tests/prediction-market-payout.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); diff --git a/high-level_contracts/tests/prediction-market_markets.test.ts b/high-level_contracts/tests/prediction-market_markets.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/high-level_contracts/tests/prediction-market_markets.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); diff --git a/high-level_contracts/tsconfig.json b/high-level_contracts/tsconfig.json new file mode 100644 index 0000000..1bdaf36 --- /dev/null +++ b/high-level_contracts/tsconfig.json @@ -0,0 +1,26 @@ + +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext"], + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src", + "tests" + ] +} diff --git a/high-level_contracts/vitest.config.js b/high-level_contracts/vitest.config.js new file mode 100644 index 0000000..c6a8506 --- /dev/null +++ b/high-level_contracts/vitest.config.js @@ -0,0 +1,42 @@ + +/// + +import { defineConfig } from "vite"; +import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest"; + +/* + In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet. + + The `vitest-environment-clarinet` will initialise the clarinet-sdk + and make the `simnet` object available globally in the test files. + + `vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things: + - run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports. + - load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`) + + The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --` + - vitest run -- --manifest ./Clarinet.toml # pass a custom path + - vitest run -- --coverage --costs # collect coverage and cost reports +*/ + +export default defineConfig({ + test: { + environment: "clarinet", // use vitest-environment-clarinet + pool: "forks", + poolOptions: { + threads: { singleThread: true }, + forks: { singleFork: true }, + }, + setupFiles: [ + vitestSetupFilePath, + // custom setup files can be added here + ], + environmentOptions: { + clarinet: { + ...getClarinetVitestsArgv(), + // add or override options + }, + }, + }, +}); +