diff --git a/blockchain-raffle/.gitattributes b/blockchain-raffle/.gitattributes new file mode 100644 index 0000000..da6a065 --- /dev/null +++ b/blockchain-raffle/.gitattributes @@ -0,0 +1,3 @@ +tests/** linguist-vendored +vitest.config.js linguist-vendored +* text=lf diff --git a/blockchain-raffle/.gitignore b/blockchain-raffle/.gitignore new file mode 100644 index 0000000..76c2842 --- /dev/null +++ b/blockchain-raffle/.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/blockchain-raffle/.vscode/settings.json b/blockchain-raffle/.vscode/settings.json new file mode 100644 index 0000000..3062519 --- /dev/null +++ b/blockchain-raffle/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "files.eol": "\n" +} diff --git a/blockchain-raffle/.vscode/tasks.json b/blockchain-raffle/.vscode/tasks.json new file mode 100644 index 0000000..4dec0ff --- /dev/null +++ b/blockchain-raffle/.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/blockchain-raffle/Clarinet.toml b/blockchain-raffle/Clarinet.toml new file mode 100644 index 0000000..d689771 --- /dev/null +++ b/blockchain-raffle/Clarinet.toml @@ -0,0 +1,19 @@ +[project] +name = 'blockchain-raffle' +description = '' +authors = [] +telemetry = true +cache_dir = '.\.cache' +requirements = [] +[contracts.stx-lottery] +path = 'contracts/stx-lottery.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/blockchain-raffle/README.md b/blockchain-raffle/README.md new file mode 100644 index 0000000..7b3a969 --- /dev/null +++ b/blockchain-raffle/README.md @@ -0,0 +1,79 @@ +# Lottery Pool Smart Contract + +# About +This smart contract implements a decentralized lottery system on the Stacks blockchain. It allows for creating and managing lotteries, selling tickets, selecting winners, and distributing prizes. + +## Features + +- Create new lotteries with customizable parameters +- Purchase lottery tickets +- Withdraw tickets during a specified period +- Automatically end lotteries and select winners +- Claim prizes for winners +- Read-only functions for querying lottery state + +## Contract Functions + +### Administrative Functions + +- `start-new-lottery`: Initializes a new lottery with specified parameters. +- `end-current-lottery`: Ends the current lottery and calculates prizes. +- `select-winners`: Selects winners for the ended lottery. + +### User Functions + +- `purchase-lottery-ticket`: Allows users to buy lottery tickets. +- `withdraw-tickets`: Enables users to withdraw their tickets during the withdrawal period. +- `claim-prize`: Allows winners to claim their prizes. + +### Read-Only Functions + +- `get-current-ticket-price`: Returns the current ticket price. +- `get-current-lottery-pot`: Returns the total amount in the lottery pot. +- `get-user-ticket-count`: Returns the number of tickets owned by a user. +- `get-total-tickets-sold`: Returns the total number of tickets sold. +- `check-if-lottery-is-active`: Checks if a lottery is currently active. +- `get-lottery-end-block-height`: Returns the block height when the lottery ends. +- `get-withdrawal-end-block-height`: Returns the block height when ticket withdrawals end. +- `get-organizer-fee-percentage`: Returns the organizer's fee percentage. +- `get-winner-info`: Returns information about a specific winner. +- `are-winners-selected`: Checks if winners have been selected for the current lottery. + +## Usage + +1. Deploy the contract to the Stacks blockchain. +2. Use the `start-new-lottery` function to initialize a new lottery. +3. Users can purchase tickets using the `purchase-lottery-ticket` function. +4. Users can withdraw tickets during the withdrawal period using `withdraw-tickets`. +5. After the lottery end time, the contract owner can end the lottery using `end-current-lottery`. +6. The contract owner then selects winners using `select-winners`. +7. Winners can claim their prizes using the `claim-prize` function. + +## Error Handling + +The contract includes various error codes for different scenarios: + +- `ERR_NOT_AUTHORIZED`: User is not authorized to perform the action. +- `ERR_LOTTERY_INACTIVE`: The lottery is not active. +- `ERR_INSUFFICIENT_BALANCE`: User has insufficient balance. +- `ERR_INVALID_TICKET_PRICE`: The ticket price is invalid. +- `ERR_NO_WINNERS`: No winners could be selected. +- `ERR_NO_TICKETS`: User has no tickets to withdraw. +- `ERR_WITHDRAWAL_PERIOD_ENDED`: The withdrawal period has ended. +- `ERR_LOTTERY_NOT_ENDED`: The lottery has not ended yet. +- `ERR_WINNERS_ALREADY_SELECTED`: Winners have already been selected. + +## Security Considerations + +- The contract uses various checks to ensure only authorized actions are performed. +- Random winner selection is based on a provided seed and the block height. +- There's a maximum fee percentage (20%) to prevent excessive fees. + +## Development and Testing + +To develop and test this contract: + +1. Set up a Stacks development environment. +2. Use Clarinet for local testing and deployment. +3. Write unit tests to cover all contract functions and edge cases. +4. Deploy to testnet before mainnet for thorough testing. \ No newline at end of file diff --git a/blockchain-raffle/Testnet.toml b/blockchain-raffle/Testnet.toml new file mode 100644 index 0000000..b9cfb45 --- /dev/null +++ b/blockchain-raffle/Testnet.toml @@ -0,0 +1,7 @@ +[network] +name = "testnet" +stacks_node_rpc_address = "https://api.testnet.hiro.so" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "" diff --git a/blockchain-raffle/contracts/stx-lottery.clar b/blockchain-raffle/contracts/stx-lottery.clar new file mode 100644 index 0000000..61db787 --- /dev/null +++ b/blockchain-raffle/contracts/stx-lottery.clar @@ -0,0 +1,185 @@ +;; Lottery Pool Smart Contract + +;; Constants +(define-constant CONTRACT_OWNER tx-sender) +(define-constant ERR_NOT_AUTHORIZED (err u100)) +(define-constant ERR_LOTTERY_INACTIVE (err u102)) +(define-constant ERR_INSUFFICIENT_BALANCE (err u103)) +(define-constant ERR_INVALID_TICKET_PRICE (err u104)) +(define-constant ERR_NO_WINNERS (err u105)) +(define-constant ERR_NO_TICKETS (err u106)) +(define-constant ERR_WITHDRAWAL_PERIOD_ENDED (err u107)) +(define-constant ERR_LOTTERY_NOT_ENDED (err u108)) +(define-constant ERR_WINNERS_ALREADY_SELECTED (err u109)) +(define-constant ERR_INVALID_DURATION (err u110)) +(define-constant ERR_INVALID_WITHDRAWAL_PERIOD (err u111)) +(define-constant ERR_INVALID_WINNER_ID (err u112)) + +;; Data Variables +(define-data-var is-lottery-active bool false) +(define-data-var current-ticket-price uint u1000000) ;; 1 STX +(define-data-var current-lottery-pot uint u0) +(define-data-var total-tickets-sold uint u0) +(define-data-var number-of-winners uint u1) +(define-data-var lottery-end-block-height uint u0) +(define-data-var withdrawal-end-block-height uint u0) +(define-data-var organizer-fee-percentage uint u5) ;; 5% fee +(define-data-var prize-per-winner uint u0) +(define-data-var winners-selected bool false) + +;; Maps +(define-map ticket-ownership {ticket-id: uint} {owner: principal}) +(define-map user-ticket-count principal uint) +(define-map winners {winner-id: uint} {address: principal, claimed: bool}) + +;; Private Functions +(define-private (is-contract-owner) + (is-eq tx-sender CONTRACT_OWNER)) + +(define-private (validate-lottery-is-active) + (if (var-get is-lottery-active) + (ok true) + ERR_LOTTERY_INACTIVE)) + +(define-private (validate-sufficient-balance (required-balance uint)) + (if (>= (stx-get-balance tx-sender) required-balance) + (ok true) + ERR_INSUFFICIENT_BALANCE)) + +(define-private (select-random-winner (random-seed uint) (ticket-id uint)) + (mod (+ random-seed ticket-id) (var-get total-tickets-sold))) + +(define-private (transfer-prize-to-winner (winner-address principal) (prize-amount uint)) + (as-contract (stx-transfer? prize-amount tx-sender winner-address))) + +(define-private (calculate-organizer-fee (total-amount uint)) + (/ (* total-amount (var-get organizer-fee-percentage)) u100)) + +;; Public Functions +(define-public (start-new-lottery (duration-in-blocks uint) (withdrawal-period uint) (ticket-price uint) (winner-count uint) (fee-percentage uint)) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (> ticket-price u0) ERR_INVALID_TICKET_PRICE) + (asserts! (> winner-count u0) ERR_NO_WINNERS) + (asserts! (<= fee-percentage u20) ERR_NOT_AUTHORIZED) ;; Max 20% fee + (asserts! (not (var-get is-lottery-active)) ERR_NOT_AUTHORIZED) + (asserts! (> duration-in-blocks u0) ERR_INVALID_DURATION) + (asserts! (> withdrawal-period u0) ERR_INVALID_WITHDRAWAL_PERIOD) + (var-set is-lottery-active true) + (var-set current-ticket-price ticket-price) + (var-set current-lottery-pot u0) + (var-set total-tickets-sold u0) + (var-set number-of-winners winner-count) + (var-set lottery-end-block-height (+ block-height duration-in-blocks)) + (var-set withdrawal-end-block-height (+ block-height withdrawal-period)) + (var-set organizer-fee-percentage fee-percentage) + (var-set winners-selected false) + (ok true))) + +(define-public (purchase-lottery-ticket) + (let ((ticket-price (var-get current-ticket-price))) + (begin + (try! (validate-lottery-is-active)) + (try! (validate-sufficient-balance ticket-price)) + (try! (stx-transfer? ticket-price tx-sender (as-contract tx-sender))) + (var-set current-lottery-pot (+ (var-get current-lottery-pot) ticket-price)) + (var-set total-tickets-sold (+ (var-get total-tickets-sold) u1)) + (map-set ticket-ownership {ticket-id: (var-get total-tickets-sold)} {owner: tx-sender}) + (map-set user-ticket-count tx-sender (+ (default-to u0 (map-get? user-ticket-count tx-sender)) u1)) + (ok (var-get total-tickets-sold))))) + +(define-public (withdraw-tickets (ticket-count uint)) + (let ((user-tickets (default-to u0 (map-get? user-ticket-count tx-sender))) + (refund-amount (* ticket-count (var-get current-ticket-price)))) + (begin + (try! (validate-lottery-is-active)) + (asserts! (<= block-height (var-get withdrawal-end-block-height)) ERR_WITHDRAWAL_PERIOD_ENDED) + (asserts! (>= user-tickets ticket-count) ERR_NO_TICKETS) + (var-set current-lottery-pot (- (var-get current-lottery-pot) refund-amount)) + (var-set total-tickets-sold (- (var-get total-tickets-sold) ticket-count)) + (map-set user-ticket-count tx-sender (- user-tickets ticket-count)) + (as-contract (stx-transfer? refund-amount tx-sender tx-sender))))) + +(define-public (end-current-lottery) + (let ((total-pot (var-get current-lottery-pot)) + (winner-count (var-get number-of-winners)) + (total-ticket-count (var-get total-tickets-sold)) + (organizer-fee (calculate-organizer-fee total-pot))) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (>= block-height (var-get lottery-end-block-height)) ERR_LOTTERY_NOT_ENDED) + (try! (validate-lottery-is-active)) + (asserts! (> total-ticket-count u0) ERR_NO_WINNERS) + (var-set is-lottery-active false) + (try! (as-contract (stx-transfer? organizer-fee tx-sender CONTRACT_OWNER))) + (let ((prize-pool (- total-pot organizer-fee))) + (var-set prize-per-winner (/ prize-pool winner-count))) + (ok true)))) + +(define-public (select-winners (random-seed uint)) + (let ((winner-count (var-get number-of-winners)) + (total-ticket-count (var-get total-tickets-sold))) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (not (var-get is-lottery-active)) ERR_LOTTERY_INACTIVE) + (asserts! (not (var-get winners-selected)) ERR_WINNERS_ALREADY_SELECTED) + (asserts! (> total-ticket-count u0) ERR_NO_WINNERS) + (var-set winners-selected true) + (let ((selected-winners (fold select-winner-and-save + (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9) + {random-seed: random-seed, current-winner-id: u0, remaining-winners: winner-count}))) + (ok (get current-winner-id selected-winners)))))) + +(define-private (select-winner-and-save (index uint) (context {random-seed: uint, current-winner-id: uint, remaining-winners: uint})) + (if (> (get remaining-winners context) u0) + (let ((winning-ticket-id (select-random-winner (get random-seed context) index)) + (winner-address (get owner (unwrap-panic (map-get? ticket-ownership {ticket-id: (+ winning-ticket-id u1)}))))) + (begin + (map-set winners {winner-id: (get current-winner-id context)} {address: winner-address, claimed: false}) + {random-seed: (+ (get random-seed context) u1), + current-winner-id: (+ (get current-winner-id context) u1), + remaining-winners: (- (get remaining-winners context) u1)})) + context)) + +(define-public (claim-prize (winner-id uint)) + (let ((winner-info (unwrap! (map-get? winners {winner-id: winner-id}) ERR_INVALID_WINNER_ID)) + (winner-address (get address winner-info)) + (claimed (get claimed winner-info))) + (begin + (asserts! (is-eq tx-sender winner-address) ERR_NOT_AUTHORIZED) + (asserts! (not claimed) ERR_NOT_AUTHORIZED) + (try! (transfer-prize-to-winner winner-address (var-get prize-per-winner))) + (asserts! (< winner-id (var-get number-of-winners)) ERR_INVALID_WINNER_ID) + (map-set winners {winner-id: winner-id} {address: winner-address, claimed: true}) + (ok true)))) + +;; Read-Only Functions +(define-read-only (get-current-ticket-price) + (ok (var-get current-ticket-price))) + +(define-read-only (get-current-lottery-pot) + (ok (var-get current-lottery-pot))) + +(define-read-only (get-user-ticket-count (user-address principal)) + (ok (default-to u0 (map-get? user-ticket-count user-address)))) + +(define-read-only (get-total-tickets-sold) + (ok (var-get total-tickets-sold))) + +(define-read-only (check-if-lottery-is-active) + (ok (var-get is-lottery-active))) + +(define-read-only (get-lottery-end-block-height) + (ok (var-get lottery-end-block-height))) + +(define-read-only (get-withdrawal-end-block-height) + (ok (var-get withdrawal-end-block-height))) + +(define-read-only (get-organizer-fee-percentage) + (ok (var-get organizer-fee-percentage))) + +(define-read-only (get-winner-info (winner-id uint)) + (ok (map-get? winners {winner-id: winner-id}))) + +(define-read-only (are-winners-selected) + (ok (var-get winners-selected))) \ No newline at end of file diff --git a/blockchain-raffle/package.json b/blockchain-raffle/package.json new file mode 100644 index 0000000..b35fb59 --- /dev/null +++ b/blockchain-raffle/package.json @@ -0,0 +1,24 @@ + +{ + "name": "blockchain-raffle-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/blockchain-raffle/settings/Devnet.toml b/blockchain-raffle/settings/Devnet.toml new file mode 100644 index 0000000..7d865b9 --- /dev/null +++ b/blockchain-raffle/settings/Devnet.toml @@ -0,0 +1,151 @@ +[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_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" + +# For testing in epoch 2.1 / using Clarity2 +# 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 + + +# Send some stacking orders +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_1" +slots = 2 +btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_2" +slots = 1 +btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 12 +wallet = "wallet_3" +slots = 1 +btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" diff --git a/blockchain-raffle/tests/stx-lottery.test.ts b/blockchain-raffle/tests/stx-lottery.test.ts new file mode 100644 index 0000000..f2d4bb8 --- /dev/null +++ b/blockchain-raffle/tests/stx-lottery.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/clarinet/feature-guides/test-contract-with-clarinet-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/blockchain-raffle/tsconfig.json b/blockchain-raffle/tsconfig.json new file mode 100644 index 0000000..1bdaf36 --- /dev/null +++ b/blockchain-raffle/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/blockchain-raffle/vitest.config.js b/blockchain-raffle/vitest.config.js new file mode 100644 index 0000000..c6a8506 --- /dev/null +++ b/blockchain-raffle/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 + }, + }, + }, +}); +