Source Code

;; felix-second-pilot
;; v5
;; Learn more at https://felixapp.xyz/
;; ---
;;
(define-constant felix 'SP3RDC4C9B0A2FG8B7DQ9MBTFPYQZNDAVCBME8Q41)
(define-constant fee u100000)
(define-constant difficulty u4)
(define-constant ticket-price u1000000)
(define-constant number-of-tickets u10000)
(define-constant slot-size u500000000)
(define-constant number-of-slots u3)
(define-constant start-block-height u169338)
(define-constant start-block-buffer u10)
(define-constant end-block-height u170338)
(define-constant end-cooldown u6)
(define-non-fungible-token felix-second-pilot uint)

(define-constant contract-principal (as-contract tx-sender))

(define-data-var drawn-number (optional uint) none)
(define-data-var winner (optional uint) none)
(define-data-var prize-pool uint u0)
(define-data-var sold-tickets-pool uint u0)
(define-data-var admin principal felix)
(define-data-var funder-count uint u0)
(define-data-var last-ticket-id uint u0)

;; Maps funders to a bool indicating if they have claimed their funds
(define-map funders principal bool)
;; Maps chosen numbers to ticket ids
(define-map numbersToTicketIds uint uint)

(define-constant err-not-ticket-owner (err u101))
(define-constant err-inexistent-ticket-id (err u102))
(define-constant err-sold-out (err u200))
(define-constant err-invalid-difficulty (err u201))
(define-constant err-invalid-number (err u202))
(define-constant err-end-too-close (err u300) )
(define-constant err-not-ended-yet (err u301) )
(define-constant err-start-too-early (err u500))
(define-constant err-not-funded (err u501))
(define-constant err-invalid-status (err u502))
(define-constant err-no-funding-slot-available (err u503))
(define-constant err-principal-already-funder (err u504))
(define-constant err-start-too-close (err u505))
(define-constant err-couldnt-update-ticket-ids (err u600))
(define-constant err-unable-to-end-lottery (err u700))
(define-constant err-unable-to-get-random-seed (err u701))
(define-constant err-number-already-sold (err u800))
(define-constant err-invalid-drawn-number (err u900))
(define-constant err-not-ticket-winner (err u901))
(define-constant err-funder-already-claimed (err u1000))
(define-constant err-not-funder (err u1001))
(define-constant err-refund-already-claimed (err u1002))
(define-constant err-admin-only (err u2000))
(define-constant err-standard-principal-only (err u2001))
(define-constant err-cant-burn-winning-ticket (err u3000))

(define-constant available-contract-status
    (list "funding" "active" "won" "cancelled" "finished"))
(define-private (funding-status) (unwrap-panic (element-at? available-contract-status u0)))
(define-private (active-status) (unwrap-panic (element-at? available-contract-status u1)))
(define-private (won-status) (unwrap-panic (element-at? available-contract-status u2)))
(define-private (cancelled-status) (unwrap-panic (element-at? available-contract-status u3)))
(define-private (finished-status) (unwrap-panic (element-at? available-contract-status u4)))
(define-data-var current-status (string-ascii 9) (funding-status))
(define-private (is-active) (is-eq (var-get current-status) (active-status)))
(define-private (is-won) (is-eq (var-get current-status) (won-status)))
(define-private (is-finished) (is-eq (var-get current-status) (finished-status)))
(define-private (is-cancelled) (is-eq (var-get current-status) (cancelled-status)))
(define-private (is-in-funding) (is-eq (var-get current-status) (funding-status)))
(define-private (is-funder (test-principal principal))
    (is-some (map-get? funders test-principal)))
(define-private (is-funded)
    (> (var-get funder-count) u0))

(define-private (is-standard-principal-call)
    (is-none (get name (unwrap! (principal-destruct? contract-caller) false))))

(define-private (is-admin (test-principal principal)) (is-eq (var-get admin) test-principal))

(define-private (pick-lottery-numbers (seed uint))
    (if (is-eq difficulty u1) (ok (mod seed u10))
    (if (is-eq difficulty u2) (ok (mod seed u100))
    (if (is-eq difficulty u3) (ok (mod seed u1000))
    (if (is-eq difficulty u4) (ok (mod seed u10000))
    (if (is-eq difficulty u5) (ok (mod seed u100000))
    (if (is-eq difficulty u6) (ok (mod seed u1000000))
    (if (is-eq difficulty u7) (ok (mod seed u10000000))
    (if (is-eq difficulty u8) (ok (mod seed u100000000))
    (if (is-eq difficulty u9) (ok (mod seed u1000000000))
    (if (is-eq difficulty u10) (ok (mod seed u10000000000))
    err-invalid-difficulty)))))))))))

(define-private (end-lottery)
    (begin
        (asserts! (is-active) err-invalid-status)
        (asserts! (is-some (var-get drawn-number)) err-unable-to-end-lottery)
        (let
            ((maybe-winner-ticket-id (map-get? numbersToTicketIds (unwrap-panic (var-get drawn-number)))))
        (var-set winner maybe-winner-ticket-id)
        (var-set current-status (if (is-some maybe-winner-ticket-id) (won-status) (finished-status)))
        (ok true))))

(define-read-only (get-drawn-number)
    (var-get drawn-number))

(define-read-only (get-winner-ticket-id) (ok (var-get winner)))

(define-read-only (get-prize-pool) (ok (var-get prize-pool)))

(define-read-only (get-sold-tickets-pool) (ok (var-get sold-tickets-pool)))

(define-read-only (get-ticket-ids (num-to-check uint))
    (ok (map-get? numbersToTicketIds num-to-check)))

(define-read-only (get-owner (token-id uint))
    (ok (nft-get-owner? felix-second-pilot token-id)))

(define-read-only (get-status) (ok (var-get current-status)))

(define-public (fund) 
    (let
        ((last-funder-count (var-get funder-count))
        (current-prize (var-get prize-pool))) 
    (asserts! (is-standard-principal-call) err-standard-principal-only)
    (asserts! (is-in-funding) err-invalid-status)
    (asserts! (> start-block-height block-height) err-start-too-close)
    (asserts! (< block-height (- end-block-height end-cooldown)) err-end-too-close)
    (asserts! (> end-block-height (+ start-block-height start-block-buffer)) err-end-too-close)
    (asserts! (< last-funder-count number-of-slots) err-no-funding-slot-available)
    (asserts! (not (is-funder contract-caller)) err-principal-already-funder)
    (try! (stx-transfer? slot-size contract-caller contract-principal))
    (map-insert funders contract-caller false)
    (var-set funder-count (+ last-funder-count u1))
    (var-set prize-pool (+ current-prize slot-size))
    (ok true)))

(define-public (start)
    (begin
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (is-in-funding) err-invalid-status)
        (asserts! (> block-height start-block-height) err-start-too-early)
        (asserts! (> end-block-height (+ start-block-height start-block-buffer)) err-end-too-close)
        (asserts! (> end-block-height block-height) err-end-too-close)
        (asserts! (> (var-get funder-count) u0) err-not-funded)
        (var-set current-status (active-status))
        (ok true)))

