diff --git a/copy-test-outputs.py b/copy-test-outputs.py index 93fed789..a849a2c1 100644 --- a/copy-test-outputs.py +++ b/copy-test-outputs.py @@ -17,7 +17,7 @@ from pathlib import Path # Backend directories to process -BACKENDS = ["gambit", "graphviz", "lightning", "smt", "solidity", "vyper"] +BACKENDS = ["clarity", "gambit", "graphviz", "lightning", "smt", "solidity", "vyper"] def copy_test_outputs(): """Copy non-.diff files from test-diffs/BACKEND/ to examples/BACKEND/""" diff --git a/examples/clarity/MontyHall.clar b/examples/clarity/MontyHall.clar new file mode 100644 index 00000000..1621df10 --- /dev/null +++ b/examples/clarity/MontyHall.clar @@ -0,0 +1,246 @@ +;; MontyHall - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-guest (optional principal) none) +(define-data-var deposit-guest uint u0) +(define-data-var role-host (optional principal) none) +(define-data-var deposit-host uint u0) +(define-data-var total-pot uint u0) + +(define-data-var commit-host-car (optional (buff 32)) none) +(define-data-var var-guest-d int 0) +(define-data-var var-host-goat int 0) +(define-data-var var-guest-switch bool false) +(define-data-var var-host-car int 0) + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-guest) + (begin + (asserts! (is-none (var-get role-guest)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u20 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u20)) + (var-set deposit-guest u20) + (var-set role-guest (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-public (register-host) + (begin + (asserts! (is-none (var-get role-host)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u20 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u20)) + (var-set deposit-host u20) + (var-set role-host (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-guest)) (is-some (var-get role-host))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + (map-set action-done u0 true) + (map-set action-done u1 true) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-guest) r + (let ((amt (var-get deposit-guest))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (match (var-get role-host) r + (let ((amt (var-get deposit-host))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-guest none) + (var-set deposit-guest u0) + (var-set role-host none) + (var-set deposit-host u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +(define-public (action-host-2 (car (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-host)) ERR_WRONG_ROLE) + (asserts! (not (is-done u2)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (var-set commit-host-car (some car)) + (map-set action-done u2 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-guest-3 (d int) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-guest)) ERR_WRONG_ROLE) + (asserts! (not (is-done u3)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (or (is-eq d 0) (is-eq d 1) (is-eq d 2)) ERR_INVALID_PARAM) + (var-set var-guest-d d) + (map-set action-done u3 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-host-4 (goat int) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-host)) ERR_WRONG_ROLE) + (asserts! (not (is-done u4)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (not (is-eq goat (var-get var-guest-d))) ERR_GUARD_FAILED) + (asserts! (or (is-eq goat 0) (is-eq goat 1) (is-eq goat 2)) ERR_INVALID_PARAM) + (var-set var-host-goat goat) + (map-set action-done u4 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-guest-5 (switch bool) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-guest)) ERR_WRONG_ROLE) + (asserts! (not (is-done u5)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u4) ERR_DEPENDENCY_NOT_MET) + (var-set var-guest-switch switch) + (map-set action-done u5 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-host-6 (car int) (car-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-host)) ERR_WRONG_ROLE) + (asserts! (not (is-done u6)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u5) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u4) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (not (is-eq (var-get var-host-goat) car)) ERR_GUARD_FAILED) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? car)) + car-salt + (unwrap-panic (var-get commit-host-car)) +) ERR_COMMIT_MISMATCH) + (asserts! (or (is-eq car 0) (is-eq car 1) (is-eq car 2)) ERR_INVALID_PARAM) + (var-set var-host-car car) + (map-set action-done u6 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u6) ERR_NOT_OPEN) + (asserts! (is-eq (+ (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u6)) (is-done u5)) (if (is-eq (not (is-eq (var-get var-guest-d) (var-get var-host-car))) (var-get var-guest-switch)) 40 0) (if (not (or (is-done u2) (is-done u6))) 40 0)))) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u6)) (is-done u5)) (if (is-eq (not (is-eq (var-get var-guest-d) (var-get var-host-car))) (var-get var-guest-switch)) 0 40) (if (not (or (is-done u2) (is-done u6))) 0 40))))) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-guest)) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u6)) (is-done u5)) (if (is-eq (not (is-eq (var-get var-guest-d) (var-get var-host-car))) (var-get var-guest-switch)) 40 0) (if (not (or (is-done u2) (is-done u6))) 40 0))))) + (map-set claims (unwrap-panic (var-get role-host)) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u6)) (is-done u5)) (if (is-eq (not (is-eq (var-get var-guest-d) (var-get var-host-car))) (var-get var-guest-switch)) 0 40) (if (not (or (is-done u2) (is-done u6))) 0 40))))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (if (not (is-done u2)) (begin (map-set claims (unwrap-panic (var-get role-guest)) u40) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u3)) (begin (map-set claims (unwrap-panic (var-get role-host)) u40) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u4)) (begin (map-set claims (unwrap-panic (var-get role-guest)) u40) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u5)) (begin (map-set claims (unwrap-panic (var-get role-host)) u40) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u6)) (begin (map-set claims (unwrap-panic (var-get role-guest)) u40) (var-set payoffs-distributed true) (ok true)) (err ERR_NOT_OPEN)))))) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/examples/clarity/OddsEvens.clar b/examples/clarity/OddsEvens.clar new file mode 100644 index 00000000..85be2a01 --- /dev/null +++ b/examples/clarity/OddsEvens.clar @@ -0,0 +1,239 @@ +;; OddsEvens - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-even (optional principal) none) +(define-data-var deposit-even uint u0) +(define-data-var role-odd (optional principal) none) +(define-data-var deposit-odd uint u0) +(define-data-var total-pot uint u0) + +(define-data-var commit-odd-c (optional (buff 32)) none) +(define-data-var commit-even-c (optional (buff 32)) none) +(define-data-var var-odd-c bool false) +(define-data-var var-even-c bool false) + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-even) + (begin + (asserts! (is-none (var-get role-even)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-even u100) + (var-set role-even (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-public (register-odd) + (begin + (asserts! (is-none (var-get role-odd)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-odd u100) + (var-set role-odd (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-even)) (is-some (var-get role-odd))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + (map-set action-done u0 true) + (map-set action-done u1 true) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-even) r + (let ((amt (var-get deposit-even))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (match (var-get role-odd) r + (let ((amt (var-get deposit-odd))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-even none) + (var-set deposit-even u0) + (var-set role-odd none) + (var-set deposit-odd u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +(define-public (action-odd-2 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-odd)) ERR_WRONG_ROLE) + (asserts! (not (is-done u2)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (var-set commit-odd-c (some c)) + (map-set action-done u2 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-even-4 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-even)) ERR_WRONG_ROLE) + (asserts! (not (is-done u3)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (var-set commit-even-c (some c)) + (map-set action-done u3 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-odd-3 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-odd)) ERR_WRONG_ROLE) + (asserts! (not (is-done u4)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-odd-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-odd-c c) + (map-set action-done u4 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-even-5 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-even)) ERR_WRONG_ROLE) + (asserts! (not (is-done u5)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u4) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-even-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-even-c c) + (map-set action-done u5 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u5) ERR_NOT_OPEN) + (asserts! (is-eq (+ (unwrap-panic (to-uint (if (and (or (is-done u3) (is-done u5)) (or (is-done u2) (is-done u4))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 126 74) (if (and (not (or (is-done u3) (is-done u5))) (or (is-done u2) (is-done u4))) 20 (if (and (or (is-done u3) (is-done u5)) (not (or (is-done u2) (is-done u4)))) 180 100))))) (unwrap-panic (to-uint (if (and (or (is-done u3) (is-done u5)) (or (is-done u2) (is-done u4))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 74 126) (if (and (not (or (is-done u3) (is-done u5))) (or (is-done u2) (is-done u4))) 180 (if (and (or (is-done u3) (is-done u5)) (not (or (is-done u2) (is-done u4)))) 20 100)))))) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-even)) (unwrap-panic (to-uint (if (and (or (is-done u3) (is-done u5)) (or (is-done u2) (is-done u4))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 126 74) (if (and (not (or (is-done u3) (is-done u5))) (or (is-done u2) (is-done u4))) 20 (if (and (or (is-done u3) (is-done u5)) (not (or (is-done u2) (is-done u4)))) 180 100)))))) + (map-set claims (unwrap-panic (var-get role-odd)) (unwrap-panic (to-uint (if (and (or (is-done u3) (is-done u5)) (or (is-done u2) (is-done u4))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 74 126) (if (and (not (or (is-done u3) (is-done u5))) (or (is-done u2) (is-done u4))) 180 (if (and (or (is-done u3) (is-done u5)) (not (or (is-done u2) (is-done u4)))) 20 100)))))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (if (not (is-done u2)) (begin (map-set claims (unwrap-panic (var-get role-even)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u3)) (begin (map-set claims (unwrap-panic (var-get role-odd)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u4)) (begin (map-set claims (unwrap-panic (var-get role-even)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u5)) (begin (map-set claims (unwrap-panic (var-get role-odd)) u200) (var-set payoffs-distributed true) (ok true)) (err ERR_NOT_OPEN))))) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/examples/clarity/OddsEvensShort.clar b/examples/clarity/OddsEvensShort.clar new file mode 100644 index 00000000..b5f692be --- /dev/null +++ b/examples/clarity/OddsEvensShort.clar @@ -0,0 +1,229 @@ +;; OddsEvensShort - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-even (optional principal) none) +(define-data-var deposit-even uint u0) +(define-data-var role-odd (optional principal) none) +(define-data-var deposit-odd uint u0) +(define-data-var total-pot uint u0) + +(define-data-var commit-odd-c (optional (buff 32)) none) +(define-data-var commit-even-c (optional (buff 32)) none) +(define-data-var var-odd-c bool false) +(define-data-var var-even-c bool false) + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-even) + (begin + (asserts! (is-none (var-get role-even)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-even u100) + (var-set role-even (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-public (register-odd) + (begin + (asserts! (is-none (var-get role-odd)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-odd u100) + (var-set role-odd (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-even)) (is-some (var-get role-odd))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-even) r + (let ((amt (var-get deposit-even))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (match (var-get role-odd) r + (let ((amt (var-get deposit-odd))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-even none) + (var-set deposit-even u0) + (var-set role-odd none) + (var-set deposit-odd u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +(define-public (action-odd-1 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-odd)) ERR_WRONG_ROLE) + (asserts! (not (is-done u0)) ERR_ACTION_ALREADY_DONE) + (var-set commit-odd-c (some c)) + (map-set action-done u0 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-even-3 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-even)) ERR_WRONG_ROLE) + (asserts! (not (is-done u1)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (var-set commit-even-c (some c)) + (map-set action-done u1 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-odd-2 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-odd)) ERR_WRONG_ROLE) + (asserts! (not (is-done u2)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-odd-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-odd-c c) + (map-set action-done u2 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-even-4 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-even)) ERR_WRONG_ROLE) + (asserts! (not (is-done u3)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u0) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-even-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-even-c c) + (map-set action-done u3 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u3) ERR_NOT_OPEN) + (asserts! (is-eq (+ (unwrap-panic (to-uint (if (and (or (is-done u1) (is-done u3)) (or (is-done u0) (is-done u2))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 126 74) (if (and (not (or (is-done u1) (is-done u3))) (or (is-done u0) (is-done u2))) 20 (if (and (or (is-done u1) (is-done u3)) (not (or (is-done u0) (is-done u2)))) 180 100))))) (unwrap-panic (to-uint (if (and (or (is-done u1) (is-done u3)) (or (is-done u0) (is-done u2))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 74 126) (if (and (not (or (is-done u1) (is-done u3))) (or (is-done u0) (is-done u2))) 180 (if (and (or (is-done u1) (is-done u3)) (not (or (is-done u0) (is-done u2)))) 20 100)))))) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-even)) (unwrap-panic (to-uint (if (and (or (is-done u1) (is-done u3)) (or (is-done u0) (is-done u2))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 126 74) (if (and (not (or (is-done u1) (is-done u3))) (or (is-done u0) (is-done u2))) 20 (if (and (or (is-done u1) (is-done u3)) (not (or (is-done u0) (is-done u2)))) 180 100)))))) + (map-set claims (unwrap-panic (var-get role-odd)) (unwrap-panic (to-uint (if (and (or (is-done u1) (is-done u3)) (or (is-done u0) (is-done u2))) (if (is-eq (var-get var-even-c) (var-get var-odd-c)) 74 126) (if (and (not (or (is-done u1) (is-done u3))) (or (is-done u0) (is-done u2))) 180 (if (and (or (is-done u1) (is-done u3)) (not (or (is-done u0) (is-done u2)))) 20 100)))))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (if (not (is-done u0)) (begin (map-set claims (unwrap-panic (var-get role-even)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u1)) (begin (map-set claims (unwrap-panic (var-get role-odd)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u2)) (begin (map-set claims (unwrap-panic (var-get role-even)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u3)) (begin (map-set claims (unwrap-panic (var-get role-odd)) u200) (var-set payoffs-distributed true) (ok true)) (err ERR_NOT_OPEN))))) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/examples/clarity/Prisoners.clar b/examples/clarity/Prisoners.clar new file mode 100644 index 00000000..11751bfc --- /dev/null +++ b/examples/clarity/Prisoners.clar @@ -0,0 +1,235 @@ +;; Prisoners - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-a (optional principal) none) +(define-data-var deposit-a uint u0) +(define-data-var role-b (optional principal) none) +(define-data-var deposit-b uint u0) +(define-data-var total-pot uint u0) + +(define-data-var commit-a-c (optional (buff 32)) none) +(define-data-var commit-b-c (optional (buff 32)) none) +(define-data-var var-a-c bool false) +(define-data-var var-b-c bool false) + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-a) + (begin + (asserts! (is-none (var-get role-a)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-a u100) + (var-set role-a (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-public (register-b) + (begin + (asserts! (is-none (var-get role-b)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u100 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u100)) + (var-set deposit-b u100) + (var-set role-b (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-a)) (is-some (var-get role-b))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + (map-set action-done u0 true) + (map-set action-done u1 true) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-a) r + (let ((amt (var-get deposit-a))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (match (var-get role-b) r + (let ((amt (var-get deposit-b))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-a none) + (var-set deposit-a u0) + (var-set role-b none) + (var-set deposit-b u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +(define-public (action-a-3 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-a)) ERR_WRONG_ROLE) + (asserts! (not (is-done u2)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (var-set commit-a-c (some c)) + (map-set action-done u2 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-b-5 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-b)) ERR_WRONG_ROLE) + (asserts! (not (is-done u3)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (var-set commit-b-c (some c)) + (map-set action-done u3 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-a-4 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-a)) ERR_WRONG_ROLE) + (asserts! (not (is-done u4)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-a-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-a-c c) + (map-set action-done u4 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-b-6 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-b)) ERR_WRONG_ROLE) + (asserts! (not (is-done u5)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u4) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-b-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-b-c c) + (map-set action-done u5 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u5) ERR_NOT_OPEN) + (asserts! (is-eq (+ (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u4)) (or (is-done u3) (is-done u5))) (if (and (var-get var-a-c) (var-get var-b-c)) 100 (if (and (var-get var-a-c) (not (var-get var-b-c))) 0 (if (and (not (var-get var-a-c)) (var-get var-b-c)) 200 90))) (if (not (or (is-done u2) (is-done u4))) 0 200)))) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u4)) (or (is-done u3) (is-done u5))) (if (and (var-get var-a-c) (var-get var-b-c)) 100 (if (and (var-get var-a-c) (not (var-get var-b-c))) 200 (if (and (not (var-get var-a-c)) (var-get var-b-c)) 0 110))) (if (not (or (is-done u2) (is-done u4))) 200 0))))) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-a)) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u4)) (or (is-done u3) (is-done u5))) (if (and (var-get var-a-c) (var-get var-b-c)) 100 (if (and (var-get var-a-c) (not (var-get var-b-c))) 0 (if (and (not (var-get var-a-c)) (var-get var-b-c)) 200 90))) (if (not (or (is-done u2) (is-done u4))) 0 200))))) + (map-set claims (unwrap-panic (var-get role-b)) (unwrap-panic (to-uint (if (and (or (is-done u2) (is-done u4)) (or (is-done u3) (is-done u5))) (if (and (var-get var-a-c) (var-get var-b-c)) 100 (if (and (var-get var-a-c) (not (var-get var-b-c))) 200 (if (and (not (var-get var-a-c)) (var-get var-b-c)) 0 110))) (if (not (or (is-done u2) (is-done u4))) 200 0))))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (if (not (is-done u2)) (begin (map-set claims (unwrap-panic (var-get role-b)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u3)) (begin (map-set claims (unwrap-panic (var-get role-a)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u4)) (begin (map-set claims (unwrap-panic (var-get role-b)) u200) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u5)) (begin (map-set claims (unwrap-panic (var-get role-a)) u200) (var-set payoffs-distributed true) (ok true)) (err ERR_NOT_OPEN))))) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/examples/clarity/Simple.clar b/examples/clarity/Simple.clar new file mode 100644 index 00000000..df89e9ba --- /dev/null +++ b/examples/clarity/Simple.clar @@ -0,0 +1,210 @@ +;; Simple - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-a (optional principal) none) +(define-data-var deposit-a uint u0) +(define-data-var role-b (optional principal) none) +(define-data-var deposit-b uint u0) +(define-data-var total-pot uint u0) + +(define-data-var commit-a-c (optional (buff 32)) none) +(define-data-var var-b-c bool false) +(define-data-var var-a-c bool false) + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-a) + (begin + (asserts! (is-none (var-get role-a)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u6 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u6)) + (var-set deposit-a u6) + (var-set role-a (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-public (register-b) + (begin + (asserts! (is-none (var-get role-b)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u6 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u6)) + (var-set deposit-b u6) + (var-set role-b (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-a)) (is-some (var-get role-b))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + (map-set action-done u0 true) + (map-set action-done u1 true) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-a) r + (let ((amt (var-get deposit-a))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (match (var-get role-b) r + (let ((amt (var-get deposit-b))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-a none) + (var-set deposit-a u0) + (var-set role-b none) + (var-set deposit-b u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +(define-public (action-a-2 (c (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-a)) ERR_WRONG_ROLE) + (asserts! (not (is-done u2)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u1) ERR_DEPENDENCY_NOT_MET) + (var-set commit-a-c (some c)) + (map-set action-done u2 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-b-3 (c bool) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-b)) ERR_WRONG_ROLE) + (asserts! (not (is-done u3)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (var-set var-b-c c) + (map-set action-done u3 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +(define-public (action-a-4 (c bool) (c-salt (buff 32)) ) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN) + (asserts! (is-eq (some tx-sender) (var-get role-a)) ERR_WRONG_ROLE) + (asserts! (not (is-done u4)) ERR_ACTION_ALREADY_DONE) + (asserts! (is-done u3) ERR_DEPENDENCY_NOT_MET) + (asserts! (is-done u2) ERR_DEPENDENCY_NOT_MET) + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? c)) + c-salt + (unwrap-panic (var-get commit-a-c)) +) ERR_COMMIT_MISMATCH) + (var-set var-a-c c) + (map-set action-done u4 true) + (var-set last-progress (get-time)) + (ok true) + ) +) + +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u4) ERR_NOT_OPEN) + (asserts! (is-eq (+ (unwrap-panic (to-uint (if (and (not (or (is-done u2) (is-done u4))) (not (is-done u3))) 6 (if (not (or (is-done u2) (is-done u4))) 1 (if (not (is-done u3)) 11 (if (not (is-eq (var-get var-a-c) (var-get var-b-c))) 9 3)))))) (unwrap-panic (to-uint (if (and (not (or (is-done u2) (is-done u4))) (not (is-done u3))) 6 (if (not (or (is-done u2) (is-done u4))) 11 (if (not (is-done u3)) 1 (if (is-eq (var-get var-a-c) (var-get var-b-c)) 9 3))))))) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-a)) (unwrap-panic (to-uint (if (and (not (or (is-done u2) (is-done u4))) (not (is-done u3))) 6 (if (not (or (is-done u2) (is-done u4))) 1 (if (not (is-done u3)) 11 (if (not (is-eq (var-get var-a-c) (var-get var-b-c))) 9 3))))))) + (map-set claims (unwrap-panic (var-get role-b)) (unwrap-panic (to-uint (if (and (not (or (is-done u2) (is-done u4))) (not (is-done u3))) 6 (if (not (or (is-done u2) (is-done u4))) 11 (if (not (is-done u3)) 1 (if (is-eq (var-get var-a-c) (var-get var-b-c)) 9 3))))))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (if (not (is-done u2)) (begin (map-set claims (unwrap-panic (var-get role-b)) u12) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u3)) (begin (map-set claims (unwrap-panic (var-get role-a)) u12) (var-set payoffs-distributed true) (ok true)) (if (not (is-done u4)) (begin (map-set claims (unwrap-panic (var-get role-b)) u12) (var-set payoffs-distributed true) (ok true)) (err ERR_NOT_OPEN)))) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/examples/clarity/Trivial1.clar b/examples/clarity/Trivial1.clar new file mode 100644 index 00000000..82fce002 --- /dev/null +++ b/examples/clarity/Trivial1.clar @@ -0,0 +1,131 @@ +;; Trivial1 - Generated by Vegas Clarity Backend + +;; Constants +(define-constant ERR_NOT_INITIALIZED (err u100)) +(define-constant ERR_ALREADY_INITIALIZED (err u101)) +(define-constant ERR_WRONG_ROLE (err u102)) +(define-constant ERR_NOT_OPEN (err u103)) +(define-constant ERR_TIMEOUT_NOT_READY (err u104)) +(define-constant ERR_NOTHING_TO_WITHDRAW (err u105)) +(define-constant ERR_INVALID_PARAM (err u106)) +(define-constant ERR_COMMIT_MISMATCH (err u107)) +(define-constant ERR_ACTION_ALREADY_DONE (err u108)) +(define-constant ERR_DEPENDENCY_NOT_MET (err u109)) +(define-constant ERR_GUARD_FAILED (err u110)) +(define-constant ERR_PAYOUT_TOO_HIGH (err u111)) + +;; Data Variables +(define-data-var initialized bool false) +(define-data-var last-progress uint u0) +(define-data-var first-dep-time uint u0) +(define-data-var payoffs-distributed bool false) + +(define-data-var role-a (optional principal) none) +(define-data-var deposit-a uint u0) +(define-data-var total-pot uint u0) + + +;; Maps +(define-map action-done uint bool) +(define-map claims principal uint) + +;; Helpers +(define-read-only (get-time) + stacks-block-time +) + +(define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) +) +(define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) +) +(define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) +) +(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender))) + +;; Registration +(define-public (register-a) + (begin + (asserts! (is-none (var-get role-a)) ERR_ALREADY_INITIALIZED) + (try! (stx-transfer? u10 tx-sender (get-contract-principal))) + (var-set total-pot (+ (var-get total-pot) u10)) + (var-set deposit-a u10) + (var-set role-a (some tx-sender)) + (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true) + (check-initialization) + (ok true) + ) +) + +(define-private (check-initialization) + (if (and (is-some (var-get role-a))) + (begin + (var-set initialized true) + (var-set last-progress (get-time)) + (map-set action-done u0 true) + ) + true + ) +) + +;; Cancel +(define-public (cancel-uninitialized) + (begin + (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED) + (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN) + (asserts! (>= (get-time) (+ (var-get first-dep-time) u100)) ERR_TIMEOUT_NOT_READY) + (match (var-get role-a) r + (let ((amt (var-get deposit-a))) + (if (> amt u0) + (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r)))) + true + ) + ) + true + ) + (var-set total-pot u0) + (var-set role-a none) + (var-set deposit-a u0) + (var-set first-dep-time u0) + (ok true) + ) +) + +;; Actions +;; Finalize +(define-public (finalize) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (is-done u0) ERR_NOT_OPEN) + (asserts! (is-eq (unwrap-panic (to-uint 10)) (var-get total-pot)) ERR_PAYOUT_TOO_HIGH) + (map-set claims (unwrap-panic (var-get role-a)) (unwrap-panic (to-uint 10))) + (var-set payoffs-distributed true) + (ok true) + ) +) + +;; Timeout +(define-public (timeout) + (begin + (asserts! (var-get initialized) ERR_NOT_INITIALIZED) + (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED) + (asserts! (check-timeout u100) ERR_TIMEOUT_NOT_READY) + (err ERR_NOT_OPEN) + ) +) + +;; Withdraw +(define-public (withdraw) + (let ( + (recipient tx-sender) + (amt (default-to u0 (map-get? claims recipient))) + ) + (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW) + (map-set claims recipient u0) + (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient)))) + (ok amt) + ) +) diff --git a/src/main/kotlin/vegas/backend/clarity/ClarityBackend.kt b/src/main/kotlin/vegas/backend/clarity/ClarityBackend.kt new file mode 100644 index 00000000..edaa7ce6 --- /dev/null +++ b/src/main/kotlin/vegas/backend/clarity/ClarityBackend.kt @@ -0,0 +1,448 @@ +package vegas.backend.clarity + +import vegas.RoleId +import vegas.FieldRef +import vegas.VarId +import vegas.backend.clarity.ClarityCompiler +import vegas.ir.* + +data class ClarityOptions( + val clarityVersion: Int = 4, + val defaultTimeout: Long = 100 +) + +class ClarityBackend(val game: GameIR, val options: ClarityOptions = ClarityOptions()) { + private val protocol = ClarityCompiler.compile(game, options.defaultTimeout) + private val sb = StringBuilder() + + // Linearly sorted actions (enforcing sequential execution) + private val sortedActions = protocol.actions + + private val actionIds: Map = sortedActions + .mapIndexed { index, action -> action.id to index } + .toMap() + + private val fieldWriters: Map> by lazy { + val map = mutableMapOf>() + sortedActions.forEach { action -> + val aid = actionIds[action.id]!! + game.dag.writes(action.id).forEach { field -> + map.getOrPut(field) { mutableListOf() }.add(aid) + } + } + map + } + + fun generate(): String { + sb.appendLine(";; ${game.name} - Generated by Vegas Clarity Backend") + sb.appendLine() + + generateConstants() + generateDataVars() + generateMaps() + generateHelpers() + generateRegistration() + generateCancel() + generateActions() + generateFinalize() + generateTimeout() + generateWithdraw() + + return sb.toString() + } + + private fun generateConstants() { + sb.appendLine(";; Constants") + sb.appendLine("(define-constant ERR_NOT_INITIALIZED (err u100))") + sb.appendLine("(define-constant ERR_ALREADY_INITIALIZED (err u101))") + sb.appendLine("(define-constant ERR_WRONG_ROLE (err u102))") + sb.appendLine("(define-constant ERR_NOT_OPEN (err u103))") + sb.appendLine("(define-constant ERR_TIMEOUT_NOT_READY (err u104))") + sb.appendLine("(define-constant ERR_NOTHING_TO_WITHDRAW (err u105))") + sb.appendLine("(define-constant ERR_INVALID_PARAM (err u106))") + sb.appendLine("(define-constant ERR_COMMIT_MISMATCH (err u107))") + sb.appendLine("(define-constant ERR_ACTION_ALREADY_DONE (err u108))") + sb.appendLine("(define-constant ERR_DEPENDENCY_NOT_MET (err u109))") + sb.appendLine("(define-constant ERR_GUARD_FAILED (err u110))") + sb.appendLine("(define-constant ERR_PAYOUT_TOO_HIGH (err u111))") + sb.appendLine() + } + + private fun generateDataVars() { + sb.appendLine(";; Data Variables") + sb.appendLine("(define-data-var initialized bool false)") + sb.appendLine("(define-data-var last-progress uint u0)") + sb.appendLine("(define-data-var first-dep-time uint u0)") + sb.appendLine("(define-data-var payoffs-distributed bool false)") + sb.appendLine() + + protocol.roles.forEach { role -> + sb.appendLine("(define-data-var role-${kebab(role.name)} (optional principal) none)") + sb.appendLine("(define-data-var deposit-${kebab(role.name)} uint u0)") + } + sb.appendLine("(define-data-var total-pot uint u0)") + sb.appendLine() + + val declaredVars = mutableSetOf() + sortedActions.flatMap { it.writes }.forEach { write -> + val varName = if (write.isCommit) "commit-${kebab(write.name)}" else "var-${kebab(write.name)}" + if (varName !in declaredVars) { + declaredVars.add(varName) + val type = if (write.isCommit) "(optional (buff 32))" else toClarityType(write.type) + val init = if (write.isCommit) "none" else defaultInit(write.type) + sb.appendLine("(define-data-var $varName $type $init)") + } + } + sb.appendLine() + } + + private fun generateMaps() { + sb.appendLine(";; Maps") + sb.appendLine("(define-map action-done uint bool)") + sb.appendLine("(define-map claims principal uint)") + sb.appendLine() + } + + private fun generateHelpers() { + sb.appendLine(";; Helpers") + sb.appendLine("(define-read-only (get-time)") + if (options.clarityVersion >= 4) { + sb.appendLine(" stacks-block-time") + } else { + sb.appendLine(" stacks-block-height") + } + sb.appendLine(")") + sb.appendLine() + + sb.appendLine(""" + (define-private (check-timeout (delta uint)) + (>= (get-time) (+ (var-get last-progress) delta)) + ) + """.trimIndent()) + + sb.appendLine(""" + (define-private (verify-commit (val (buff 128)) (salt (buff 32)) (comm (buff 32))) + (is-eq (sha256 (concat val salt)) comm) + ) + """.trimIndent()) + + sb.appendLine(""" + (define-private (is-done (id uint)) + (default-to false (map-get? action-done id)) + ) + """.trimIndent()) + + if (options.clarityVersion >= 4) { + sb.appendLine("(define-private (get-contract-principal) (unwrap-panic (as-contract? () tx-sender)))") + } else { + sb.appendLine("(define-private (get-contract-principal) (as-contract tx-sender))") + } + sb.appendLine() + } + + private fun generateRegistration() { + sb.appendLine(";; Registration") + protocol.roles.forEach { role -> + val roleName = kebab(role.name) + val joinDeposit = game.dag.deposit(role) + val depositAmount = joinDeposit?.asInt() ?: 0 + + sb.appendLine("(define-public (register-$roleName)") + sb.appendLine(" (begin") + sb.appendLine(" (asserts! (is-none (var-get role-$roleName)) ERR_ALREADY_INITIALIZED)") + + if (depositAmount > 0) { + // Use helper to get contract principal + sb.appendLine(" (try! (stx-transfer? u$depositAmount tx-sender (get-contract-principal)))") + sb.appendLine(" (var-set total-pot (+ (var-get total-pot) u$depositAmount))") + sb.appendLine(" (var-set deposit-$roleName u$depositAmount)") + } + + sb.appendLine(" (var-set role-$roleName (some tx-sender))") + sb.appendLine(" (if (is-eq (var-get first-dep-time) u0) (var-set first-dep-time (get-time)) true)") + sb.appendLine(" (check-initialization)") + sb.appendLine(" (ok true)") + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + + sb.appendLine("(define-private (check-initialization)") + sb.append(" (if (and") + protocol.roles.forEach { role -> + sb.append(" (is-some (var-get role-${kebab(role.name)}))") + } + sb.append(")") + sb.appendLine() + sb.appendLine(" (begin") + sb.appendLine(" (var-set initialized true)") + sb.appendLine(" (var-set last-progress (get-time))") + protocol.initialDone.forEach { actionId -> + val uintId = actionIds[actionId]!! + sb.appendLine(" (map-set action-done u$uintId true)") + } + sb.appendLine(" )") + sb.appendLine(" true") + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + + private fun generateCancel() { + sb.appendLine(";; Cancel") + sb.appendLine("(define-public (cancel-uninitialized)") + sb.appendLine(" (begin") + sb.appendLine(" (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED)") + sb.appendLine(" (asserts! (> (var-get first-dep-time) u0) ERR_NOT_OPEN)") + sb.appendLine(" (asserts! (>= (get-time) (+ (var-get first-dep-time) u${options.defaultTimeout})) ERR_TIMEOUT_NOT_READY)") + + protocol.roles.forEach { role -> + val roleVar = "role-${kebab(role.name)}" + val depVar = "deposit-${kebab(role.name)}" + + sb.appendLine(" (match (var-get $roleVar) r") + sb.appendLine(" (let ((amt (var-get $depVar)))") + sb.appendLine(" (if (> amt u0)") + if (options.clarityVersion >= 4) { + // Nested unwrap for Clarity 4: (as-contract? (stx-transfer?)) returns (response (response bool uint) uint) + // We need 'bool' result for the match branch. + sb.appendLine(" (unwrap-panic (as-contract? ((with-stx amt)) (unwrap-panic (stx-transfer? amt tx-sender r))))") + } else { + sb.appendLine(" (unwrap-panic (as-contract (stx-transfer? amt tx-sender r)))") + } + sb.appendLine(" true") + sb.appendLine(" )") + sb.appendLine(" )") + sb.appendLine(" true") + sb.appendLine(" )") + } + + sb.appendLine(" (var-set total-pot u0)") + protocol.roles.forEach { role -> + sb.appendLine(" (var-set role-${kebab(role.name)} none)") + sb.appendLine(" (var-set deposit-${kebab(role.name)} u0)") + } + sb.appendLine(" (var-set first-dep-time u0)") + sb.appendLine(" (ok true)") + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + + private fun generateActions() { + sb.appendLine(";; Actions") + + sortedActions.forEachIndexed { index, action -> + if (action.id in protocol.initialDone) return@forEachIndexed + + val actionIdUint = index + val actionName = "action-${kebab(action.owner.name)}-${action.id.second}" + + val funcParams = mutableListOf() + val paramChecks = mutableListOf() + val updates = mutableListOf() + + val localParams = mutableMapOf() + + action.params.forEach { param -> + val type = toClarityType(param.type) + localParams[FieldRef(action.owner, VarId(param.name))] = param.name + if (param.isSalt) { + funcParams.add("(${param.name} $type)") + funcParams.add("(${param.name}-salt (buff 32))") + } else { + if (action.type is ActionType.Commit) { + funcParams.add("(${param.name} (buff 32))") + } else { + funcParams.add("(${param.name} $type)") + } + } + } + + if (action.type is ActionType.Commit) { + action.writes.forEachIndexed { i, write -> + val paramName = action.params[i].name + updates.add("(var-set commit-${kebab(write.name)} (some $paramName))") + } + } else if (action.type is ActionType.Reveal) { + action.writes.forEachIndexed { i, write -> + val paramName = action.params[i].name + val saltName = "$paramName-salt" + paramChecks.add(""" + (asserts! (verify-commit + (unwrap-panic (to-consensus-buff? $paramName)) + $saltName + (unwrap-panic (var-get commit-${kebab(write.name)})) + ) ERR_COMMIT_MISMATCH) + """.trimIndent()) + updates.add("(var-set var-${kebab(write.name)} $paramName)") + } + } else { + action.writes.forEachIndexed { i, write -> + val paramName = action.params[i].name + updates.add("(var-set var-${kebab(write.name)} $paramName)") + } + } + + action.params.forEach { p -> + if (p.type is Type.SetType && !(action.type is ActionType.Commit)) { + val setType = p.type as Type.SetType + val checks = setType.values.joinToString(" ") { "(is-eq ${p.name} $it)" } + paramChecks.add("(asserts! (or $checks) ERR_INVALID_PARAM)") + } + } + + sb.appendLine("(define-public ($actionName ${funcParams.joinToString(" ")} )") + sb.appendLine(" (begin") + sb.appendLine(" (asserts! (var-get initialized) ERR_NOT_INITIALIZED)") + sb.appendLine(" (asserts! (not (var-get payoffs-distributed)) ERR_NOT_OPEN)") + sb.appendLine(" (asserts! (is-eq (some tx-sender) (var-get role-${kebab(action.owner.name)})) ERR_WRONG_ROLE)") + + sb.appendLine(" (asserts! (not (is-done u$actionIdUint)) ERR_ACTION_ALREADY_DONE)") + + if (index > 0) { + sb.appendLine(" (asserts! (is-done u${index - 1}) ERR_DEPENDENCY_NOT_MET)") + } + + action.prereqs.forEach { prereq -> + val pid = actionIds[prereq]!! + if (pid != index - 1) { + sb.appendLine(" (asserts! (is-done u$pid) ERR_DEPENDENCY_NOT_MET)") + } + } + + if (action.guard != null) { + sb.appendLine(" (asserts! ${translateExpr(action.guard, ::getFieldWriters, localParams)} ERR_GUARD_FAILED)") + } + + paramChecks.forEach { sb.appendLine(" $it") } + updates.forEach { sb.appendLine(" $it") } + + sb.appendLine(" (map-set action-done u$actionIdUint true)") + sb.appendLine(" (var-set last-progress (get-time))") + sb.appendLine(" (ok true)") + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + } + + private fun generateFinalize() { + sb.appendLine(";; Finalize") + sb.appendLine("(define-public (finalize)") + sb.appendLine(" (begin") + sb.appendLine(" (asserts! (var-get initialized) ERR_NOT_INITIALIZED)") + sb.appendLine(" (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED)") + + val lastActionId = sortedActions.size - 1 + sb.appendLine(" (asserts! (is-done u$lastActionId) ERR_NOT_OPEN)") + + val payouts = protocol.payoffs.mapValues { (_, expr) -> + val intExpr = translateExpr(expr, ::getFieldWriters) + "(unwrap-panic (to-uint $intExpr))" + } + + if (payouts.isNotEmpty()) { + val sumExpr = payouts.values.reduce { acc, s -> "(+ $acc $s)" } + sb.appendLine(" (asserts! (is-eq $sumExpr (var-get total-pot)) ERR_PAYOUT_TOO_HIGH)") + } + + payouts.forEach { (role, exprStr) -> + sb.appendLine(" (map-set claims (unwrap-panic (var-get role-${kebab(role.name)})) $exprStr)") + } + + sb.appendLine(" (var-set payoffs-distributed true)") + sb.appendLine(" (ok true)") + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + + private fun generateTimeout() { + sb.appendLine(";; Timeout") + sb.appendLine("(define-public (timeout)") + sb.appendLine(" (begin") + sb.appendLine(" (asserts! (var-get initialized) ERR_NOT_INITIALIZED)") + sb.appendLine(" (asserts! (not (var-get payoffs-distributed)) ERR_ALREADY_INITIALIZED)") + sb.appendLine(" (asserts! (check-timeout u${options.defaultTimeout}) ERR_TIMEOUT_NOT_READY)") + + var expr = "(err ERR_NOT_OPEN)" + + val reversedActions = sortedActions.reversed() + + for (action in reversedActions) { + val aid = actionIds[action.id]!! + val prevSet = sortedActions.take(aid).map { it.id }.toSet() + val payoff = protocol.abortPayoffs[prevSet] + + if (payoff != null) { + val payBlock = StringBuilder() + payBlock.append("(begin ") + payoff.forEach { (role, amt) -> + if (amt > 0) { + payBlock.append("(map-set claims (unwrap-panic (var-get role-${kebab(role.name)})) u$amt) ") + } + } + payBlock.append("(var-set payoffs-distributed true) (ok true))") + + expr = "(if (not (is-done u$aid)) $payBlock $expr)" + } + } + sb.appendLine(" $expr") + + sb.appendLine(" )") + sb.appendLine(")") + sb.appendLine() + } + + private fun generateWithdraw() { + sb.appendLine(";; Withdraw") + sb.appendLine("(define-public (withdraw)") + sb.appendLine(" (let (") + sb.appendLine(" (recipient tx-sender)") + sb.appendLine(" (amt (default-to u0 (map-get? claims recipient)))") + sb.appendLine(" )") + sb.appendLine(" (asserts! (> amt u0) ERR_NOTHING_TO_WITHDRAW)") + sb.appendLine(" (map-set claims recipient u0)") + + if (options.clarityVersion >= 4) { + sb.appendLine(" (try! (as-contract? ((with-stx amt)) (try! (stx-transfer? amt tx-sender recipient))))") + } else { + sb.appendLine(" (try! (as-contract (stx-transfer? amt tx-sender recipient)))") + } + + sb.appendLine(" (ok amt)") + sb.appendLine(" )") + sb.appendLine(")") + } + + // Utils + private fun getFieldWriters(f: FieldRef): List { + return fieldWriters[f] ?: emptyList() + } + + private fun kebab(s: String): String = s.fold(StringBuilder()) { acc, c -> + if (c.isUpperCase()) { + if (acc.isNotEmpty()) acc.append('-') + acc.append(c.lowercaseChar()) + } else { + acc.append(c) + } + }.toString() + + private fun defaultInit(t: Type): String = when(t) { + Type.IntType -> "0" + Type.BoolType -> "false" + is Type.SetType -> "${t.values.first()}" + } + + private fun toClarityType(t: Type): String = when(t) { + Type.IntType -> "int" + Type.BoolType -> "bool" + is Type.SetType -> "int" + } +} + +fun genClarity(game: GameIR, opt: ClarityOptions = ClarityOptions()): String { + return ClarityBackend(game, opt).generate() +} diff --git a/src/main/kotlin/vegas/backend/clarity/ClarityCompiler.kt b/src/main/kotlin/vegas/backend/clarity/ClarityCompiler.kt new file mode 100644 index 00000000..10fe0ccf --- /dev/null +++ b/src/main/kotlin/vegas/backend/clarity/ClarityCompiler.kt @@ -0,0 +1,264 @@ +package vegas.backend.clarity + +import java.util.ArrayDeque +import vegas.RoleId +import vegas.ir.GameIR +import vegas.ir.ActionId +import vegas.ir.Type +import vegas.ir.Visibility +import vegas.ir.asInt +import vegas.semantics.* + +class ClarityCompilationException(message: String) : Exception(message) + +internal object ClarityCompiler { + + fun compile(game: GameIR, defaultTimeout: Long): ClarityGame { + // 1. Roles + val rolesSorted = game.roles.sortedBy { it.name } + val potAmount = computePot(game, game.roles) + + // 2. Build Actions + val rawActions = game.dag.actions.sortedBy { it.second }.map { actionId -> + buildAction(game, actionId) + } + + // Sort Topologically + val actions = topologicalSort(game, rawActions) + + // 3. Explore State Space (Linear Sequence) + val (abortPayoffs, terminalFrontiers, initialDone) = exploreLinearStateSpace(game, rolesSorted, potAmount, actions) + + return ClarityGame( + name = game.name, + roles = rolesSorted, + pot = potAmount, + actions = actions, + abortPayoffs = abortPayoffs, + initialDone = initialDone, + terminalFrontiers = terminalFrontiers, + payoffs = game.payoffs + ) + } + + private fun topologicalSort(game: GameIR, actions: List): List { + val result = mutableListOf() + val visited = mutableSetOf() + val temp = mutableSetOf() + val actionMap = actions.associateBy { it.id } + + fun visit(id: ActionId) { + if (id in visited) return + if (id in temp) throw ClarityCompilationException("Cycle detected in ActionDAG") + temp.add(id) + + game.dag.prerequisitesOf(id).forEach { dep -> + if (dep in actionMap) visit(dep) + } + + temp.remove(id) + visited.add(id) + result.add(actionMap[id]!!) + } + + // Sort by ID to ensure deterministic order among independent nodes + actions.sortedBy { it.id.second }.forEach { action -> + visit(action.id) + } + + return result + } + + private fun buildAction(game: GameIR, actionId: ActionId): ClarityAction { + val owner = game.dag.owner(actionId) + val spec = game.dag.spec(actionId) + val kind = game.dag.kind(actionId) + val visMap = game.dag.visibilityOf(actionId) + + val params = spec.params.map { param -> + val fieldRef = vegas.FieldRef(owner, param.name) + val vis = visMap[fieldRef]!! + ClarityParam( + name = param.name.name, + type = param.type, + isSalt = (vis == Visibility.REVEAL) + ) + } + + val writes = spec.params.map { param -> + val fieldRef = vegas.FieldRef(owner, param.name) + val vis = visMap[fieldRef]!! + ClarityStateVar( + name = "${owner.name}-${param.name.name}", + type = param.type, + isCommit = (vis == Visibility.COMMIT) + ) + } + + val type = when (kind) { + Visibility.COMMIT -> ActionType.Commit + Visibility.REVEAL -> ActionType.Reveal("") + Visibility.PUBLIC -> ActionType.Public + } + + val guard = if (spec.guardExpr is vegas.ir.Expr.Const.BoolVal && spec.guardExpr.v) null else spec.guardExpr + + return ClarityAction( + id = actionId, + owner = owner, + prereqs = game.dag.prerequisitesOf(actionId), + params = params, + type = type, + writes = writes, + guard = guard + ) + } + + private fun exploreLinearStateSpace( + game: GameIR, + roles: List, + pot: Long, + sortedActions: List + ): Triple, Map>, List>, Set> { + val semantics = GameSemantics(game) + + val frontierPayoffs = mutableMapOf, Map>() + val terminalFrontiers = mutableListOf>() + + val initial = Configuration.initial(game) + val (canonInitial, initialDone) = canonicalize(semantics, initial, roles) + + var currentConfig = canonInitial + var currentDone = initialDone + + // Loop through actions + for (action in sortedActions) { + if (action.id in currentDone) continue + + val moves = semantics.enabledMoves(currentConfig) + val move = moves.filterIsInstance() + .find { (it.tag as? PlayTag.Action)?.actionId == action.id } + + if (move == null) break + + val abortPayoff = resolveAbortScenario(semantics, game, currentConfig, currentDone, action.owner, roles, pot) + frontierPayoffs[currentDone] = abortPayoff + + val next = applyMove(currentConfig, move) + val (canonNext, canonDone) = canonicalize(semantics, next, roles) + + currentConfig = canonNext + currentDone = currentDone + action.id + canonDone + } + + if (currentConfig.isTerminal()) { + terminalFrontiers.add(currentDone) + } + + return Triple(frontierPayoffs, terminalFrontiers, initialDone) + } + + private fun computePot(game: GameIR, players: Set): Long { + val dag = game.dag + var pot = 0L + for (actionId in dag.actions) { + val owner = dag.owner(actionId) + if (owner !in players) continue + val join = dag.spec(actionId).join + if (join != null) { + val deposit = join.deposit.v + if (deposit < 0) error("Negative deposit") + pot += deposit.toLong() + } + } + return pot + } + + private fun canonicalize( + semantics: GameSemantics, start: Configuration, players: List + ): Pair> { + var current = start + val done = mutableSetOf() + + while (true) { + if (current.isTerminal()) return current to done + val moves = semantics.enabledMoves(current) + val hasStrategic = moves.any { + it is Label.Play && it.tag != PlayTag.Quit && it.role in players + } + if (hasStrategic) return current to done + val hasQuit = moves.any { + it is Label.Play && it.tag == PlayTag.Quit && it.role in players + } + if (hasQuit) return current to done + + val systemMoves = moves + if (systemMoves.isEmpty()) return current to done + + val finalize = systemMoves.find { it is Label.FinalizeFrontier } + if (finalize != null) { + done.addAll(current.enabled()) + current = applyMove(current, finalize) + } else { + return current to done + } + } + } + + private fun resolvePayoff( + game: GameIR, config: Configuration, roles: List, pot: Long + ): Map { + val evalContext = { ref: vegas.FieldRef -> config.history.get(ref) } + val payoffs = mutableMapOf() + var sum = 0L + for (role in roles) { + val expr = game.payoffs[role] + val amount = if (expr != null) eval(evalContext, expr).asInt().toLong() else 0L + payoffs[role] = amount + sum += amount + } + return payoffs + } + + private fun resolveAbortScenario( + semantics: GameSemantics, + game: GameIR, + config: Configuration, + doneActions: Set, + quitter: RoleId, + players: List, + pot: Long + ): Map { + val allFields = game.dag.actions + .filter { game.dag.owner(it) == quitter && it !in doneActions } + .flatMap { game.dag.writes(it) } + .toSet() + + val quitDelta = allFields.associateWith { vegas.ir.Expr.Const.Quit } + val quitLabel = Label.Play(quitter, quitDelta, PlayTag.Quit) + + var current = applyMove(config, quitLabel) + current = canonicalize(semantics, current, players).first + + val calculatedPayoffs = resolvePayoff(game, current, players, pot) + + // Strict Quit Policy: Quitter forfeits everything to the remaining players. + if (players.size > 1 && (calculatedPayoffs[quitter] ?: 0L) > 0L) { + val quitterAmt = calculatedPayoffs[quitter]!! + val others = players - quitter + val bonus = quitterAmt / others.size + val remainder = quitterAmt % others.size + + val newPayoffs = calculatedPayoffs.toMutableMap() + newPayoffs[quitter] = 0L + + others.forEachIndexed { idx, role -> + val extra = if (idx == 0) remainder else 0L + newPayoffs[role] = (newPayoffs[role] ?: 0L) + bonus + extra + } + return newPayoffs + } + + return calculatedPayoffs + } +} diff --git a/src/main/kotlin/vegas/backend/clarity/ClarityExpr.kt b/src/main/kotlin/vegas/backend/clarity/ClarityExpr.kt new file mode 100644 index 00000000..8e6ef217 --- /dev/null +++ b/src/main/kotlin/vegas/backend/clarity/ClarityExpr.kt @@ -0,0 +1,56 @@ +package vegas.backend.clarity + +import vegas.FieldRef +import vegas.ir.Expr +import vegas.ir.Expr.* + +internal fun translateExpr(e: Expr, fieldToActionIds: (FieldRef) -> List, localParams: Map = emptyMap()): String = when (e) { + // Literals + is Const.IntVal -> "${e.v}" // Clarity int is signed 128 + is Const.BoolVal -> "${e.v}" + is Const.Hidden -> error("Hidden value in runtime expression") + Const.Opaque -> error("Opaque value in runtime expression") + Const.Quit -> error("Quit value in runtime expression") + + // Field Access + is Field -> localParams[e.field] ?: "(var-get var-${kebab(e.field.owner.name)}-${kebab(e.field.param.name)})" + + // Meta-ops + is IsDefined -> { + val ids = fieldToActionIds(e.field) + if (ids.isEmpty()) "false" + else if (ids.size == 1) "(is-done u${ids[0]})" + else "(or ${ids.joinToString(" ") { "(is-done u$it)" }})" + } + + // Arithmetic + is Add -> "(+ ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Sub -> "(- ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Mul -> "(* ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Div -> "(/ ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Mod -> "(mod ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Neg -> "(- 0 ${translateExpr(e.x, fieldToActionIds, localParams)})" // 0 - x for negation + + // Comparisons + is Eq -> "(is-eq ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Ne -> "(not (is-eq ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)}))" + is Lt -> "(< ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Le -> "(<= ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Gt -> "(> ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Ge -> "(>= ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + + // Boolean + is And -> "(and ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Or -> "(or ${translateExpr(e.l, fieldToActionIds, localParams)} ${translateExpr(e.r, fieldToActionIds, localParams)})" + is Not -> "(not ${translateExpr(e.x, fieldToActionIds, localParams)})" + is Ite -> "(if ${translateExpr(e.c, fieldToActionIds, localParams)} ${translateExpr(e.t, fieldToActionIds, localParams)} ${translateExpr(e.e, fieldToActionIds, localParams)})" +} + +private fun kebab(s: String): String = s.fold(StringBuilder()) { acc, c -> + if (c.isUpperCase()) { + if (acc.isNotEmpty()) acc.append('-') + acc.append(c.lowercaseChar()) + } else { + acc.append(c) + } +}.toString() diff --git a/src/main/kotlin/vegas/backend/clarity/ClarityIR.kt b/src/main/kotlin/vegas/backend/clarity/ClarityIR.kt new file mode 100644 index 00000000..bb18e051 --- /dev/null +++ b/src/main/kotlin/vegas/backend/clarity/ClarityIR.kt @@ -0,0 +1,52 @@ +package vegas.backend.clarity + +import vegas.RoleId +import vegas.ir.ActionId +import vegas.ir.Expr +import vegas.ir.Type + +/** + * Clean IR for Clarity backend. + * Represents the game as a set of Actions (dependencies) + Timeout Rules. + */ +internal data class ClarityGame( + val name: String, + val roles: List, + val pot: Long, + val actions: List, + val abortPayoffs: Map, Map>, // DoneSet -> Payoff + val initialDone: Set, + val terminalFrontiers: List>, + val payoffs: Map +) + +internal data class ClarityAction( + val id: ActionId, + val owner: RoleId, + val prereqs: Set, + val params: List, + // If commit: we store the hash. + // If reveal: we verify hash and store value. + // If public: we store value. + val type: ActionType, + val writes: List, + val guard: Expr? +) + +internal sealed class ActionType { + object Commit : ActionType() + data class Reveal(val commitVar: String) : ActionType() // References the variable holding the commit + object Public : ActionType() +} + +internal data class ClarityParam( + val name: String, + val type: Type, + val isSalt: Boolean = false // If true, this is the salt for a reveal +) + +internal data class ClarityStateVar( + val name: String, + val type: Type, // If Commit, this is Buff 32 (implicit) + val isCommit: Boolean +) diff --git a/src/main/kotlin/vegas/semantics/Eval.kt b/src/main/kotlin/vegas/semantics/Eval.kt index 0c39a4bc..5627b96d 100644 --- a/src/main/kotlin/vegas/semantics/Eval.kt +++ b/src/main/kotlin/vegas/semantics/Eval.kt @@ -23,7 +23,7 @@ internal fun eval(readField: (FieldRef)-> Expr.Const, e: Expr): Expr.Const { is Expr.Field -> readField(x.field) is Expr.IsDefined -> { val v = readField(x.field) - BoolVal(v !is Quit && v !is Hidden && v !is Opaque) + BoolVal(v !is Quit) } // arithmetic diff --git a/src/test/kotlin/vegas/GoldenMasterTest.kt b/src/test/kotlin/vegas/GoldenMasterTest.kt index b05baefc..40914358 100644 --- a/src/test/kotlin/vegas/GoldenMasterTest.kt +++ b/src/test/kotlin/vegas/GoldenMasterTest.kt @@ -12,6 +12,8 @@ import vegas.backend.smt.generateSMT import vegas.backend.bitcoin.generateLightningProtocol import vegas.backend.scribble.genScribbleFromIR import vegas.backend.bitcoin.CompilationException +import vegas.backend.clarity.genClarity +import vegas.backend.clarity.ClarityCompilationException import vegas.frontend.compileToIR import vegas.frontend.parseFile import vegas.frontend.GameAst @@ -37,19 +39,19 @@ data class TestCase( class GoldenMasterTest : FreeSpec({ val exampleFiles = listOf( - Example("Bet", disableBackend = setOf("lightning")), // Not 2-player (has random role) + Example("Bet", disableBackend = setOf("lightning", "clarity")), // Not 2-player (has random role) Example("MontyHall", disableBackend = setOf()), - Example("MontyHallChance", disableBackend = setOf("lightning")), // Has randomness + utility semantics + Example("MontyHallChance", disableBackend = setOf("lightning", "clarity")), // Has randomness + utility semantics Example("OddsEvens", disableBackend = setOf()), Example("OddsEvensShort", disableBackend = setOf()), Example("Prisoners", disableBackend = setOf()), Example("Simple", disableBackend = setOf()), Example("Trivial1", disableBackend = setOf("lightning")), // Not 2-player (only 1 player) - Example("Puzzle", disableBackend = setOf("gambit", "lightning")), // Unbounded int - Example("ThreeWayLottery", disableBackend = setOf("lightning")), // Not 2-player (3 players) - Example("ThreeWayLotteryBuggy", disableBackend = setOf("lightning")), // Not 2-player (3 players) - Example("ThreeWayLotteryShort", disableBackend = setOf("lightning")), // Not 2-player (3 players) - Example("TicTacToe", disableBackend = setOf("gambit", "lightning")), // Complex game + Example("Puzzle", disableBackend = setOf("gambit", "lightning", "clarity")), // Unbounded int + Example("ThreeWayLottery", disableBackend = setOf("lightning", "clarity")), // Not 2-player (3 players) + Example("ThreeWayLotteryBuggy", disableBackend = setOf("lightning", "clarity")), // Not 2-player (3 players) + Example("ThreeWayLotteryShort", disableBackend = setOf("lightning", "clarity")), // Not 2-player (3 players) + Example("TicTacToe", disableBackend = setOf("gambit", "lightning", "clarity")), // Complex game ) val testCases = exampleFiles.flatMap { example -> @@ -76,6 +78,9 @@ class GoldenMasterTest : FreeSpec({ TestCase(example, "scr", "scribble") { prog -> val ir = compileToIR(prog) genScribbleFromIR(ir) + }, + TestCase(example, "clar", "clarity") { prog -> + genClarity(compileToIR(prog)) } ).filter { t -> t.backend !in example.disableBackend } } @@ -122,6 +127,13 @@ class GoldenMasterTest : FreeSpec({ } else { throw e } + } catch (e: ClarityCompilationException) { + // Clarity backend compilation failure - check if expected + if (testCase.backend == "clarity" && testCase.example.name in testCase.example.disableBackend) { + println("Skipped (Clarity): ${testCase.example.name} - ${e.message}") + } else { + throw e + } } } } diff --git a/src/test/kotlin/vegas/backend/clarity/ClarityBackendTest.kt b/src/test/kotlin/vegas/backend/clarity/ClarityBackendTest.kt new file mode 100644 index 00000000..705bcaf9 --- /dev/null +++ b/src/test/kotlin/vegas/backend/clarity/ClarityBackendTest.kt @@ -0,0 +1,61 @@ +package vegas.backend.clarity + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* +import vegas.frontend.parseCode +import vegas.frontend.compileToIR +import java.io.File + +class ClarityBackendTest { + + @Test + fun testOddsEvensGeneration() { + val code = """ + join Odd() ${'$'} 100 Even() ${'$'} 100; + yield Odd(c: bool) Even(c: bool); + withdraw (Even.c != null && Odd.c != null) ? + { Even -> ((Even.c <-> Odd.c) ? 126 : 74); Odd -> ((Even.c <-> Odd.c) ? 74 : 126) } + : (Even.c == null && Odd.c != null) ? { Even -> 20; Odd -> 180 } + : (Even.c != null && Odd.c == null) ? { Even -> 180; Odd -> 20 } + : { Even -> 100; Odd -> 100 } + """.trimIndent() + + val ast = parseCode(code) + val ir = compileToIR(ast) + val clarityCode = genClarity(ir) + + println(clarityCode) + + // Basic structural checks + assertTrue(clarityCode.contains("(define-data-var total-pot uint u0)")) + assertTrue(clarityCode.contains("(define-data-var role-odd (optional principal) none)")) + assertTrue(clarityCode.contains("(define-data-var role-even (optional principal) none)")) + + // Check for commit/reveal expansion + // Should have commit vars + assertTrue(clarityCode.contains("(define-data-var commit-odd-c (optional (buff 32)) none)")) + assertTrue(clarityCode.contains("(define-data-var commit-even-c (optional (buff 32)) none)")) + + // Check for reveal vars (values) + assertTrue(clarityCode.contains("(define-data-var var-odd-c bool false)")) + assertTrue(clarityCode.contains("(define-data-var var-even-c bool false)")) + + // Check for map + assertTrue(clarityCode.contains("(define-map action-done uint bool)")) + assertFalse(clarityCode.contains("(define-data-var state uint")) + + // Check for registration functions + assertTrue(clarityCode.contains("(define-public (register-odd")) + assertTrue(clarityCode.contains("(define-public (register-even")) + + // Check for timeout + assertTrue(clarityCode.contains("(define-public (timeout)")) + + // Check for finalize + assertTrue(clarityCode.contains("(define-public (finalize)")) + assertTrue(clarityCode.contains("ERR_PAYOUT_TOO_HIGH")) + + // Check for withdraw + assertTrue(clarityCode.contains("(define-public (withdraw)")) + } +}