diff --git a/Clarinet.toml b/Clarinet.toml index f40f2a7..99122d0 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -8,6 +8,7 @@ cache_dir = "./.cache" [[project.requirements]] contract_id = "SP21YTSM60CAY6D011EZVEVNKXVW8FVZE198XEFFP.pox-fast-pool-v2" + [[project.requirements]] contract_id = "SP001SFSMC2ZY76PD4M68P3WGX154XCH7NE3TYMX.pox-pools-1-cycle-v2" @@ -155,6 +156,10 @@ path = "contracts/traits/sip-010-trait.clar" epoch = 2.4 [contracts.rebase-strategy-trait] +path = "contracts/deployed/rebase-strategy-trait.clar" +epoch = 2.4 + +[contracts.rebase-strategy-trait-v1-01] path = "contracts/traits/rebase-strategy-trait.clar" epoch = 2.4 @@ -170,10 +175,14 @@ epoch = 2.4 path = "contracts/extensions/public-pools-strategy-manager.clar" epoch = 2.4 -[contracts.lqstx-mint-endpoint] +[contracts.lqstx-mint-endpoint-v1-01] path = "contracts/extensions/lqstx-mint-endpoint.clar" epoch = 2.4 +[contracts.lqstx-mint-endpoint] +path = "contracts/deployed/lqstx-mint-endpoint.clar" +epoch = 2.4 + [contracts.lqstx-mint-registry] path = "contracts/aux/lqstx-mint-registry.clar" epoch = 2.4 diff --git a/contracts/boot.clar b/contracts/boot.clar index 06f9832..e98394f 100644 --- a/contracts/boot.clar +++ b/contracts/boot.clar @@ -3,7 +3,8 @@ (define-public (execute (sender principal)) (begin (try! (contract-call? .lisa-dao set-extensions (list - { extension: .lqstx-mint-endpoint, enabled: true } + { extension: .lqstx-mint-endpoint, enabled: false } + { extension: .lqstx-mint-endpoint-v1-01, enabled: true } { extension: .lqstx-vault, enabled: true } { extension: .treasury, enabled: true } { extension: .token-vesting, enabled: true } @@ -28,6 +29,7 @@ (try! (contract-call? .operators set-proposal-threshold 4)) ;; Set initial strategy managers, sender is the deployer + ;; TODO add manager (try! (contract-call? .public-pools-strategy-manager set-authorised-manager sender true)) ;; Mint max LISA token supply (1bn) @@ -36,8 +38,8 @@ ))) ;; Enable whitelist - (try! (contract-call? .lqstx-mint-endpoint set-use-whitelist true)) - (try! (contract-call? .lqstx-mint-endpoint set-whitelisted-many + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-use-whitelist true)) + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-whitelisted-many (list 'SP3BQ65DRM8DMTYDD5HWMN60EYC0JFS5NC2V5CWW7 'SP2VZBR9GCVM33BN0WXA05VJP6QV7CJ3Z3SQKJ5HH @@ -52,7 +54,7 @@ true true ))) - + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-paused false)) (ok true) ) ) diff --git a/contracts/deployed/lqstx-mint-endpoint.clar b/contracts/deployed/lqstx-mint-endpoint.clar new file mode 100644 index 0000000..8daff19 --- /dev/null +++ b/contracts/deployed/lqstx-mint-endpoint.clar @@ -0,0 +1,237 @@ +;; +;; lqstx-mint-endpoint +;; +(use-trait sip-010-trait .sip-010-trait.sip-010-trait) +(use-trait rebase-strategy-trait .rebase-strategy-trait.rebase-strategy-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-paused (err u1001)) +(define-constant err-request-pending (err u1006)) +(define-constant err-request-finalized-or-revoked (err u1007)) +(define-constant err-not-whitelisted (err u1008)) + +(define-constant PENDING 0x00) +(define-constant FINALIZED 0x01) +(define-constant REVOKED 0x02) + +(define-constant max-uint u340282366920938463463374607431768211455) + +(define-data-var paused bool true) +(define-data-var mint-delay uint u144) ;; mint available 1 day after cycle starts + +;; @dev used for testing only +(define-data-var activation-block uint u0) +(define-data-var reward-cycle-length uint u2016) ;; 2 weeks + +(define-data-var use-whitelist bool false) +(define-map whitelisted principal bool) + +;; read-only calls + +(define-read-only (is-dao-or-extension) + (ok (asserts! (or (is-eq tx-sender .lisa-dao) (contract-call? .lisa-dao is-extension contract-caller)) err-unauthorised))) + +(define-read-only (is-paused) + (var-get paused)) + +(define-read-only (is-paused-or-fail) + (ok (asserts! (not (is-paused)) err-paused))) + +(define-read-only (get-mint-request-or-fail (request-id uint)) + (contract-call? .lqstx-mint-registry get-mint-request-or-fail request-id)) + +(define-read-only (get-burn-request-or-fail (request-id uint)) + (contract-call? .lqstx-mint-registry get-burn-request-or-fail request-id)) + +(define-read-only (get-mint-requests-pending-or-default (user principal)) + (contract-call? .lqstx-mint-registry get-mint-requests-pending-or-default user)) + +(define-read-only (get-burn-requests-pending-or-default (user principal)) + (contract-call? .lqstx-mint-registry get-burn-requests-pending-or-default user)) + +(define-read-only (get-mint-requests-pending-amount) + (contract-call? .lqstx-mint-registry get-mint-requests-pending-amount)) + +(define-read-only (get-mint-request-or-fail-many (request-ids (list 1000 uint))) + (ok (map get-mint-request-or-fail request-ids))) + +(define-read-only (get-burn-request-or-fail-many (request-ids (list 1000 uint))) + (ok (map get-burn-request-or-fail request-ids))) + +(define-read-only (validate-mint-request (request-id uint)) + (let ( + (request-details (try! (contract-call? .lqstx-mint-registry get-mint-request-or-fail request-id))) + (request-id-idx (unwrap! (index-of? (get-mint-requests-pending-or-default (get requested-by request-details)) request-id) err-request-finalized-or-revoked))) + (asserts! (>= block-height (+ (get-first-stacks-block-in-reward-cycle (+ (get requested-at request-details) u1)) (var-get mint-delay))) err-request-pending) + (ok request-id-idx))) + +;; @dev it favours smaller amounts as we do not allow partial burn +(define-read-only (validate-burn-request (request-id uint)) + (let ( + (request-details (try! (contract-call? .lqstx-mint-registry get-burn-request-or-fail request-id))) + (request-id-idx (unwrap! (index-of? (get-burn-requests-pending-or-default (get requested-by request-details)) request-id) err-request-finalized-or-revoked)) + (vaulted-amount (contract-call? .token-vlqstx get-shares-to-tokens (get wrapped-amount request-details)))) + (asserts! (>= (stx-get-balance .lqstx-vault) vaulted-amount) err-request-pending) + (ok { vaulted-amount: vaulted-amount, request-id-idx: request-id-idx }))) + +;; @dev used for testing only +(define-read-only (get-reward-cycle (stacks-height uint)) + ;; (some (contract-call? 'SP000000000000000000002Q6VF78.pox-3 current-pox-reward-cycle))) + (if (>= stacks-height (var-get activation-block)) + (some (/ (- stacks-height (var-get activation-block)) (var-get reward-cycle-length))) + none)) + +;; @dev used for testing only +(define-read-only (get-first-stacks-block-in-reward-cycle (reward-cycle uint)) + ;; (contract-call? 'SP000000000000000000002Q6VF78.pox-3 reward-cycle-to-burn-height reward-cycle)) + (+ (var-get activation-block) (* (var-get reward-cycle-length) reward-cycle))) + +(define-read-only (get-mint-delay) + (var-get mint-delay)) + +(define-read-only (is-whitelisted-or-mint-for-all (user principal)) + (or (not (var-get use-whitelist)) (default-to false (map-get? whitelisted user)))) + +;; governance calls + +(define-public (set-use-whitelist (new-use bool)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set use-whitelist new-use)))) + +(define-public (set-whitelisted (user principal) (new-whitelisted bool)) + (begin + (try! (is-dao-or-extension)) + (set-whitelisted-private user new-whitelisted))) + +(define-public (set-whitelisted-many (users (list 1000 principal)) (new-whitelisteds (list 1000 bool))) + (begin + (try! (is-dao-or-extension)) + (fold check-err (map set-whitelisted-private users new-whitelisteds) (ok true)))) + +(define-public (set-paused (new-paused bool)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set paused new-paused)))) + +(define-public (set-mint-delay (new-delay uint)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set mint-delay new-delay)))) + +;; @dev used for testing only +(define-public (set-reward-cycle-length (new-reward-cycle-length uint)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set reward-cycle-length new-reward-cycle-length)))) + +;; public calls + +;; @dev the requestor stx is held by the contract until mint can be finalized. +(define-public (request-mint (amount uint)) + (let ( + (sender tx-sender) + (cycle (unwrap-panic (get-reward-cycle block-height))) + (request-details { requested-by: sender, amount: amount, requested-at: cycle, status: PENDING }) + (request-id (try! (contract-call? .lqstx-mint-registry set-mint-request u0 request-details)))) + (try! (is-paused-or-fail)) + (asserts! (is-whitelisted-or-mint-for-all sender) err-not-whitelisted) + (try! (stx-transfer? amount sender .lqstx-vault)) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (+ (get-mint-requests-pending-amount) amount))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending sender (unwrap-panic (as-max-len? (append (get-mint-requests-pending-or-default sender) request-id) u1000)))) + (print { type: "mint-request", id: request-id, details: request-details }) + (ok request-id))) + +(define-public (finalize-mint (request-id uint)) + (let ( + (request-details (try! (get-mint-request-or-fail request-id))) + (mint-requests (get-mint-requests-pending-or-default (get requested-by request-details))) + (request-id-idx (try! (validate-mint-request request-id)))) + (try! (is-paused-or-fail)) + (try! (is-dao-or-extension)) + (try! (contract-call? .token-lqstx dao-mint (get amount request-details) (get requested-by request-details))) + (try! (contract-call? .lqstx-mint-registry set-mint-request request-id (merge request-details { status: FINALIZED }))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (- (get-mint-requests-pending-amount) (get amount request-details)))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending (get requested-by request-details) (pop mint-requests request-id-idx))) + (ok true))) + +(define-public (finalize-mint-many (request-ids (list 1000 uint))) + (fold check-err (map finalize-mint request-ids) (ok true))) + +(define-public (revoke-mint (request-id uint)) + (let ( + (request-details (try! (get-mint-request-or-fail request-id))) + (mint-requests (get-mint-requests-pending-or-default (get requested-by request-details))) + (request-id-idx (unwrap! (index-of? mint-requests request-id) err-request-finalized-or-revoked))) + (try! (is-paused-or-fail)) + (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) + (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) + (try! (contract-call? .lqstx-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: (get amount request-details), recipient: (get requested-by request-details) })))) + (try! (contract-call? .lqstx-mint-registry set-mint-request request-id (merge request-details { status: REVOKED }))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (- (get-mint-requests-pending-amount) (get amount request-details)))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending (get requested-by request-details) (pop mint-requests request-id-idx))) + (ok true))) + +(define-public (request-burn (amount uint) (rebase-trait )) + (let ( + (sender tx-sender) + ;; @dev requested-at not used for burn + (cycle (unwrap-panic (get-reward-cycle block-height))) + (vlqstx-amount (contract-call? .token-vlqstx get-tokens-to-shares amount)) + (request-details { requested-by: sender, amount: amount, wrapped-amount: vlqstx-amount, requested-at: cycle, status: PENDING }) + (request-id (try! (contract-call? .lqstx-mint-registry set-burn-request u0 request-details)))) + (try! (is-paused-or-fail)) + (try! (contract-call? .token-vlqstx mint amount sender)) + (try! (contract-call? .token-vlqstx transfer vlqstx-amount sender .lqstx-mint-registry none)) + (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending sender (unwrap-panic (as-max-len? (append (get-burn-requests-pending-or-default sender) request-id) u1000)))) + (match (contract-call? rebase-trait finalize-burn request-id) + ok-value (ok { request-id: request-id, status: FINALIZED }) + err-value (begin (print { type: "burn-request", id: request-id, details: request-details, finalize-err: err-value }) (ok { request-id: request-id, status: PENDING }))))) + +(define-public (finalize-burn (request-id uint)) + (let ( + (request-details (try! (get-burn-request-or-fail request-id))) + (transfer-vlqstx (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) (as-contract tx-sender) .token-vlqstx))) + (burn-requests (get-burn-requests-pending-or-default (get requested-by request-details))) + (validation-data (try! (validate-burn-request request-id)))) + (try! (is-paused-or-fail)) + (try! (is-dao-or-extension)) + (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) (as-contract tx-sender))) + (try! (contract-call? .token-lqstx dao-burn (get vaulted-amount validation-data) (as-contract tx-sender))) + (try! (contract-call? .lqstx-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: (get vaulted-amount validation-data), recipient: (get requested-by request-details) })))) + (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: FINALIZED }))) + (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending (get requested-by request-details) (pop burn-requests (get request-id-idx validation-data)))) + (ok true))) + +(define-public (finalize-burn-many (request-ids (list 1000 uint))) + (fold check-err (map finalize-burn request-ids) (ok true))) + +(define-public (revoke-burn (request-id uint)) + (let ( + (request-details (try! (get-burn-request-or-fail request-id))) + (burn-requests (get-burn-requests-pending-or-default (get requested-by request-details))) + (request-id-idx (unwrap! (index-of? burn-requests request-id) err-request-finalized-or-revoked)) + (lqstx-amount (contract-call? .token-vlqstx get-shares-to-tokens (get wrapped-amount request-details)))) + (try! (is-paused-or-fail)) + (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) + (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) + (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) (as-contract tx-sender) .token-vlqstx)) + (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) (as-contract tx-sender))) + (try! (contract-call? .token-lqstx transfer lqstx-amount (as-contract tx-sender) (get requested-by request-details) none)) + (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: REVOKED }))) + (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending (get requested-by request-details) (pop burn-requests request-id-idx))) + (ok true))) + +(define-private (check-err (result (response bool uint)) (prior (response bool uint))) + (match prior + ok-value result + err-value (err err-value))) + +(define-private (pop (target (list 1000 uint)) (idx uint)) + (match (slice? target (+ idx u1) (len target)) + some-value (unwrap-panic (as-max-len? (concat (unwrap-panic (slice? target u0 idx)) some-value) u1000)) + (unwrap-panic (slice? target u0 idx)))) + +(define-private (set-whitelisted-private (user principal) (new-whitelisted bool)) + (ok (map-set whitelisted user new-whitelisted))) + diff --git a/contracts/deployed/rebase-strategy-trait.clar b/contracts/deployed/rebase-strategy-trait.clar new file mode 100644 index 0000000..cc631cf --- /dev/null +++ b/contracts/deployed/rebase-strategy-trait.clar @@ -0,0 +1,8 @@ +(define-trait rebase-strategy-trait + ( + (rebase () (response uint uint)) + (finalize-mint (uint) (response bool uint)) + (finalize-burn (uint) (response bool uint)) + ) +) + diff --git a/contracts/extensions/lisa-rebase.clar b/contracts/extensions/lisa-rebase.clar index 962c55c..eac1857 100644 --- a/contracts/extensions/lisa-rebase.clar +++ b/contracts/extensions/lisa-rebase.clar @@ -11,7 +11,7 @@ ) (define-public (rebase (strategies (list 20 ))) - (let ((total-stx (- (+ (stx-get-balance .lqstx-vault) (try! (fold sum-strategy-amounts strategies (ok u0)))) (contract-call? .lqstx-mint-endpoint get-mint-requests-pending-amount)))) + (let ((total-stx (- (+ (stx-get-balance .lqstx-vault) (try! (fold sum-strategy-amounts strategies (ok u0)))) (contract-call? .lqstx-mint-endpoint-v1-01 get-mint-requests-pending-amount)))) (try! (is-dao-or-extension)) (as-contract (try! (contract-call? .token-lqstx set-reserve total-stx))) (ok total-stx) diff --git a/contracts/extensions/lqstx-mint-endpoint.clar b/contracts/extensions/lqstx-mint-endpoint.clar index 4de2269..7be2e0f 100644 --- a/contracts/extensions/lqstx-mint-endpoint.clar +++ b/contracts/extensions/lqstx-mint-endpoint.clar @@ -1,8 +1,7 @@ ;; -;; lqstx-mint-endpoint +;; lqstx-mint-endpoint-v1-01 ;; (use-trait sip-010-trait .sip-010-trait.sip-010-trait) -(use-trait rebase-strategy-trait .rebase-strategy-trait.rebase-strategy-trait) (define-constant err-unauthorised (err u1000)) (define-constant err-paused (err u1001)) @@ -17,11 +16,11 @@ (define-constant max-uint u340282366920938463463374607431768211455) (define-data-var paused bool true) -(define-data-var mint-delay uint u144) ;; mint available 1 day after cycle starts +(define-data-var mint-delay uint u432) ;; mint available 3 day after cycle starts -;; @dev used for testing only -(define-data-var activation-block uint u0) -(define-data-var reward-cycle-length uint u2016) ;; 2 weeks +;; corresponds to `first-burnchain-block-height` and `pox-reward-cycle-length` in pox-3 +(define-data-var activation-burn-block uint u666050) +(define-data-var reward-cycle-length uint u2100) (define-data-var use-whitelist bool false) (define-map whitelisted principal bool) @@ -62,7 +61,7 @@ (let ( (request-details (try! (contract-call? .lqstx-mint-registry get-mint-request-or-fail request-id))) (request-id-idx (unwrap! (index-of? (get-mint-requests-pending-or-default (get requested-by request-details)) request-id) err-request-finalized-or-revoked))) - (asserts! (>= block-height (+ (get-first-stacks-block-in-reward-cycle (+ (get requested-at request-details) u1)) (var-get mint-delay))) err-request-pending) + (asserts! (>= burn-block-height (+ (get-first-burn-block-in-reward-cycle (+ (get requested-at request-details) u1)) (var-get mint-delay))) err-request-pending) (ok request-id-idx))) ;; @dev it favours smaller amounts as we do not allow partial burn @@ -74,17 +73,13 @@ (asserts! (>= (stx-get-balance .lqstx-vault) vaulted-amount) err-request-pending) (ok { vaulted-amount: vaulted-amount, request-id-idx: request-id-idx }))) -;; @dev used for testing only -(define-read-only (get-reward-cycle (stacks-height uint)) - (some (contract-call? 'SP000000000000000000002Q6VF78.pox-3 current-pox-reward-cycle))) - ;; (if (>= stacks-height (var-get activation-block)) - ;; (some (/ (- stacks-height (var-get activation-block)) (var-get reward-cycle-length))) - ;; none)) +(define-read-only (get-reward-cycle (burn-block uint)) + (if (>= burn-block (var-get activation-burn-block)) + (some (/ (- burn-block (var-get activation-burn-block)) (var-get reward-cycle-length))) + none)) -;; @dev used for testing only -(define-read-only (get-first-stacks-block-in-reward-cycle (reward-cycle uint)) - (contract-call? 'SP000000000000000000002Q6VF78.pox-3 reward-cycle-to-burn-height reward-cycle)) - ;; (+ (var-get activation-block) (* (var-get reward-cycle-length) reward-cycle))) +(define-read-only (get-first-burn-block-in-reward-cycle (reward-cycle uint)) + (+ (var-get activation-burn-block) (* (var-get reward-cycle-length) reward-cycle))) (define-read-only (get-mint-delay) (var-get mint-delay)) @@ -92,15 +87,62 @@ (define-read-only (is-whitelisted-or-mint-for-all (user principal)) (or (not (var-get use-whitelist)) (default-to false (map-get? whitelisted user)))) +;; public calls + +;; @dev the requestor stx is held by the contract until mint can be finalized. +(define-public (request-mint (amount uint)) + (let ( + (sender tx-sender) + (cycle (unwrap-panic (get-reward-cycle burn-block-height))) + (request-details { requested-by: sender, amount: amount, requested-at: cycle, status: PENDING }) + (request-id (try! (contract-call? .lqstx-mint-registry set-mint-request u0 request-details)))) + (try! (is-paused-or-fail)) + (asserts! (is-whitelisted-or-mint-for-all sender) err-not-whitelisted) + (try! (stx-transfer? amount sender .lqstx-vault)) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (+ (get-mint-requests-pending-amount) amount))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending sender (unwrap-panic (as-max-len? (append (get-mint-requests-pending-or-default sender) request-id) u1000)))) + (print { type: "mint-request", id: request-id, details: request-details }) + (ok request-id))) + +(define-public (revoke-mint (request-id uint)) + (let ( + (request-details (try! (get-mint-request-or-fail request-id))) + (mint-requests (get-mint-requests-pending-or-default (get requested-by request-details))) + (request-id-idx (unwrap! (index-of? mint-requests request-id) err-request-finalized-or-revoked))) + (try! (is-paused-or-fail)) + (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) + (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) + (try! (contract-call? .lqstx-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: (get amount request-details), recipient: (get requested-by request-details) })))) + (try! (contract-call? .lqstx-mint-registry set-mint-request request-id (merge request-details { status: REVOKED }))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (- (get-mint-requests-pending-amount) (get amount request-details)))) + (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending (get requested-by request-details) (pop mint-requests request-id-idx))) + (ok true))) + +(define-public (revoke-burn (request-id uint)) + (let ( + (request-details (try! (get-burn-request-or-fail request-id))) + (burn-requests (get-burn-requests-pending-or-default (get requested-by request-details))) + (request-id-idx (unwrap! (index-of? burn-requests request-id) err-request-finalized-or-revoked)) + (lqstx-amount (contract-call? .token-vlqstx get-shares-to-tokens (get wrapped-amount request-details)))) + (try! (is-paused-or-fail)) + (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) + (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) + (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) (as-contract tx-sender) .token-vlqstx)) + (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) (as-contract tx-sender))) + (try! (contract-call? .token-lqstx transfer lqstx-amount (as-contract tx-sender) (get requested-by request-details) none)) + (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: REVOKED }))) + (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending (get requested-by request-details) (pop burn-requests request-id-idx))) + (ok true))) + ;; governance calls (define-public (set-use-whitelist (new-use bool)) - (begin + (begin (try! (is-dao-or-extension)) (ok (var-set use-whitelist new-use)))) (define-public (set-whitelisted (user principal) (new-whitelisted bool)) - (begin + (begin (try! (is-dao-or-extension)) (set-whitelisted-private user new-whitelisted))) @@ -119,28 +161,17 @@ (try! (is-dao-or-extension)) (ok (var-set mint-delay new-delay)))) -;; @dev used for testing only +(define-public (set-activation-burn-block (new-activation-burn-block uint)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set activation-burn-block new-activation-burn-block)))) + (define-public (set-reward-cycle-length (new-reward-cycle-length uint)) (begin (try! (is-dao-or-extension)) (ok (var-set reward-cycle-length new-reward-cycle-length)))) -;; public calls - -;; @dev the requestor stx is held by the contract until mint can be finalized. -(define-public (request-mint (amount uint)) - (let ( - (sender tx-sender) - (cycle (unwrap-panic (get-reward-cycle block-height))) - (request-details { requested-by: sender, amount: amount, requested-at: cycle, status: PENDING }) - (request-id (try! (contract-call? .lqstx-mint-registry set-mint-request u0 request-details)))) - (try! (is-paused-or-fail)) - (asserts! (is-whitelisted-or-mint-for-all sender) err-not-whitelisted) - (try! (stx-transfer? amount sender .lqstx-vault)) - (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (+ (get-mint-requests-pending-amount) amount))) - (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending sender (unwrap-panic (as-max-len? (append (get-mint-requests-pending-or-default sender) request-id) u1000)))) - (print { type: "mint-request", id: request-id, details: request-details }) - (ok request-id))) +;; privileged calls (define-public (finalize-mint (request-id uint)) (let ( @@ -158,35 +189,20 @@ (define-public (finalize-mint-many (request-ids (list 1000 uint))) (fold check-err (map finalize-mint request-ids) (ok true))) -(define-public (revoke-mint (request-id uint)) +(define-public (request-burn (sender principal) (amount uint)) (let ( - (request-details (try! (get-mint-request-or-fail request-id))) - (mint-requests (get-mint-requests-pending-or-default (get requested-by request-details))) - (request-id-idx (unwrap! (index-of? mint-requests request-id) err-request-finalized-or-revoked))) - (try! (is-paused-or-fail)) - (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) - (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) - (try! (contract-call? .lqstx-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: (get amount request-details), recipient: (get requested-by request-details) })))) - (try! (contract-call? .lqstx-mint-registry set-mint-request request-id (merge request-details { status: REVOKED }))) - (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending-amount (- (get-mint-requests-pending-amount) (get amount request-details)))) - (try! (contract-call? .lqstx-mint-registry set-mint-requests-pending (get requested-by request-details) (pop mint-requests request-id-idx))) - (ok true))) - -(define-public (request-burn (amount uint) (rebase-trait )) - (let ( - (sender tx-sender) ;; @dev requested-at not used for burn - (cycle (unwrap-panic (get-reward-cycle block-height))) + (cycle (unwrap-panic (get-reward-cycle burn-block-height))) (vlqstx-amount (contract-call? .token-vlqstx get-tokens-to-shares amount)) (request-details { requested-by: sender, amount: amount, wrapped-amount: vlqstx-amount, requested-at: cycle, status: PENDING }) (request-id (try! (contract-call? .lqstx-mint-registry set-burn-request u0 request-details)))) (try! (is-paused-or-fail)) - (try! (contract-call? .token-vlqstx mint amount sender)) - (try! (contract-call? .token-vlqstx transfer vlqstx-amount sender .lqstx-mint-registry none)) + (try! (is-dao-or-extension)) + (try! (contract-call? .token-vlqstx mint amount tx-sender)) + (try! (contract-call? .token-vlqstx transfer vlqstx-amount tx-sender .lqstx-mint-registry none)) (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending sender (unwrap-panic (as-max-len? (append (get-burn-requests-pending-or-default sender) request-id) u1000)))) - (match (contract-call? rebase-trait finalize-burn request-id) - ok-value (ok { request-id: request-id, status: FINALIZED }) - err-value (begin (print { type: "burn-request", id: request-id, details: request-details, finalize-err: err-value }) (ok { request-id: request-id, status: PENDING }))))) + (print { type: "burn-request", id: request-id, details: request-details }) + (ok { request-id: request-id, status: PENDING }))) (define-public (finalize-burn (request-id uint)) (let ( @@ -194,7 +210,7 @@ (transfer-vlqstx (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) (as-contract tx-sender) .token-vlqstx))) (burn-requests (get-burn-requests-pending-or-default (get requested-by request-details))) (validation-data (try! (validate-burn-request request-id)))) - (try! (is-paused-or-fail)) + (try! (is-paused-or-fail)) (try! (is-dao-or-extension)) (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) (as-contract tx-sender))) (try! (contract-call? .token-lqstx dao-burn (get vaulted-amount validation-data) (as-contract tx-sender))) @@ -206,21 +222,7 @@ (define-public (finalize-burn-many (request-ids (list 1000 uint))) (fold check-err (map finalize-burn request-ids) (ok true))) -(define-public (revoke-burn (request-id uint)) - (let ( - (request-details (try! (get-burn-request-or-fail request-id))) - (burn-requests (get-burn-requests-pending-or-default (get requested-by request-details))) - (request-id-idx (unwrap! (index-of? burn-requests request-id) err-request-finalized-or-revoked)) - (lqstx-amount (contract-call? .token-vlqstx get-shares-to-tokens (get wrapped-amount request-details)))) - (try! (is-paused-or-fail)) - (asserts! (is-eq PENDING (get status request-details)) err-request-finalized-or-revoked) - (asserts! (is-eq tx-sender (get requested-by request-details)) err-unauthorised) - (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) (as-contract tx-sender) .token-vlqstx)) - (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) (as-contract tx-sender))) - (try! (contract-call? .token-lqstx transfer lqstx-amount (as-contract tx-sender) (get requested-by request-details) none)) - (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: REVOKED }))) - (try! (contract-call? .lqstx-mint-registry set-burn-requests-pending (get requested-by request-details) (pop burn-requests request-id-idx))) - (ok true))) +;; private calls (define-private (check-err (result (response bool uint)) (prior (response bool uint))) (match prior diff --git a/contracts/extensions/public-pools-strategy-manager.clar b/contracts/extensions/public-pools-strategy-manager.clar index 493fe91..c22081f 100644 --- a/contracts/extensions/public-pools-strategy-manager.clar +++ b/contracts/extensions/public-pools-strategy-manager.clar @@ -1,4 +1,4 @@ -(define-constant err-unauthorised (err u10000)) +(define-constant err-unauthorised (err u3000)) (define-map authorised-managers principal bool) (map-set authorised-managers tx-sender true) diff --git a/contracts/mocks/rebase-mock.clar b/contracts/mocks/rebase-mock.clar index db37e5a..fad76e6 100644 --- a/contracts/mocks/rebase-mock.clar +++ b/contracts/mocks/rebase-mock.clar @@ -1,19 +1,32 @@ +(define-constant PENDING 0x00) +(define-constant FINALIZED 0x01) +(define-constant REVOKED 0x02) + (define-public (rebase) (as-contract (contract-call? .lisa-rebase rebase (list .mock-strategy)))) (define-public (finalize-mint (request-id uint)) (begin (try! (rebase)) - (as-contract (try! (contract-call? .lqstx-mint-endpoint finalize-mint request-id))) + (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 finalize-mint request-id))) (try! (rebase)) (ok true))) (define-public (finalize-burn (request-id uint)) (begin (try! (rebase)) - (as-contract (try! (contract-call? .lqstx-mint-endpoint finalize-burn request-id))) + (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 finalize-burn request-id))) (try! (rebase)) (ok true))) + +(define-public (request-burn (amount uint)) + (let ( + (sender tx-sender) + (send-token (try! (contract-call? .token-lqstx transfer amount sender (as-contract tx-sender) none))) + (request-data (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 request-burn sender amount))))) + (match (finalize-burn (get request-id request-data)) + ok-value (ok { request-id: (get request-id request-data), status: FINALIZED }) + err-value (ok request-data)))) (define-public (callback (extension principal) (payload (buff 2048))) (ok true)) \ No newline at end of file diff --git a/contracts/regtest-boot.clar b/contracts/regtest-boot.clar index 5d40a39..bd7c8db 100644 --- a/contracts/regtest-boot.clar +++ b/contracts/regtest-boot.clar @@ -3,7 +3,8 @@ (define-public (execute (sender principal)) (begin (try! (contract-call? .lisa-dao set-extensions (list - {extension: .lqstx-mint-endpoint, enabled: true} + {extension: .lqstx-mint-endpoint, enabled: false} + {extension: .lqstx-mint-endpoint-v1-01, enabled: true} {extension: .lisa-rebase, enabled: true} {extension: .rebase-mock, enabled: true} {extension: .mock-strategy-manager, enabled: true} @@ -19,9 +20,10 @@ ))) (try! (contract-call? .operators set-proposal-threshold 2)) - (try! (contract-call? .lqstx-mint-endpoint set-paused false)) - (try! (contract-call? .lqstx-mint-endpoint set-reward-cycle-length u200)) - (try! (contract-call? .lqstx-mint-endpoint set-mint-delay u14)) + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-paused false)) + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-activation-burn-block u0)) + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-reward-cycle-length u200)) + (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-mint-delay u14)) (try! (contract-call? .mock-strategy-manager set-authorised-manager 'ST2QXSK64YQX3CQPC530K79XWQ98XFAM9W3XKEH3N true)) (try! (contract-call? .mock-strategy-manager set-authorised-manager 'ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND true)) (ok true) diff --git a/contracts/rules/rebase-1.clar b/contracts/rules/rebase-1.clar index cf891b7..f234748 100644 --- a/contracts/rules/rebase-1.clar +++ b/contracts/rules/rebase-1.clar @@ -1,3 +1,31 @@ + +(define-constant PENDING 0x00) +(define-constant FINALIZED 0x01) +(define-constant REVOKED 0x02) + (define-public (rebase) (contract-call? .lisa-rebase rebase (list .public-pools-strategy)) ) + +(define-public (finalize-mint (request-id uint)) + (begin + (try! (rebase)) + (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 finalize-mint request-id))) + (try! (rebase)) + (ok true))) + +(define-public (finalize-burn (request-id uint)) + (begin + (try! (rebase)) + (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 finalize-burn request-id))) + (try! (rebase)) + (ok true))) + +(define-public (request-burn (amount uint)) + (let ( + (sender tx-sender) + (send-token (try! (contract-call? .token-lqstx transfer amount sender (as-contract tx-sender) none))) + (request-data (as-contract (try! (contract-call? .lqstx-mint-endpoint-v1-01 request-burn sender amount))))) + (match (finalize-burn (get request-id request-data)) + ok-value (ok { request-id: (get request-id request-data), status: FINALIZED }) + err-value (ok request-data)))) diff --git a/contracts/traits/rebase-strategy-trait.clar b/contracts/traits/rebase-strategy-trait.clar index cc631cf..ac21dd3 100644 --- a/contracts/traits/rebase-strategy-trait.clar +++ b/contracts/traits/rebase-strategy-trait.clar @@ -3,6 +3,7 @@ (rebase () (response uint uint)) (finalize-mint (uint) (response bool uint)) (finalize-burn (uint) (response bool uint)) + (request-burn (uint) (response { request-id: uint, status: (buff 1) } uint)) ) ) diff --git a/deployments/default.simnet-plan.yaml b/deployments/default.simnet-plan.yaml index 95df709..a19128e 100644 --- a/deployments/default.simnet-plan.yaml +++ b/deployments/default.simnet-plan.yaml @@ -154,11 +154,6 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/extensions/lqstx-vault.clar clarity-version: 2 - - emulated-contract-publish: - contract-name: rebase-strategy-trait - emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/traits/rebase-strategy-trait.clar - clarity-version: 2 - emulated-contract-publish: contract-name: stx-transfer-proxy emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -175,7 +170,7 @@ plan: path: contracts/token-vlqstx.clar clarity-version: 2 - emulated-contract-publish: - contract-name: lqstx-mint-endpoint + contract-name: lqstx-mint-endpoint-v1-01 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/extensions/lqstx-mint-endpoint.clar clarity-version: 2 @@ -229,14 +224,14 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/strategies/public-pools/fastpool-member.clar clarity-version: 2 - epoch: "2.4" - - id: 3 - transactions: - emulated-contract-publish: contract-name: fastpool-member9 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/strategies/public-pools/fastpool-member.clar clarity-version: 2 + epoch: "2.4" + - id: 3 + transactions: - emulated-contract-publish: contract-name: xverse-member1 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -317,6 +312,16 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/proxies/lisa-transfer-proxy.clar clarity-version: 2 + - emulated-contract-publish: + contract-name: rebase-strategy-trait + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/deployed/rebase-strategy-trait.clar + clarity-version: 2 + - emulated-contract-publish: + contract-name: lqstx-mint-endpoint + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/deployed/lqstx-mint-endpoint.clar + clarity-version: 2 - emulated-contract-publish: contract-name: lqstx-transfer-proxy emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -347,6 +352,14 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/mocks/rebase-mock.clar clarity-version: 2 + - emulated-contract-publish: + contract-name: rebase-strategy-trait-v1-01 + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/traits/rebase-strategy-trait.clar + clarity-version: 2 + epoch: "2.4" + - id: 4 + transactions: - emulated-contract-publish: contract-name: regtest-boot emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -357,9 +370,6 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/traits/sip-010-extensions-trait.clar clarity-version: 2 - epoch: "2.4" - - id: 4 - transactions: - emulated-contract-publish: contract-name: sip-010-transferable-trait emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM diff --git a/tests/lqstx-mint-endpoint.test.ts b/tests/lqstx-mint-endpoint.test.ts index a8b44ac..071b725 100644 --- a/tests/lqstx-mint-endpoint.test.ts +++ b/tests/lqstx-mint-endpoint.test.ts @@ -9,7 +9,7 @@ const bot = accounts.get('wallet_3')!; const manager = accounts.get('wallet_4')!; const contracts = { - endpoint: 'lqstx-mint-endpoint', + endpoint: 'lqstx-mint-endpoint-v1-01', registry: 'lqstx-mint-registry', vault: 'lqstx-vault', lqstx: 'token-lqstx', @@ -76,11 +76,10 @@ const requestBurn = (payload: Buffer) => oracle ), tx.callPublicFn( - contracts.endpoint, + contracts.rebase1, 'request-burn', [ Cl.uint(100e6), - Cl.contractPrincipal(simnet.deployer, contracts.rebase1), ], user ), @@ -89,7 +88,6 @@ const requestBurn = (payload: Buffer) => describe(contracts.endpoint, () => { it('can request mint', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - const response = requestMint(); expect(response.result).toBeOk(Cl.uint(1)); }); @@ -100,7 +98,7 @@ describe(contracts.endpoint, () => { expect(requestMint().result).toBeOk(Cl.uint(1)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle const finaliseErr = simnet.callPublicFn( @@ -154,7 +152,7 @@ describe(contracts.endpoint, () => { expect(responses[1].result).toBeOk(Cl.bool(true)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle simnet.mineEmptyBlocks(144); // mint-delay @@ -174,7 +172,7 @@ describe(contracts.endpoint, () => { expect(requestMint().result).toBeOk(Cl.uint(1)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle simnet.mineEmptyBlocks(144); // mint-delay @@ -197,7 +195,7 @@ describe(contracts.endpoint, () => { expect(requestMint().result).toBeOk(Cl.uint(1)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle simnet.mineEmptyBlocks(144); // mint-delay @@ -254,7 +252,7 @@ describe(contracts.endpoint, () => { expect(requestMint().result).toBeOk(Cl.uint(1)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle simnet.mineEmptyBlocks(144); // mint-delay @@ -318,7 +316,7 @@ describe(contracts.endpoint, () => { ).result.buffer; const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine - 100); let responses = simnet.mineBlock([ @@ -360,11 +358,10 @@ describe(contracts.endpoint, () => { bot ), tx.callPublicFn( - contracts.endpoint, + contracts.rebase1, 'request-burn', [ Cl.uint(100e6), - Cl.contractPrincipal(simnet.deployer, contracts.rebase1), ], user ), @@ -427,7 +424,7 @@ describe(contracts.endpoint, () => { expect(requestMint().result).toBeOk(Cl.uint(1)); const cycle = simnet.callReadOnlyFn(contracts.endpoint, 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user).result.value.value; - const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-stacks-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; + const blocksToMine = Number(simnet.callReadOnlyFn(contracts.endpoint, 'get-first-burn-block-in-reward-cycle', [Cl.uint(cycle + 1n)], user).result.value) - simnet.blockHeight; simnet.mineEmptyBlocks(blocksToMine); // go to the next cycle const finaliseErr = simnet.callPublicFn(