(define-public (cancel)
    (begin
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (is-admin contract-caller) err-admin-only)
        (var-set current-status (cancelled-status))
        (ok true)))

(define-public (draw-numbers)
    (begin 
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (is-active) err-invalid-status)
        (asserts! (> block-height end-block-height) err-not-ended-yet)
        (let
            ;; We're using the citycoin-vrf-v2 contract to get a random number
            ((random-number (unwrap! (contract-call? 'SP3RDC4C9B0A2FG8B7DQ9MBTFPYQZNDAVCBME8Q41.felix-meta-v3 get-rnd (- end-block-height u1)) err-unable-to-get-random-seed))
            (lottery-numbers (unwrap-panic (pick-lottery-numbers random-number))))
        (var-set drawn-number (some lottery-numbers))
        (try! (end-lottery))
        (ok lottery-numbers))))

(define-public (buy-ticket (recipient principal) (selected-nums uint))
    (let
        ((ticket-id (+ (var-get last-ticket-id) u1))
        (current-sells (var-get sold-tickets-pool)))
    (asserts! (is-standard-principal-call) err-standard-principal-only)
    (asserts! (is-active) err-invalid-status)
    (asserts! (< block-height (- end-block-height end-cooldown)) err-end-too-close)
    (asserts! (< (var-get last-ticket-id) number-of-tickets) err-sold-out)
    (asserts! (<= selected-nums (- (pow u10 difficulty) u1)) err-invalid-number)
    (asserts! (is-none (map-get? numbersToTicketIds selected-nums)) err-number-already-sold)
    ;; #[allow(unchecked_data)]
    (asserts! (map-insert numbersToTicketIds selected-nums ticket-id) err-couldnt-update-ticket-ids)
    ;; #[allow(unchecked_data)]
    (try! (stx-transfer? ticket-price contract-caller contract-principal))
    (try! (stx-transfer? fee contract-caller (var-get admin)))
    ;; #[allow(unchecked_data)]
    (try! (nft-mint? felix-second-pilot ticket-id recipient))
    (var-set last-ticket-id ticket-id)
    (var-set sold-tickets-pool (+ current-sells ticket-price))
    (ok ticket-id)))

(define-public (claim-prize (ticket-id uint))
    (let
        ((alleged-winner-principal contract-caller)
        (prize (var-get prize-pool)))
    (asserts! (is-standard-principal-call) err-standard-principal-only)
    (asserts! (is-won) err-invalid-status)
    (asserts! (is-eq (unwrap! (nft-get-owner? felix-second-pilot ticket-id) err-inexistent-ticket-id) contract-caller) err-not-ticket-owner)
    (asserts! (is-eq ticket-id (unwrap-panic (var-get winner))) err-not-ticket-winner)
    (try! (as-contract (stx-transfer? prize contract-principal alleged-winner-principal)))
    (try! (nft-burn? felix-second-pilot ticket-id alleged-winner-principal))
    (ok true)))

(define-public (claim-funds)
    (let
        ((claimer contract-caller)
        (has-claimed (map-get? funders claimer)))
    (asserts! (is-standard-principal-call) err-standard-principal-only)
    (asserts! (is-funder claimer) err-not-funder)
    (asserts! (or (is-won) (is-finished)) err-invalid-status)
    (asserts! (not (unwrap! has-claimed err-not-funder)) err-funder-already-claimed)
    (let
        ((number-of-funders (var-get funder-count))
        (sold-ticket-part (/ (var-get sold-tickets-pool) number-of-funders))
        (fund-return (if (is-won) u0 slot-size))
        (total-claim (+ sold-ticket-part fund-return)))
    (try! (as-contract (stx-transfer? total-claim contract-principal claimer)))
    (map-set funders claimer true)
    (ok true))))

(define-public (get-ticket-refund (ticket-id uint))
    (let
        ((ticket-owner contract-caller))
    (asserts! (is-standard-principal-call) err-standard-principal-only)
    (asserts! (is-eq (unwrap! (nft-get-owner? felix-second-pilot ticket-id) err-inexistent-ticket-id) contract-caller) err-not-ticket-owner)
    (asserts! (is-cancelled) err-invalid-status)
    (try! (as-contract (stx-transfer? ticket-price contract-principal ticket-owner)))
    (try! (nft-burn? felix-second-pilot ticket-id ticket-owner))
    (ok ticket-id)))

(define-public (get-fund-refund)
    (begin
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (is-cancelled) err-invalid-status)
        (let
            ((claimer contract-caller)
            (has-refunded (unwrap! (map-get? funders claimer) err-not-funder)))
        (asserts! (is-funder claimer) err-not-funder)
        (asserts! (not has-refunded) err-refund-already-claimed)
        (try! (as-contract (stx-transfer? slot-size contract-principal claimer)))
        (map-set funders claimer true)
        (ok true))))

(define-public (burn-ticket (ticket-id uint))
    (begin
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (or (is-finished) (is-won)) err-invalid-status)
        (asserts! (not (is-eq (default-to u0 (var-get winner)) ticket-id)) err-cant-burn-winning-ticket)
        ;; #[allow(unchecked_data)]
        (nft-burn? felix-second-pilot ticket-id contract-caller)))

(define-public (update-admin (new-admin principal))
    (begin
        (asserts! (is-standard-principal-call) err-standard-principal-only)
        (asserts! (is-admin contract-caller) err-admin-only)
        ;; #[allow(unchecked_data)]
        (var-set admin new-admin)
        (ok new-admin)))

Functions (34)

FunctionAccessArgs
funding-statusprivate
active-statusprivate
won-statusprivate
cancelled-statusprivate
finished-statusprivate
is-activeprivate
is-wonprivate
is-finishedprivate
is-cancelledprivate
is-in-fundingprivate
is-funderprivatetest-principal: principal
is-fundedprivate
is-standard-principal-callprivate
is-adminprivatetest-principal: principal
pick-lottery-numbersprivateseed: uint
end-lotteryprivate
get-drawn-numberread-only
get-winner-ticket-idread-only
get-prize-poolread-only
get-sold-tickets-poolread-only
get-ticket-idsread-onlynum-to-check: uint
get-ownerread-onlytoken-id: uint
get-statusread-only
fundpublic
startpublic
cancelpublic
draw-numberspublic
buy-ticketpublicrecipient: principal, selected-nums: uint
claim-prizepublicticket-id: uint
claim-fundspublic
get-ticket-refundpublicticket-id: uint
get-fund-refundpublic
burn-ticketpublicticket-id: uint
update-adminpublicnew-admin: principal