diff --git a/Clarinet.toml b/Clarinet.toml index 87158f8..eb7a72e 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -15,6 +15,12 @@ contract_id = "SP001SFSMC2ZY76PD4M68P3WGX154XCH7NE3TYMX.pox-pools-1-cycle-v2" [[project.requirements]] contract_id = "SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard" +[[project.requirements]] +contract_id = "SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait" + +[[project.requirements]] +contract_id = "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait" + [contracts.lisa-dao] path = "contracts/lisa-dao.clar" epoch = 2.4 @@ -39,6 +45,14 @@ epoch = 2.4 path = "contracts/traits/sip-010-trait.clar" epoch = 2.4 +[contracts.nft-trait] +path = "contracts/traits/nft-trait.clar" +epoch = 2.4 + +[contracts.commission-trait] +path = "contracts/traits/commission-trait.clar" +epoch = 2.4 + [contracts.strategy-trait] path = "contracts/traits/strategy-trait.clar" epoch = 2.4 @@ -175,10 +189,14 @@ epoch = 2.4 path = "contracts/extensions/public-pools-strategy-manager.clar" epoch = 2.4 -[contracts.lqstx-mint-endpoint-v1-01] +[contracts.lqstx-mint-endpoint-v1-02] path = "contracts/extensions/lqstx-mint-endpoint.clar" epoch = 2.4 +[contracts.lqstx-mint-endpoint-v1-01] +path = "contracts/deployed/lqstx-mint-endpoint-v1-01.clar" +epoch = 2.4 + [contracts.lqstx-mint-endpoint] path = "contracts/deployed/lqstx-mint-endpoint.clar" epoch = 2.4 @@ -187,6 +205,14 @@ epoch = 2.4 path = "contracts/aux/lqstx-mint-registry.clar" epoch = 2.4 +[contracts.li-stx-mint-nft] +path = "contracts/aux/li-stx-mint-nft.clar" +epoch = 2.4 + +[contracts.li-stx-burn-nft] +path = "contracts/aux/li-stx-burn-nft.clar" +epoch = 2.4 + [contracts.token-lqstx] path = "contracts/token-lqstx.clar" epoch = 2.4 @@ -200,21 +226,17 @@ path = "contracts/token-lisa.clar" epoch = 2.4 [contracts.lisa-rebase] -path = "contracts/extensions/lisa-rebase.clar" +path = "contracts/deployed/lisa-rebase.clar" epoch = 2.4 [contracts.rebase-1] -path = "contracts/rules/rebase-1.clar" +path = "contracts/deployed/rebase-1.clar" epoch = 2.4 [contracts.operators] path = "contracts/extensions/operators.clar" epoch = 2.4 -[contracts.rebase-mock] -path = "contracts/mocks/rebase-mock.clar" -epoch = 2.4 - [contracts.mock-strategy] path = "contracts/mocks/mock-strategy.clar" epoch = 2.4 diff --git a/README.md b/README.md new file mode 100644 index 0000000..286842c --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Liquid Stacking (LISA) + +## Deployment + +1. npm run + +## Errors + +The LISA protocol contracts each have their own error space. All protocol errors +are in the form `(err uint)` and they are unique across all contracts. + +### Error space + +| Group | Error space | Description | +| ------------- | ----------- | ---------------------------------------------- | +| Dao | 1XXX | Errors related to the dao. | +| Operators | 1XXX | Errors related to the operators. | +| Strategy | 2XXX | Errors related to stacking strategy. | +| Permissions | 3XXX | Errors related to governance of the dao. | +| Token | 3XXX | Errors coming directly from the tokens. | +| Proxy | 4XXX | Errors related to proxy contracts. | +| Pool members | 5XXX | Errors related to stacking pool members. | +| Wrapped token | 6XXX | Errors coming directly from the wrapped token. | +| Mint Endpoint | 7XXX | Errors coming directly from the mint endpoint. | +| NFTs | 8XXX | Errors coming directly from the NFTs. | +| Vesting | 9XXX | Errors coming directly from vesting. | +| Pools | 4XX/5XX/6XX | Errors coming public stacking pools. | +| Assets | 1/2/3/4 | Errors coming for native Clarity assets. | + +### Error table + + +| Contract | Constant | Value | Description | +|-------------------------------|----------------------------------|-------------|-------------| +| lisa-dao | err-unauthorised | (err u1000) | | +| lqstx-mint-endpoint | err-unauthorised | (err u1000) | | +| lqstx-mint-endpoint-v1-01 | err-unauthorised | (err u1000) | | +| lqstx-mint-registry | err-unauthorised | (err u1000) | | +| lqstx-vault | err-unauthorised | (err u1000) | | +| operators | err-unauthorised | (err u1000) | | +| token-vesting | err-unauthorised | (err u1000) | | +| token-wlqstx | err-not-authorized | (err u1000) | | +| treasury | err-unauthorised | (err u1000) | | +| lisa-dao | err-already-executed | (err u1001) | | +| lqstx-mint-endpoint | err-paused | (err u1001) | | +| lqstx-mint-endpoint-v1-01 | err-paused | (err u1001) | | +| operators | err-not-operator | (err u1001) | | +| lisa-dao | err-invalid-extension | (err u1002) | | +| operators | err-already-signalled | (err u1002) | | +| operators | err-proposal-expired | (err u1003) | | +| operators | err-unknown-proposal | (err u1004) | | +| operators | err-reused-proposal | (err u1005) | | +| lqstx-mint-endpoint | err-request-pending | (err u1006) | | +| lqstx-mint-endpoint-v1-01 | err-request-pending | (err u1006) | | +| lqstx-mint-endpoint | err-request-finalized-or-revoked | (err u1007) | | +| lqstx-mint-endpoint-v1-01 | err-request-finalized-or-revoked | (err u1007) | | +| lqstx-mint-endpoint | err-not-whitelisted | (err u1008) | | +| lqstx-mint-endpoint-v1-01 | err-not-whitelisted | (err u1008) | | +| lqstx-mint-registry | err-unknown-request-id | (err u1008) | | +| public-pools-strategy | err-not-vault-caller | (err u2000) | | +| public-pools-strategy | err-invalid-payload | (err u2001) | | +| li-stx-burn-nft | err-unauthorised | (err u3000) | | +| li-stx-mint-nft | err-unauthorised | (err u3000) | | +| lisa-rebase | err-unauthorised | (err u3000) | | +| lqstx-mint-endpoint-v1-02 | err-unauthorised | (err u3000) | | +| public-pools-strategy-manager | err-unauthorised | (err u3000) | | +| token-lisa | err-unauthorised | (err u3000) | | +| token-lqstx | err-unauthorised | (err u3000) | | +| token-vlqstx | err-unauthorised | (err u3000) | | +| token-wlqstx | err-transfer-failed | (err u3000) | | +| token-lqstx | err-invalid-amount | (err u3001) | | +| token-lisa | err-not-token-owner | (err u4) | | +| lisa-transfer-proxy | err-invalid-payload | (err u4000) | | +| lqstx-transfer-proxy | err-invalid-payload | (err u4000) | | +| stx-transfer-many-proxy | err-invalid-payload | (err u4000) | | +| stx-transfer-proxy | err-invalid-payload | (err u4000) | | +| pox-fast-pool-v2 | err-unauthorized | (err u401) | | +| pox-fast-pool-v2 | err-forbidden | (err u403) | | +| pox-pools-1-cycle-v2 | err-not-found | (err u404) | | +| pox-fast-pool-v2 | err-too-early | (err u500) | | +| pox-pools-1-cycle-v2 | err-non-positive-amount | (err u500) | | +| fastpool-member1 | err-unauthorised | (err u5000) | | +| fastpool-member10 | err-unauthorised | (err u5000) | | +| fastpool-member2 | err-unauthorised | (err u5000) | | +| fastpool-member3 | err-unauthorised | (err u5000) | | +| fastpool-member4 | err-unauthorised | (err u5000) | | +| fastpool-member5 | err-unauthorised | (err u5000) | | +| fastpool-member6 | err-unauthorised | (err u5000) | | +| fastpool-member7 | err-unauthorised | (err u5000) | | +| fastpool-member8 | err-unauthorised | (err u5000) | | +| fastpool-member9 | err-unauthorised | (err u5000) | | +| xverse-member1 | err-unauthorised | (err u5000) | | +| xverse-member10 | err-unauthorised | (err u5000) | | +| xverse-member2 | err-unauthorised | (err u5000) | | +| xverse-member3 | err-unauthorised | (err u5000) | | +| xverse-member4 | err-unauthorised | (err u5000) | | +| xverse-member5 | err-unauthorised | (err u5000) | | +| xverse-member6 | err-unauthorised | (err u5000) | | +| xverse-member7 | err-unauthorised | (err u5000) | | +| xverse-member8 | err-unauthorised | (err u5000) | | +| xverse-member9 | err-unauthorised | (err u5000) | | +| pox-pools-1-cycle-v2 | err-no-stacker-info | (err u501) | | +| pox-pools-1-cycle-v2 | err-no-user-info | (err u502) | | +| pox-fast-pool-v2 | err-decrease-forbidden | (err u503) | | +| pox-pools-1-cycle-v2 | err-decrease-forbidden | (err u503) | | +| pox-fast-pool-v2 | err-pox-address-deactivated | (err u504) | | +| token-wlqstx | err-mint-failed | (err u6002) | | +| token-wlqstx | err-burn-failed | (err u6003) | | +| token-wlqstx | err-not-supported | (err u6004) | | +| pox-fast-pool-v2 | err-already-stacking | (err u603) | | +| pox-pools-1-cycle-v2 | err-already-stacking | (err u603) | | +| pox-fast-pool-v2 | err-stacking-permission-denied | (err u609) | | +| pox-pools-1-cycle-v2 | err-stacking-permission-denied | (err u609) | | +| lqstx-mint-endpoint-v1-02 | err-paused | (err u7001) | | +| lqstx-mint-endpoint-v1-02 | err-request-pending | (err u7006) | | +| lqstx-mint-endpoint-v1-02 | err-request-finalized-or-revoked | (err u7007) | | +| lqstx-mint-endpoint-v1-02 | err-not-whitelisted | (err u7008) | | +| li-stx-mint-nft | err-not-authorized | (err u8000) | | +| li-stx-mint-nft | err-listing | (err u8001) | | +| li-stx-mint-nft | err-wrong-commission | (err u8002) | | +| li-stx-mint-nft | err-not-found | (err u8003) | | +| li-stx-mint-nft | err-metadata-frozen | (err u8004) | | +| li-stx-burn-nft | err-not-authorized | (err u8100) | | +| li-stx-burn-nft | err-listing | (err u8101) | | +| li-stx-burn-nft | err-wrong-commission | (err u8102) | | +| li-stx-burn-nft | err-not-found | (err u8103) | | +| li-stx-burn-nft | err-metadata-frozen | (err u8104) | | +| token-vesting | err-caller-not-recipient | (err u9000) | | +| token-vesting | err-unknown-vesting-id | (err u9001) | | +| token-vesting | err-event-not-vested | (err u9002) | | +| token-vesting | err-event-already-claimed | (err u9003) | | +| token-vesting | err-recipient-exists | (err u9004) | | + + +## References diff --git a/contracts/aux/li-stx-burn-nft.clar b/contracts/aux/li-stx-burn-nft.clar new file mode 100644 index 0000000..cfbceb9 --- /dev/null +++ b/contracts/aux/li-stx-burn-nft.clar @@ -0,0 +1,144 @@ +;; li-stx-burn +;; contractType: public + +;; __IF_MAINNET__ +(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) +;; (impl-trait .nft-trait.nft-trait) +;; __ENDIF__ + +(define-non-fungible-token li-stx-burn uint) + +;; Constants +(define-constant err-not-authorized (err u8100)) +(define-constant err-listing (err u8101)) +(define-constant err-wrong-commission (err u8102)) +(define-constant err-not-found (err u8103)) +(define-constant err-metadata-frozen (err u8104)) + +(define-constant err-unauthorised (err u3000)) + +(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)) +) + +;; Internal variables +(define-data-var last-id uint u0) +(define-data-var ipfs-root (string-ascii 80) "") +(define-data-var metadata-frozen bool false) + +(define-read-only (max (a uint) (b uint)) (if (> a b) a b)) + +(define-public (mint (id uint) (amount uint) (recipient principal)) + (let ((current-balance (get-balance recipient))) + (try! (is-dao-or-extension)) + (var-set last-id (max id (var-get last-id))) + (map-set token-count recipient (+ current-balance u1)) + (nft-mint? li-stx-burn id recipient))) + +(define-public (burn (token-id uint)) + (let ((owner (unwrap! (nft-get-owner? li-stx-burn token-id) err-not-found)) + (current-balance (get-balance owner))) + (try! (is-dao-or-extension)) + (asserts! (is-none (map-get? market token-id)) err-listing) + (try! (nft-burn? li-stx-burn token-id owner)) + (map-set token-count owner (- current-balance u1)) + (ok true))) + +(define-private (is-owner (token-id uint) (user principal)) + (is-eq user (unwrap! (nft-get-owner? li-stx-burn token-id) false))) + +;; governance calls + +(define-public (set-base-uri (new-base-uri (string-ascii 80))) + (begin + (try! (is-dao-or-extension)) + (asserts! (not (var-get metadata-frozen)) err-metadata-frozen) + (print { notification: "token-metadata-update", payload: { token-class: "nft", contract-id: (as-contract tx-sender) }}) + (var-set ipfs-root new-base-uri) + (ok true))) + +(define-public (freeze-metadata) + (begin + (try! (is-dao-or-extension)) + (var-set metadata-frozen true) + (ok true))) + +;; Non-custodial SIP-009 transfer function +(define-public (transfer (id uint) (sender principal) (recipient principal)) + (begin + (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) err-not-authorized) + (asserts! (is-none (map-get? market id)) err-listing) + (trnsfr id sender recipient))) + +;; read-only functions +(define-read-only (get-owner (token-id uint)) + (ok (nft-get-owner? li-stx-burn token-id))) + +(define-read-only (get-last-token-id) + (ok (var-get last-id))) + +(define-read-only (get-token-uri (token-id uint)) + (ok (some (concat (concat (var-get ipfs-root) "{id}") ".json")))) + +;; Non-custodial marketplace extras +;; __IF_MAINNET__ +(use-trait commission-trait 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait.commission) +;; (use-trait commission-trait .commission-trait.commission) +;; __ENDIF__ + +(define-map token-count principal uint) +(define-map market uint {price: uint, commission: principal, royalty: uint}) + +(define-read-only (get-balance (account principal)) + (default-to u0 + (map-get? token-count account))) + +(define-private (trnsfr (id uint) (sender principal) (recipient principal)) + (match (nft-transfer? li-stx-burn id sender recipient) + success + (let + ((sender-balance (get-balance sender)) + (recipient-balance (get-balance recipient))) + (map-set token-count + sender + (- sender-balance u1)) + (map-set token-count + recipient + (+ recipient-balance u1)) + (ok success)) + error (err error))) + +(define-private (is-sender-owner (id uint)) + (let ((owner (unwrap! (nft-get-owner? li-stx-burn id) false))) + (or (is-eq tx-sender owner) (is-eq contract-caller owner)))) + +(define-read-only (get-listing-in-ustx (id uint)) + (map-get? market id)) + +(define-public (list-in-ustx (id uint) (price uint) (comm-trait )) + (let ((listing {price: price, commission: (contract-of comm-trait), royalty: u0})) + (asserts! (is-sender-owner id) err-not-authorized) + (map-set market id listing) + (print (merge listing {a: "list-in-ustx", id: id})) + (ok true))) + +(define-public (unlist-in-ustx (id uint)) + (begin + (asserts! (is-sender-owner id) err-not-authorized) + (map-delete market id) + (print {a: "unlist-in-ustx", id: id}) + (ok true))) + +(define-public (buy-in-ustx (id uint) (comm-trait )) + (let ((owner (unwrap! (nft-get-owner? li-stx-burn id) err-not-found)) + (listing (unwrap! (map-get? market id) err-listing)) + (price (get price listing)) + (royalty (get royalty listing))) + (asserts! (is-eq (contract-of comm-trait) (get commission listing)) err-wrong-commission) + (try! (stx-transfer? price tx-sender owner)) + (try! (contract-call? comm-trait pay id price)) + (try! (trnsfr id owner tx-sender)) + (map-delete market id) + (print {a: "buy-in-ustx", id: id}) + (ok true))) + diff --git a/contracts/aux/li-stx-mint-nft.clar b/contracts/aux/li-stx-mint-nft.clar new file mode 100644 index 0000000..6dc4790 --- /dev/null +++ b/contracts/aux/li-stx-mint-nft.clar @@ -0,0 +1,145 @@ +;; li-stx-mint +;; contractType: public + +;; __IF_MAINNET__ +(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) +;; (impl-trait .nft-trait.nft-trait) +;; __ENDIF__ + +(define-non-fungible-token li-stx-mint uint) + +;; Constants +(define-constant err-not-authorized (err u8000)) +(define-constant err-listing (err u8001)) +(define-constant err-wrong-commission (err u8002)) +(define-constant err-not-found (err u8003)) +(define-constant err-metadata-frozen (err u8004)) + +(define-constant err-unauthorised (err u3000)) + +(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)) +) + +;; Internal variables +(define-data-var last-id uint u0) +(define-data-var ipfs-root (string-ascii 80) "") +(define-data-var metadata-frozen bool false) + +(define-read-only (max (a uint) (b uint)) (if (> a b) a b)) + +(define-public (mint (id uint) (amount uint) (recipient principal)) + (let ((current-balance (get-balance recipient))) + (try! (is-dao-or-extension)) + (var-set last-id (max id (var-get last-id))) + (map-set token-count recipient (+ current-balance u1)) + (nft-mint? li-stx-mint id recipient))) + +(define-public (burn (token-id uint)) + (let ((owner (unwrap! (nft-get-owner? li-stx-mint token-id) err-not-found)) + (current-balance (get-balance owner))) + (try! (is-dao-or-extension)) + (asserts! (is-none (map-get? market token-id)) err-listing) + (try! (nft-burn? li-stx-mint token-id owner)) + (map-set token-count owner (- current-balance u1)) + (ok true))) + +(define-private (is-owner (token-id uint) (user principal)) + (is-eq user (unwrap! (nft-get-owner? li-stx-mint token-id) false))) + +;; governance calls + +(define-public (set-base-uri (new-base-uri (string-ascii 80))) + (begin + (try! (is-dao-or-extension)) + (asserts! (not (var-get metadata-frozen)) err-metadata-frozen) + (print { notification: "token-metadata-update", payload: { token-class: "nft", contract-id: (as-contract tx-sender) }}) + (var-set ipfs-root new-base-uri) + (ok true))) + +(define-public (freeze-metadata) + (begin + (try! (is-dao-or-extension)) + (var-set metadata-frozen true) + (ok true))) + +;; Non-custodial SIP-009 transfer function +(define-public (transfer (id uint) (sender principal) (recipient principal)) + (begin + (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) err-not-authorized) + (asserts! (is-none (map-get? market id)) err-listing) + (trnsfr id sender recipient))) + +;; read-only functions +(define-read-only (get-owner (token-id uint)) + (ok (nft-get-owner? li-stx-mint token-id))) + +(define-read-only (get-last-token-id) + (ok (var-get last-id))) + +(define-read-only (get-token-uri (token-id uint)) + (ok (some (concat (concat (var-get ipfs-root) "{id}") ".json")))) + +;; Non-custodial marketplace extras +;; __IF_MAINNET__ +(use-trait commission-trait 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait.commission) +;; (use-trait commission-trait .commission-trait.commission) +;; __ENDIF__ + + +(define-map token-count principal uint) +(define-map market uint {price: uint, commission: principal, royalty: uint}) + +(define-read-only (get-balance (account principal)) + (default-to u0 + (map-get? token-count account))) + +(define-private (trnsfr (id uint) (sender principal) (recipient principal)) + (match (nft-transfer? li-stx-mint id sender recipient) + success + (let + ((sender-balance (get-balance sender)) + (recipient-balance (get-balance recipient))) + (map-set token-count + sender + (- sender-balance u1)) + (map-set token-count + recipient + (+ recipient-balance u1)) + (ok success)) + error (err error))) + +(define-private (is-sender-owner (id uint)) + (let ((owner (unwrap! (nft-get-owner? li-stx-mint id) false))) + (or (is-eq tx-sender owner) (is-eq contract-caller owner)))) + +(define-read-only (get-listing-in-ustx (id uint)) + (map-get? market id)) + +(define-public (list-in-ustx (id uint) (price uint) (comm-trait )) + (let ((listing {price: price, commission: (contract-of comm-trait), royalty: u0})) + (asserts! (is-sender-owner id) err-not-authorized) + (map-set market id listing) + (print (merge listing {a: "list-in-ustx", id: id})) + (ok true))) + +(define-public (unlist-in-ustx (id uint)) + (begin + (asserts! (is-sender-owner id) err-not-authorized) + (map-delete market id) + (print {a: "unlist-in-ustx", id: id}) + (ok true))) + +(define-public (buy-in-ustx (id uint) (comm-trait )) + (let ((owner (unwrap! (nft-get-owner? li-stx-mint id) err-not-found)) + (listing (unwrap! (map-get? market id) err-listing)) + (price (get price listing)) + (royalty (get royalty listing))) + (asserts! (is-eq (contract-of comm-trait) (get commission listing)) err-wrong-commission) + (try! (stx-transfer? price tx-sender owner)) + (try! (contract-call? comm-trait pay id price)) + (try! (trnsfr id owner tx-sender)) + (map-delete market id) + (print {a: "buy-in-ustx", id: id}) + (ok true))) + diff --git a/contracts/extensions/lisa-rebase.clar b/contracts/deployed/lisa-rebase.clar similarity index 100% rename from contracts/extensions/lisa-rebase.clar rename to contracts/deployed/lisa-rebase.clar diff --git a/contracts/deployed/lqstx-mint-endpoint-v1-01.clar b/contracts/deployed/lqstx-mint-endpoint-v1-01.clar new file mode 100644 index 0000000..ac84ab8 --- /dev/null +++ b/contracts/deployed/lqstx-mint-endpoint-v1-01.clar @@ -0,0 +1,243 @@ + +;; SPDX-License-Identifier: BUSL-1.1 + +;; +;; lqstx-mint-endpoint-v1-01 +;; + +;; __IF_MAINNET__ +(use-trait sip-010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) +;; (use-trait sip-010-trait .sip-010-trait.sip-010-trait) +;; __ENDIF__ + +(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 u432) ;; mint available 3 day after cycle starts + +;; __IF_MAINNET__ +(define-constant pox-info (unwrap-panic (contract-call? 'SP000000000000000000002Q6VF78.pox-3 get-pox-info))) +(define-constant activation-burn-block (get first-burnchain-block-height pox-info)) +(define-constant reward-cycle-length (get reward-cycle-length pox-info)) +(define-constant prepare-cycle-length (get prepare-cycle-length pox-info)) +;; (define-constant activation-burn-block u0) +;; (define-constant reward-cycle-length u200) +;; (define-constant prepare-cycle-length u100) +;; __ENDIF__ + +(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-not-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! (>= 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 +(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 get-reward-cycle measures prepare to prepare, not end to end +(define-read-only (get-reward-cycle (burn-block uint)) + (if (>= burn-block activation-burn-block) + (some (/ (- (+ burn-block prepare-cycle-length) activation-burn-block) reward-cycle-length)) + none)) + +(define-read-only (get-first-burn-block-in-reward-cycle (reward-cycle uint)) + (+ activation-burn-block (* 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)))) + +;; 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-not-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-not-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-not-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 + (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)))) + +;; privileged calls + +(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-not-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 (request-burn (sender principal) (amount uint)) + (let ( + ;; @dev requested-at not used for burn + (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-not-paused-or-fail)) + (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)))) + (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 ( + (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-not-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))) + +;; private calls + +(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/rules/rebase-1.clar b/contracts/deployed/rebase-1.clar similarity index 100% rename from contracts/rules/rebase-1.clar rename to contracts/deployed/rebase-1.clar diff --git a/contracts/extensions/lqstx-mint-endpoint.clar b/contracts/extensions/lqstx-mint-endpoint.clar index 2725804..ad313da 100644 --- a/contracts/extensions/lqstx-mint-endpoint.clar +++ b/contracts/extensions/lqstx-mint-endpoint.clar @@ -2,19 +2,21 @@ ;; SPDX-License-Identifier: BUSL-1.1 ;; -;; lqstx-mint-endpoint-v1-01 +;; lqstx-mint-endpoint-v1-02 ;; +(use-trait strategy-trait .strategy-trait.strategy-trait) + ;; __IF_MAINNET__ (use-trait sip-010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) ;; (use-trait sip-010-trait .sip-010-trait.sip-010-trait) ;; __ENDIF__ -(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 err-unauthorised (err u3000)) +(define-constant err-paused (err u7001)) +(define-constant err-request-pending (err u7006)) +(define-constant err-request-finalized-or-revoked (err u7007)) +(define-constant err-not-whitelisted (err u7008)) (define-constant PENDING 0x00) (define-constant FINALIZED 0x01) @@ -25,13 +27,16 @@ (define-data-var paused bool true) (define-data-var mint-delay uint u432) ;; mint available 3 day after cycle starts -;; corresponds to `first-burnchain-block-height` and `pox-reward-cycle-length` in pox-3 ;; __IF_MAINNET__ +(define-data-var request-cutoff uint u300) ;; request must be made 300 blocks before prepare stage starts (define-constant pox-info (unwrap-panic (contract-call? 'SP000000000000000000002Q6VF78.pox-3 get-pox-info))) (define-constant activation-burn-block (get first-burnchain-block-height pox-info)) (define-constant reward-cycle-length (get reward-cycle-length pox-info)) +(define-constant prepare-cycle-length (get prepare-cycle-length pox-info)) +;; (define-data-var request-cutoff uint u10) ;; (define-constant activation-burn-block u0) ;; (define-constant reward-cycle-length u200) +;; (define-constant prepare-cycle-length u10) ;; __ENDIF__ (define-data-var use-whitelist bool false) @@ -45,7 +50,7 @@ (define-read-only (is-paused) (var-get paused)) -(define-read-only (is-paused-or-fail) +(define-read-only (is-not-paused-or-fail) (ok (asserts! (not (is-paused)) err-paused))) (define-read-only (get-mint-request-or-fail (request-id uint)) @@ -69,83 +74,174 @@ (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 (get-owner-mint-nft (id uint)) + (contract-call? .li-stx-mint-nft get-owner id)) + +(define-read-only (get-owner-burn-nft (id uint)) + (contract-call? .li-stx-burn-nft get-owner id)) + (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))) + (recipient (unwrap! (get-owner-mint-nft request-id) err-request-finalized-or-revoked))) (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))) + (ok recipient))) ;; @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)) + (recipient (unwrap! (get-owner-burn-nft 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 }))) + (ok { vaulted-amount: vaulted-amount, recipient: recipient }))) +;; @dev get-reward-cycle measures end to end (define-read-only (get-reward-cycle (burn-block uint)) - (if (>= burn-block activation-burn-block) - (some (/ (- burn-block activation-burn-block) reward-cycle-length)) - none)) + (/ (- burn-block activation-burn-block) reward-cycle-length)) (define-read-only (get-first-burn-block-in-reward-cycle (reward-cycle uint)) (+ activation-burn-block (* reward-cycle-length reward-cycle))) +;; @dev get-request-cycle measures request-cutoff to request-cutoff +(define-read-only (get-request-cycle (burn-block uint)) + (/ (- (+ burn-block prepare-cycle-length (var-get request-cutoff)) activation-burn-block) reward-cycle-length)) + +(define-read-only (get-first-burn-block-in-request-cycle (reward-cycle uint)) + (- (+ activation-burn-block (* reward-cycle-length reward-cycle)) prepare-cycle-length (var-get request-cutoff))) + (define-read-only (get-mint-delay) (var-get mint-delay)) +(define-read-only (get-request-cutoff) + (var-get request-cutoff)) + (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 +(define-public (rebase) + (let ( + (available-stx (stx-get-balance .lqstx-vault)) + ;; __IF_MAINNET__ + (deployed-stx (unwrap-panic (contract-call? .public-pools-strategy get-amount-in-strategy))) + ;; (deployed-stx (unwrap-panic (contract-call? .mock-strategy get-amount-in-strategy))) + ;; __ENDIF__ + (pending-stx (get-mint-requests-pending-amount)) + (total-stx (- (+ available-stx deployed-stx) pending-stx))) + (try! (contract-call? .token-lqstx set-reserve total-stx)) + (ok total-stx))) + ;; @dev the requestor stx is held by the contract until mint can be finalized. (define-public (request-mint (amount uint)) - (let ( + (let ( (sender tx-sender) - (cycle (unwrap-panic (get-reward-cycle burn-block-height))) + (rebase-first (try! (rebase))) + (cycle (get-request-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)) + (try! (is-not-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)))) + (try! (contract-call? .li-stx-mint-nft mint request-id amount sender)) + (try! (rebase)) (print { type: "mint-request", id: request-id, details: request-details }) (ok request-id))) (define-public (revoke-mint (request-id uint)) (let ( + (rebase-first (try! (rebase))) (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) + (recipient (unwrap! (unwrap-panic (get-owner-mint-nft request-id)) err-request-finalized-or-revoked))) + (try! (is-not-paused-or-fail)) + (asserts! (is-eq tx-sender recipient) 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-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: (get amount request-details), recipient: recipient })))) (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))) + (try! (contract-call? .li-stx-mint-nft burn request-id)) + (try! (rebase)) (ok true))) +(define-public (request-burn (amount uint)) + (let ( + (sender tx-sender) + (rebase-first (try! (rebase))) + (cycle (get-request-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-not-paused-or-fail)) + (print { type: "burn-request", id: request-id, details: request-details }) + (if (>= (stx-get-balance .lqstx-vault) amount) + (begin + (try! (contract-call? .token-lqstx dao-burn amount sender)) + (try! (contract-call? .lqstx-vault proxy-call .stx-transfer-proxy (unwrap-panic (to-consensus-buff? { ustx: amount, recipient: sender })))) + (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: FINALIZED }))) + (try! (rebase)) + (ok {request-id: request-id, status: FINALIZED }) + ) + (begin + (try! (contract-call? .token-vlqstx mint amount sender)) + (try! (contract-call? .token-vlqstx transfer vlqstx-amount sender .lqstx-mint-registry none)) + (try! (contract-call? .li-stx-burn-nft mint request-id amount sender)) + (try! (rebase)) + (ok { request-id: request-id, status: PENDING }))))) + (define-public (revoke-burn (request-id uint)) (let ( + (rebase-first (try! (rebase))) (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)) + (recipient (unwrap! (unwrap-panic (get-owner-burn-nft 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)) + (try! (is-not-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)) + (asserts! (is-eq tx-sender recipient) err-unauthorised) + (try! (contract-call? .lqstx-mint-registry transfer (get wrapped-amount request-details) recipient .token-vlqstx)) + (try! (contract-call? .token-vlqstx burn (get wrapped-amount request-details) recipient)) (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))) + (try! (contract-call? .li-stx-burn-nft burn request-id)) + (try! (rebase)) (ok true))) +(define-public (finalize-mint (request-id uint)) + (let ( + (rebase-first (try! (rebase))) + (request-details (try! (get-mint-request-or-fail request-id))) + (recipient (unwrap! (unwrap-panic (get-owner-mint-nft request-id)) err-request-finalized-or-revoked))) + (try! (validate-mint-request request-id)) + (try! (is-not-paused-or-fail)) + (try! (contract-call? .token-lqstx dao-mint (get amount request-details) recipient)) + (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? .li-stx-mint-nft burn request-id)) + (try! (rebase)) + (ok true))) + +(define-public (finalize-mint-many (request-ids (list 1000 uint))) + (fold check-err (map finalize-mint request-ids) (ok true))) + +(define-public (finalize-burn (request-id uint)) + (let ( + (rebase-first (try! (rebase))) + (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))) + (recipient (unwrap! (unwrap-panic (get-owner-burn-nft request-id)) err-request-finalized-or-revoked)) + (validation-data (try! (validate-burn-request request-id)))) + (try! (is-not-paused-or-fail)) + (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: recipient })))) + (try! (contract-call? .lqstx-mint-registry set-burn-request request-id (merge request-details { status: FINALIZED }))) + (try! (contract-call? .li-stx-burn-nft burn request-id)) + (try! (rebase)) + (ok true))) + +(define-public (finalize-burn-many (request-ids (list 1000 uint))) + (fold check-err (map finalize-burn request-ids) (ok true))) + ;; governance calls (define-public (set-use-whitelist (new-use bool)) @@ -173,59 +269,19 @@ (try! (is-dao-or-extension)) (ok (var-set mint-delay new-delay)))) +(define-public (set-request-cutoff (new-cutoff uint)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set request-cutoff new-cutoff)))) + ;; privileged calls -(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 (request-burn (sender principal) (amount uint)) - (let ( - ;; @dev requested-at not used for burn - (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! (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)))) - (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 ( - (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))) - ;; private calls +(define-private (sum-strategy-amounts (strategy ) (accumulator (response uint uint))) + (ok (+ (try! (contract-call? strategy get-amount-in-strategy)) (try! accumulator))) +) + (define-private (check-err (result (response bool uint)) (prior (response bool uint))) (match prior ok-value result diff --git a/contracts/mocks/rebase-mock.clar b/contracts/mocks/rebase-mock.clar deleted file mode 100644 index 97739c5..0000000 --- a/contracts/mocks/rebase-mock.clar +++ /dev/null @@ -1,35 +0,0 @@ - -;; SPDX-License-Identifier: BUSL-1.1 - -(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-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)))) - -(define-public (callback (extension principal) (payload (buff 2048))) - (ok true)) \ No newline at end of file diff --git a/contracts/proposals/lip001.clar b/contracts/proposals/lip001.clar new file mode 100644 index 0000000..2e17c6e --- /dev/null +++ b/contracts/proposals/lip001.clar @@ -0,0 +1,45 @@ + +;; SPDX-License-Identifier: BUSL-1.1 + +(impl-trait .proposal-trait.proposal-trait) + +(define-public (execute (sender principal)) + (begin + (try! (contract-call? .lisa-dao set-extensions (list + { extension: .lqstx-mint-endpoint-v1-01, enabled: false } + { extension: .lqstx-mint-endpoint-v1-02, enabled: true } + { extension: .lisa-rebase, enabled: false } + { extension: .rebase-1, enabled: false } + { extension: .lisa-rebase-v1-02, enabled: true } + { extension: .rebase-1-v1-02, enabled: true } + ))) + + (try! (contract-call? .token-lqstx dao-set-name "LiSTX")) + (try! (contract-call? .token-lqstx dao-set-symbol "LiSTX")) + (try! (contract-call? .token-vlqstx dao-set-name "vLiSTX")) + (try! (contract-call? .token-vlqstx dao-set-symbol "vLiSTX")) + (print { notification: "what-is-LISA", payload: "LISA is the goddess of liquid stacking. Liberate your STX with LiSTX!"}) + + ;; Enable whitelist + (try! (contract-call? .lqstx-mint-endpoint-v1-02 set-use-whitelist true)) + (try! (contract-call? .lqstx-mint-endpoint-v1-02 set-whitelisted-many + (list + 'SP3BQ65DRM8DMTYDD5HWMN60EYC0JFS5NC2V5CWW7 + 'SP2VZBR9GCVM33BN0WXA05VJP6QV7CJ3Z3SQKJ5HH + 'SP12BFYTH3NJ6N63KE0S50GHSYV0M91NGQND2B704 + 'SPGAB1P3YV109E22KXFJYM63GK0G21BYX50CQ80B + 'SPFJVM9Y1A4KJ31T8ZBDESZH36YGPDAZ9WXEFC53 + 'SPHFAXDZVFHMY8YR3P9J7ZCV6N89SBET203ZAY25 + ) + (list + true + true + true + true + true + true + ))) + (try! (contract-call? .lqstx-mint-endpoint-v1-02 set-paused false)) + (ok true) + ) +) diff --git a/contracts/regtest-boot.clar b/contracts/regtest-boot.clar index 25b06ee..55a3891 100644 --- a/contracts/regtest-boot.clar +++ b/contracts/regtest-boot.clar @@ -6,13 +6,13 @@ (define-public (execute (sender principal)) (begin (try! (contract-call? .lisa-dao set-extensions (list - {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} - {extension: .lqstx-vault, enabled: true} - {extension: .operators, enabled: true} + { extension: .lqstx-mint-endpoint, enabled: false } + { extension: .lqstx-mint-endpoint-v1-02, enabled: true } + { extension: .lqstx-vault, enabled: true } + { extension: .treasury, enabled: true } + { extension: .token-vesting, enabled: true } + { extension: .operators, enabled: true } + { extension: .mock-strategy-manager, enabled: true } ))) ;; Set initial operators @@ -24,8 +24,8 @@ ))) (try! (contract-call? .operators set-proposal-threshold 2)) - (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-paused false)) - (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-mint-delay u14)) + (try! (contract-call? .lqstx-mint-endpoint-v1-02 set-paused false)) + (try! (contract-call? .lqstx-mint-endpoint-v1-02 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/simnet-boot.clar b/contracts/simnet-boot.clar index 006bc78..6b8ec06 100644 --- a/contracts/simnet-boot.clar +++ b/contracts/simnet-boot.clar @@ -7,56 +7,34 @@ (begin (try! (contract-call? .lisa-dao set-extensions (list { extension: .lqstx-mint-endpoint, enabled: false } - { extension: .lqstx-mint-endpoint-v1-01, enabled: true } + { extension: .lqstx-mint-endpoint-v1-02, enabled: true } { extension: .lqstx-vault, enabled: true } { extension: .treasury, enabled: true } { extension: .token-vesting, enabled: true } { extension: .public-pools-strategy-manager, enabled: true } - { extension: .lisa-rebase, enabled: true } - { extension: .rebase-1, enabled: true } { extension: .operators, enabled: true } ))) ;; Set initial operators (try! (contract-call? .operators set-operators (list - ;; three from ALEX - { operator: 'SP3BQ65DRM8DMTYDD5HWMN60EYC0JFS5NC2V5CWW7, enabled: true } - { operator: 'SPHFAXDZVFHMY8YR3P9J7ZCV6N89SBET203ZAY25, enabled: true } - { operator: 'SPSZ26REB731JN8H00TD010S600F4AB4Z8F0JRB7, enabled: true } - ;; three from Ryder/FAST Pool - { operator: 'SP12BFYTH3NJ6N63KE0S50GHSYV0M91NGQND2B704, enabled: true } - { operator: 'SP1ZPTDQ3801C1AYEZ37NJWNDZ3HM60HC2TCFP228, enabled: true } - { operator: 'SPGAB1P3YV109E22KXFJYM63GK0G21BYX50CQ80B, enabled: true } + {operator: tx-sender, enabled: true} + {operator: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM, enabled: true} + {operator: 'ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND, enabled: true} + {operator: 'ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB, enabled: true} ))) - ;; Set operator signal threshold, i.e. 4-of-6 - (try! (contract-call? .operators set-proposal-threshold 4)) - - ;; Set initial strategy managers, sender is the deployer - (try! (contract-call? .public-pools-strategy-manager set-authorised-manager sender true)) + (try! (contract-call? .operators set-proposal-threshold 2)) ;; Mint max LISA token supply (1bn) (try! (contract-call? .token-lisa dao-mint-many (list { recipient: .treasury, amount: u1000000000000000 } ))) - ;; Enable whitelist - (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-use-whitelist false)) - (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-whitelisted-many - (list - 'SP3BQ65DRM8DMTYDD5HWMN60EYC0JFS5NC2V5CWW7 - 'SP2VZBR9GCVM33BN0WXA05VJP6QV7CJ3Z3SQKJ5HH - 'SP12BFYTH3NJ6N63KE0S50GHSYV0M91NGQND2B704 - 'SPGAB1P3YV109E22KXFJYM63GK0G21BYX50CQ80B - 'SPFJVM9Y1A4KJ31T8ZBDESZH36YGPDAZ9WXEFC53 - ) - (list - true - true - true - true - true - ))) - (try! (contract-call? .lqstx-mint-endpoint-v1-01 set-paused false)) + ;; Set initial strategy managers, sender is the deployer + (try! (contract-call? .public-pools-strategy-manager set-authorised-manager sender true)) + (try! (contract-call? .public-pools-strategy-manager set-authorised-manager 'ST2QXSK64YQX3CQPC530K79XWQ98XFAM9W3XKEH3N true)) + (try! (contract-call? .public-pools-strategy-manager set-authorised-manager 'ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND true)) + + (try! (contract-call? .lqstx-mint-endpoint-v1-02 set-paused false)) (ok true) ) ) diff --git a/contracts/token-lisa.clar b/contracts/token-lisa.clar index 9dd4931..9c58d53 100644 --- a/contracts/token-lisa.clar +++ b/contracts/token-lisa.clar @@ -1,7 +1,11 @@ ;; SPDX-License-Identifier: BUSL-1.1 +;; __IF_MAINNET__ (impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) +;; (impl-trait .sip-010-trait.sip-010-trait) +;; __ENDIF__ + (define-constant err-unauthorised (err u3000)) (define-constant err-not-token-owner (err u4)) diff --git a/contracts/traits/commission-trait.clar b/contracts/traits/commission-trait.clar new file mode 100644 index 0000000..4d26d52 --- /dev/null +++ b/contracts/traits/commission-trait.clar @@ -0,0 +1,2 @@ +(define-trait commission + ((pay (uint uint) (response bool uint)))) diff --git a/contracts/traits/nft-trait.clar b/contracts/traits/nft-trait.clar new file mode 100644 index 0000000..cc558fd --- /dev/null +++ b/contracts/traits/nft-trait.clar @@ -0,0 +1,15 @@ +(define-trait nft-trait + ( + ;; Last token ID, limited to uint range + (get-last-token-id () (response uint uint)) + + ;; URI for metadata associated with the token + (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) + + ;; Owner of a given token identifier + (get-owner (uint) (response (optional principal) uint)) + + ;; Transfer from the sender to a new principal + (transfer (uint principal principal) (response bool uint)) + ) +) diff --git a/deployments/default.simnet-plan.yaml b/deployments/default.simnet-plan.yaml index 398380a..4b3dd46 100644 --- a/deployments/default.simnet-plan.yaml +++ b/deployments/default.simnet-plan.yaml @@ -49,14 +49,21 @@ plan: batches: - id: 0 transactions: + - emulated-contract-publish: + contract-name: nft-trait + emulated-sender: SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9 + path: "./.cache/requirements/SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.clar" + clarity-version: 1 + - emulated-contract-publish: + contract-name: commission-trait + emulated-sender: SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335 + path: "./.cache/requirements/SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait.clar" + clarity-version: 1 - emulated-contract-publish: contract-name: sip-010-trait-ft-standard emulated-sender: SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE path: "./.cache/requirements/SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.clar" clarity-version: 1 - epoch: "2.0" - - id: 1 - transactions: - emulated-contract-publish: contract-name: trait-sip-010 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -102,8 +109,8 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts_modules/alex_v1/traits/trait-vault.clar clarity-version: 1 - epoch: "2.05" - - id: 2 + epoch: "2.1" + - id: 1 transactions: - emulated-contract-publish: contract-name: pox-pools-1-cycle-v2 @@ -168,7 +175,7 @@ plan: - emulated-contract-publish: contract-name: lqstx-mint-endpoint-v1-01 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/extensions/lqstx-mint-endpoint.clar + path: contracts/deployed/lqstx-mint-endpoint-v1-01.clar clarity-version: 2 - emulated-contract-publish: contract-name: operators @@ -231,7 +238,7 @@ plan: path: contracts/strategies/public-pools/xverse-member.clar clarity-version: 2 epoch: "2.4" - - id: 3 + - id: 2 transactions: - emulated-contract-publish: contract-name: xverse-member10 @@ -298,10 +305,25 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/boot.clar clarity-version: 2 + - emulated-contract-publish: + contract-name: commission-trait + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/traits/commission-trait.clar + clarity-version: 2 + - emulated-contract-publish: + contract-name: li-stx-burn-nft + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/aux/li-stx-burn-nft.clar + clarity-version: 2 + - emulated-contract-publish: + contract-name: li-stx-mint-nft + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/aux/li-stx-mint-nft.clar + clarity-version: 2 - emulated-contract-publish: contract-name: lisa-rebase emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/extensions/lisa-rebase.clar + path: contracts/deployed/lisa-rebase.clar clarity-version: 2 - emulated-contract-publish: contract-name: lisa-transfer-proxy @@ -318,6 +340,11 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/deployed/lqstx-mint-endpoint.clar clarity-version: 2 + - emulated-contract-publish: + contract-name: lqstx-mint-endpoint-v1-02 + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/extensions/lqstx-mint-endpoint.clar + clarity-version: 2 - emulated-contract-publish: contract-name: lqstx-transfer-proxy emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -338,15 +365,18 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/mocks/mock-strategy-manager.clar clarity-version: 2 + epoch: "2.4" + - id: 3 + transactions: + - emulated-contract-publish: + contract-name: nft-trait + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/traits/nft-trait.clar + clarity-version: 2 - emulated-contract-publish: contract-name: rebase-1 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/rules/rebase-1.clar - clarity-version: 2 - - emulated-contract-publish: - contract-name: rebase-mock - emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/mocks/rebase-mock.clar + path: contracts/deployed/rebase-1.clar clarity-version: 2 - emulated-contract-publish: contract-name: rebase-strategy-trait-v1-01 @@ -358,9 +388,6 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/regtest-boot.clar clarity-version: 2 - epoch: "2.4" - - id: 4 - transactions: - emulated-contract-publish: contract-name: simnet-boot emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM diff --git a/package.json b/package.json index 49952bf..32f7892 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "multisig-analyse": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/analyse-multisig-deployment-plan.ts", "get-keys": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/get-secret-pubkeys.ts", "generate-secret": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/generate-secret.ts", + "error-codes": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/error-codes.ts", "replace:mainnet": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/replace-mainnet-address.ts", "replace:testnet": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/replace-testnet-address.ts", "setup:clarity": "./scripts/clarinet_manager.sh clean && ./scripts/clarinet_manager.sh install", "test": "npm run replace:mainnet && vitest run && npm run replace:testnet", "test:init": "vitest run -t notests", - "test:report": "npm run replace:testnet && vitest run -- --coverage --costs && npm run replace:testnet", + "test:report": "npm run replace:mainnet && vitest run -- --coverage --costs && npm run replace:testnet", "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\"", "generate:report": "genhtml --branch-coverage -o coverage lcov.info" }, diff --git a/scripts/error-codes.ts b/scripts/error-codes.ts new file mode 100644 index 0000000..8b5c23b --- /dev/null +++ b/scripts/error-codes.ts @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BUSL-1.1 +import { initSimnet } from '@hirosystems/clarinet-sdk'; +import { createErrorsTable } from './lib/error-codes.ts'; + +const manifestFile = './Clarinet.toml'; +const simnet = await initSimnet(manifestFile); + +createErrorsTable(simnet, false); diff --git a/scripts/lib/error-codes.ts b/scripts/lib/error-codes.ts new file mode 100644 index 0000000..bfe942f --- /dev/null +++ b/scripts/lib/error-codes.ts @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BUSL-1.1 + +import { Simnet } from '@hirosystems/clarinet-sdk'; +import { readFileSync, writeFileSync } from 'fs'; +const readmeFile = './README.md'; + +const constantErrRegex = /^\s*\(define-constant\s+(err-.+?)\s+(\(.+?\))\s*\)(.*?)$/gm; +const errorCodeRegex = /u([0-9]+)/; +const commentRegex = /;;\s*(.+)/; +const readmeErrorsDelineator = ''; + +const tableHeader = ['Contract', 'Constant', 'Value', 'Description']; + +export function isTestContract(contractName: string) { + return contractName.indexOf('mock') >= 0; +} + +function padTableCell(content: string, length: number) { + const repeat = length - content.length + 1; + return repeat > 0 ? ' ' + content + ' '.repeat(repeat) : ' '; +} + +export function createErrorsTable(simnet: Simnet, extractCheck: boolean) { + const errorsSeenCount: { [key: string]: { lastConstantName: string; count: number } } = {}; + let readme = readFileSync(readmeFile).toString(); + const errorTable: Array> = []; + const longestColumnCells = tableHeader.map(v => v.length); + + const compareReadme = extractCheck && readme; + + for (const [contractId] of simnet.getContractsInterfaces()) { + if (isTestContract(contractId)) continue; + const source = simnet.getContractSource(contractId); + if (!source) continue; + const errorConstants = source.matchAll(constantErrRegex); + for (const [, errorConstant, errorValue, errorComment] of errorConstants) { + const errorDescription = errorComment?.match(commentRegex)?.[1] || ''; // || '_None_'; + if (!errorValue.match(errorCodeRegex)) + console.error(`Constant '${errorConstant}' error value is not in form of (err uint)`); + if (!errorsSeenCount[errorValue]) + errorsSeenCount[errorValue] = { lastConstantName: errorConstant, count: 1 }; + else if (errorsSeenCount[errorValue].lastConstantName !== errorConstant) { + errorsSeenCount[errorValue].lastConstantName = errorConstant; + ++errorsSeenCount[errorValue].count; + } + const row = [contractId.split('.')[1], errorConstant, errorValue, errorDescription]; + row.map((content, index) => { + if (content.length > longestColumnCells[index]) longestColumnCells[index] = content.length; + }); + errorTable.push(row); + } + } + + const nonUniqueErrors = Object.entries(errorsSeenCount).filter(([, value]) => value.count > 1); + if (nonUniqueErrors.length > 0) console.log(nonUniqueErrors); + + errorTable.sort((a, b) => (a[2] === b[2] ? (a[0] > b[0] ? 1 : -1) : a[2] > b[2] ? 1 : -1)); // string sort + + let errors = + '|' + + tableHeader + .map((content, index) => padTableCell(content, longestColumnCells[index])) + .join('|') + + '|\n'; + errors += '|' + longestColumnCells.map(length => '-'.repeat(length + 2)).join('|') + '|\n'; + errors += errorTable.reduce( + (accumulator, row) => + accumulator + + '|' + + row.map((content, index) => padTableCell(content, longestColumnCells[index])).join('|') + + '|\n', + '' + ); + + const split = readme.split(readmeErrorsDelineator); + readme = `${split[0]}${readmeErrorsDelineator}\n${errors}${readmeErrorsDelineator}${split[2]}`; + + if (compareReadme && compareReadme !== readme) { + throw new Error( + 'Generated readme is not equal to readme in current commit (error table mismatch)' + ); + } + + writeFileSync(readmeFile, readme); + console.log(`Error table written to ${readmeFile}`); +} diff --git a/scripts/logErrors.js b/scripts/logErrors.js new file mode 100644 index 0000000..191a234 --- /dev/null +++ b/scripts/logErrors.js @@ -0,0 +1,5 @@ +import { setUncaughtExceptionCaptureCallback } from 'node:process'; +setUncaughtExceptionCaptureCallback(err => { + console.error(err); + process.exit(1); +}); diff --git a/tests/clients/mock-client.ts b/tests/clients/mock-client.ts new file mode 100644 index 0000000..1e78fb5 --- /dev/null +++ b/tests/clients/mock-client.ts @@ -0,0 +1,140 @@ +import { tx } from '@hirosystems/clarinet-sdk'; +import { IntegerType } from '@stacks/common'; +import { Cl, ResponseOkCV, UIntCV } from '@stacks/transactions'; + +export const createClientMockSetup = () => { + const accounts = simnet.getAccounts(); + const user = accounts.get('wallet_1')!; + const oracle = accounts.get('wallet_2')!; + const bot = accounts.get('wallet_3')!; + const manager = accounts.get('wallet_4')!; + const operator3 = accounts.get('wallet_5')!; + const user2 = accounts.get('wallet_6')!; + + const contracts = { + endpoint: 'lqstx-mint-endpoint-v1-02', + registry: 'lqstx-mint-registry', + vault: 'lqstx-vault', + lqstx: 'token-lqstx', + vlqstx: 'token-vlqstx', + wstx: 'token-wstx', + strategy: 'public-pools-strategy', + amm: 'amm-swap-pool-v1-1', + wlqstx: 'token-wlqstx', + dao: 'lisa-dao', + boot: 'simnet-boot', + manager: 'public-pools-strategy-manager', + operators: 'operators', + proposal: 'mock-proposal', + proposal2: 'mock-proposal', + mintNft: 'li-stx-mint-nft', + burnNft: 'li-stx-burn-nft', + }; + + const prepareTest = () => + simnet.mineBlock([ + tx.callPublicFn( + contracts.dao, + 'construct', + [Cl.contractPrincipal(simnet.deployer, contracts.boot)], + simnet.deployer + ), + ]); + + const requestMint = (amount: IntegerType) => + simnet.callPublicFn(contracts.endpoint, 'request-mint', [Cl.uint(amount)], user); + + const requestBurn = (amount: IntegerType) => + simnet.callPublicFn(contracts.endpoint, 'request-burn', [Cl.uint(amount)], user); + + const fundStrategy = (amount: IntegerType) => + simnet.callPublicFn(contracts.manager, 'fund-strategy', [Cl.list([Cl.uint(amount)])], manager); + + const finalizeMint = (requestId: IntegerType) => + simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(requestId)], bot); + + const getRewardCycle = () => { + return ( + simnet.callReadOnlyFn( + contracts.endpoint, + 'get-reward-cycle', + [Cl.uint(simnet.blockHeight)], + user + ).result as UIntCV + ).value; + }; + + const getRequestCycle = () => { + return ( + simnet.callReadOnlyFn( + contracts.endpoint, + 'get-request-cycle', + [Cl.uint(simnet.blockHeight)], + user + ).result as UIntCV + ).value; + }; + + const getRequestCutoff = () => { + return ( + simnet.callReadOnlyFn(contracts.endpoint, 'get-request-cutoff', [], user).result as UIntCV + ).value; + }; + + const getBlocksToStartOfCycle = (cycle: bigint) => { + return ( + Number( + ( + simnet.callReadOnlyFn( + contracts.endpoint, + 'get-first-burn-block-in-reward-cycle', + [Cl.uint(cycle)], + user + ).result as UIntCV + ).value + ) - simnet.blockHeight + ); + }; + + const goToNextRequestCycle = () => { + const cycle = getRequestCycle(); + const blocksToMine = getBlocksToStartOfCycle(cycle + 1n); + + simnet.mineEmptyBlocks(blocksToMine); + }; + + const goToNextCycle = () => { + const cycle = getRewardCycle(); + const blocksToMine = getBlocksToStartOfCycle(cycle + 1n); + + simnet.mineEmptyBlocks(blocksToMine); + }; + + const liSTXBalance = (user: string) => { + return ( + simnet.callReadOnlyFn(contracts.lqstx, 'get-balance', [Cl.standardPrincipal(user)], user) + .result as ResponseOkCV + ).value; + }; + return { + contracts, + prepareTest, + requestMint, + requestBurn, + fundStrategy, + finalizeMint, + getRewardCycle, + getRequestCycle, + getBlocksToStartOfCycle, + goToNextCycle, + goToNextRequestCycle, + getRequestCutoff, + liSTXBalance, + user, + user2, + oracle, + bot, + manager, + operator3, + }; +}; diff --git a/tests/clients/sip10-client.ts b/tests/clients/sip10-client.ts new file mode 100644 index 0000000..0992c6c --- /dev/null +++ b/tests/clients/sip10-client.ts @@ -0,0 +1,29 @@ +import { Cl, ClarityType, cvToString } from '@stacks/transactions'; +import { expect, it } from 'vitest'; + +export const sip10Tests = (contract: string) => { + const accounts = simnet.getAccounts(); + const alice = accounts.get('wallet_1')!; + const bob = accounts.get('wallet_2')!; + + it('owner can transfer', () => { + let response = simnet.callPublicFn( + contract, + 'transfer', + [Cl.uint(1), Cl.standardPrincipal(alice), Cl.standardPrincipal(bob), Cl.none()], + alice + ); + console.log('transfer', cvToString(response.result)); + expect(response.result).toBeOk(Cl.bool(true)); + }); + + it('owner cannot transfer to owner', () => { + let response = simnet.callPublicFn( + contract, + 'transfer', + [Cl.uint(1), Cl.standardPrincipal(alice), Cl.standardPrincipal(alice), Cl.none()], + alice + ); + expect(response.result).toHaveClarityType(ClarityType.ResponseErr); + }); +}; diff --git a/tests/errors.test.ts b/tests/errors.test.ts new file mode 100644 index 0000000..aa666ae --- /dev/null +++ b/tests/errors.test.ts @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 + +import { describe, it } from 'vitest'; +import { createErrorsTable } from '../scripts/lib/error-codes.ts'; + +describe('readme', () => { + it('should have the correct error code table', () => { + createErrorsTable(simnet, true); + // should not throw an error + }); +}); diff --git a/tests/listx-nft.test.ts b/tests/listx-nft.test.ts new file mode 100644 index 0000000..85cae31 --- /dev/null +++ b/tests/listx-nft.test.ts @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: BUSL-1.1 + +import { Cl } from '@stacks/transactions'; +import { describe, expect, it } from 'vitest'; +import { createClientMockSetup } from './clients/mock-client'; + +const { contracts, prepareTest, requestMint, requestBurn, goToNextCycle, liSTXBalance, goToNextRequestCycle, fundStrategy, user, bot } = + createClientMockSetup(); + +const mintDelay = 14; + +const transferMintNFT = (nftId: number, account: string) => { + return simnet.callPublicFn( + contracts.mintNft, + 'transfer', + [Cl.uint(nftId), Cl.standardPrincipal(user), Cl.standardPrincipal(account)], + user + ); +}; + +const transferBurnNFT = (nftId: number, account: string) => { + return simnet.callPublicFn( + contracts.burnNft, + 'transfer', + [Cl.uint(nftId), Cl.standardPrincipal(user), Cl.standardPrincipal(account)], + user + ); +}; + +describe('LiSTX NFT', () => { + it('user can transfer nft before finalize mint', () => { + prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); + let response = requestMint(100e6); + expect(response.result).toBeOk(Cl.uint(1)); + + // transfer nft to bot + response = transferMintNFT(1, bot); + expect(response.result).toBeOk(Cl.bool(true)); + // finalize mint + goToNextRequestCycle(); + expect(fundStrategy(100e6).result).toBeOk(Cl.uint(100e6)); + goToNextCycle(); + simnet.mineEmptyBlocks(mintDelay + 1); + simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); + + // check that bot received liquid stx + expect(liSTXBalance(user)).toBeUint(0); + expect(liSTXBalance(bot)).toBeUint(100e6); + }); + + it('new owner can revoke mint', () => { + prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); + let response = requestMint(100e6); + expect(response.result).toBeOk(Cl.uint(1)); + + // transfer nft to bot + response = transferMintNFT(1, bot); + expect(response.result).toBeOk(Cl.bool(true)); + + response = simnet.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], bot); + expect(response.result).toBeOk(Cl.bool(true)); + + // check that bot received stx + expect(simnet.getAssetsMap().get('STX')?.get(bot)).toBe(100000100000000n); + expect(simnet.getAssetsMap().get('STX')?.get(user)).toBe(99999900000000n); + }); + + it('there is no burn nft when liquid token is burnt immediately', () => { + prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); + let response; + + // request and finalize mint + response = requestMint(100e6); + expect(response.result).toBeOk(Cl.uint(1)); + goToNextRequestCycle(); + expect(fundStrategy(1e6).result).toBeOk(Cl.uint(1e6)); + goToNextCycle(); + simnet.mineEmptyBlocks(mintDelay + 1); + response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); + expect(response.result).toBeOk(Cl.bool(true)); + + // request burn + response = requestBurn(60e6); + expect(response.result).toBeOk( + Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('01') }) + ); + response = requestBurn(39e6); + expect(response.result).toBeOk( + Cl.tuple({ 'request-id': Cl.uint(2), status: Cl.bufferFromHex('01') }) + ); + // nfts were never minted + expect(simnet.callReadOnlyFn(contracts.burnNft, 'get-owner', [Cl.uint(1)], user).result).toBeOk( + Cl.none() + ); + expect(simnet.callReadOnlyFn(contracts.burnNft, 'get-owner', [Cl.uint(2)], user).result).toBeOk( + Cl.none() + ); + expect(simnet.callReadOnlyFn(contracts.burnNft, 'get-last-token-id', [], user).result).toBeOk( + Cl.uint(0) + ); + + // check that bot received stx + expect(simnet.getAssetsMap().get('STX')?.get(bot)).toBe(100000000000000n); + expect(simnet.getAssetsMap().get('STX')?.get(user)).toBe(99999999000000n); + }); +}); diff --git a/tests/lqstx-mint-endpoint-with-public-pools.test.ts b/tests/lqstx-mint-endpoint-with-public-pools.test.ts index 486227e..abd3eaf 100644 --- a/tests/lqstx-mint-endpoint-with-public-pools.test.ts +++ b/tests/lqstx-mint-endpoint-with-public-pools.test.ts @@ -1,157 +1,78 @@ // SPDX-License-Identifier: BUSL-1.1 import { tx } from '@hirosystems/clarinet-sdk'; -import { Cl, ResponseOkCV, TupleCV, UIntCV, cvToString } from '@stacks/transactions'; +import { Cl, TupleCV, UIntCV } from '@stacks/transactions'; import { describe, expect, it } from 'vitest'; +import { createClientMockSetup } from './clients/mock-client'; -const mintDelay = 432; -const accounts = simnet.getAccounts(); -const user = accounts.get('wallet_1')!; -const oracle = accounts.get('wallet_2')!; -const bot = accounts.get('wallet_3')!; -const manager = accounts.get('deployer')!; -const operator = accounts.get('wallet_4')!; -const user2 = accounts.get('wallet_5')!; - -const contracts = { - endpoint: 'lqstx-mint-endpoint-v1-01', - registry: 'lqstx-mint-registry', - vault: 'lqstx-vault', - lqstx: 'token-lqstx', - vlqstx: 'token-vlqstx', - wstx: 'token-wstx', - strategy: 'public-pools-strategy', - rebase: 'lisa-rebase', - rebase1: 'rebase-1', - amm: 'amm-swap-pool-v1-1', - wlqstx: 'token-wlqstx', - dao: 'lisa-dao', - boot: 'simnet-boot', - manager: 'public-pools-strategy-manager', - operators: 'operators', - proposal: 'mock-proposal', -}; - -const prepareTest = () => - simnet.mineBlock([ - tx.callPublicFn( - contracts.dao, - 'construct', - [Cl.contractPrincipal(simnet.deployer, contracts.boot)], - simnet.deployer - ), - ]); - -const getRewardCycle = () => { - return ( - simnet.callReadOnlyFn( - contracts.endpoint, - 'get-reward-cycle', - [Cl.uint(simnet.blockHeight)], - user - ).result as ResponseOkCV - ).value.value; -}; - -const getBlocksToStartOfCycle = (cycle: bigint) => { - return ( - Number( - ( - simnet.callReadOnlyFn( - contracts.endpoint, - 'get-first-burn-block-in-reward-cycle', - [Cl.uint(cycle)], - user - ).result as ResponseOkCV - ).value - ) - simnet.blockHeight - ); -}; -const goToNextCycle = () => { - const cycle = getRewardCycle(); - const blocksToMine = getBlocksToStartOfCycle(cycle + 1n); - - simnet.mineEmptyBlocks(blocksToMine); -}; +const { contracts, user, user2, oracle, bot, manager, + prepareTest, goToNextCycle, goToNextRequestCycle, + requestMint, requestBurn, fundStrategy, finalizeMint } = createClientMockSetup(); // 1m STX const mintAmount = 1_000_000e6; -const requestMint = () => - simnet.callPublicFn(contracts.endpoint, 'request-mint', [Cl.uint(mintAmount)], user); - -// lock mintAmount stx and request burn of 1 stx -const requestBurn = () => - simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.list([Cl.uint(mintAmount)])], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(1e6)], user), - ]); +const mintDelay = 432; describe(contracts.endpoint, () => { it('can request mint', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - const response = requestMint(); + const response = requestMint(mintAmount); expect(response.result).toBeOk(Cl.uint(1)); }); it('can finalize mint', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); goToNextCycle(); - const finaliseErr = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); - expect(finaliseErr.result).toBeErr(Cl.uint(1006)); + const finaliseErr = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); + expect(finaliseErr.result).toBeErr(Cl.uint(7006)); simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user), ]); expect(responses[0].result).toBeOk(Cl.bool(true)); - expect(responses[1].result).toBeErr(Cl.uint(1007)); + expect(responses[1].result).toBeErr(Cl.uint(7007)); }); it('can revoke mint', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); let responses = simnet.mineBlock([ tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], bot), tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user), ]); - expect(responses[0].result).toBeErr(Cl.uint(1000)); + expect(responses[0].result).toBeErr(Cl.uint(3000)); expect(responses[1].result).toBeOk(Cl.bool(true)); goToNextCycle(); simnet.mineEmptyBlocks(mintDelay + 1); responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), ]); - expect(responses[0].result).toBeErr(Cl.uint(1007)); + expect(responses[0].result).toBeErr(Cl.uint(7007)); }); it('can request burn', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); + goToNextRequestCycle(); + expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount)); goToNextCycle(); simnet.mineEmptyBlocks(mintDelay + 1); - - const responses = requestBurn(); - expect(responses[0].result).toBeOk(Cl.uint(0)); - expect(responses[1].result).toBeOk(Cl.bool(true)); - expect(responses[2].result).toBeOk(Cl.uint(mintAmount)); - expect(responses[3].result).toBeOk(Cl.uint(mintAmount)); - console.log(cvToString(responses[4].result)); - expect(responses[4].result).toBeOk( + expect(finalizeMint(1).result).toBeOk(Cl.bool(true)); + + expect(requestBurn(mintAmount).result).toBeOk( Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) ); }); @@ -159,44 +80,40 @@ describe(contracts.endpoint, () => { it('can finalize burn', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); + + goToNextRequestCycle(); + expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount)); goToNextCycle(); simnet.mineEmptyBlocks(mintDelay + 1); - - const burnResponses = requestBurn(); - expect(burnResponses[0].result).toBeOk(Cl.uint(0)); - expect(burnResponses[1].result).toBeOk(Cl.bool(true)); - expect(burnResponses[2].result).toBeOk(Cl.uint(mintAmount)); - expect(burnResponses[3].result).toBeOk(Cl.uint(mintAmount)); - expect(burnResponses[4].result).toBeOk( + expect(finalizeMint(1).result).toBeOk(Cl.bool(true)); + + expect(requestBurn(1e6).result).toBeOk( Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) ); const responses = simnet.mineBlock([ tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot), tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user), ]); expect(responses[0].result).toBeOk(Cl.uint(1e6)); - expect(responses[1].result).toBeOk(Cl.uint(mintAmount)); - expect(responses[2].result).toBeOk(Cl.bool(true)); - expect(responses[3].result).toBeErr(Cl.uint(1007)); + expect(responses[1].result).toBeOk(Cl.bool(true)); + expect(responses[2].result).toBeErr(Cl.uint(7007)); }); it('can revoke burn', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); + + goToNextRequestCycle(); + expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount)); goToNextCycle(); simnet.mineEmptyBlocks(mintDelay + 1); - - const burnResponses = requestBurn(); - expect(burnResponses[0].result).toBeOk(Cl.uint(0)); - expect(burnResponses[1].result).toBeOk(Cl.bool(true)); - expect(burnResponses[2].result).toBeOk(Cl.uint(mintAmount)); - expect(burnResponses[3].result).toBeOk(Cl.uint(mintAmount)); - expect(burnResponses[4].result).toBeOk( + expect(finalizeMint(1).result).toBeOk(Cl.bool(true)); + + expect(requestBurn(mintAmount).result).toBeOk( Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) ); @@ -204,18 +121,18 @@ describe(contracts.endpoint, () => { tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], bot), tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user), - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), ]); expect(responses[0].result).toBeOk(Cl.uint(1e6)); - expect(responses[1].result).toBeErr(Cl.uint(1000)); + expect(responses[1].result).toBeErr(Cl.uint(3000)); expect(responses[2].result).toBeOk(Cl.bool(true)); - expect(responses[3].result).toBeErr(Cl.uint(1007)); + expect(responses[3].result).toBeErr(Cl.uint(7007)); }); it('can interact with strategies', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); const cycle = ( simnet.callReadOnlyFn( @@ -223,8 +140,8 @@ describe(contracts.endpoint, () => { 'get-reward-cycle', [Cl.uint(simnet.blockHeight)], user - ).result as ResponseOkCV - ).value.value; + ).result as UIntCV + ).value; const blocksToMine = Number( ( @@ -239,7 +156,7 @@ describe(contracts.endpoint, () => { simnet.mineEmptyBlocks(blocksToMine - 100); let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.list([Cl.uint(mintAmount)])], bot), tx.callPublicFn( contracts.manager, @@ -248,9 +165,8 @@ describe(contracts.endpoint, () => { manager ), ]); - expect(responses[0].result).toBeErr(Cl.uint(1006)); // request pending + expect(responses[0].result).toBeErr(Cl.uint(7006)); // request pending expect(responses[1].result).toBeErr(Cl.uint(3000)); // not authorized - responses[2].events.map((e: any) => console.log(e)); expect(responses[2].result).toBeOk(Cl.uint(mintAmount)); // mintAmount stx transferred, mintAmount - 1 stx locked const stxAccountFastPoolMember1 = simnet.runSnippet( @@ -262,27 +178,25 @@ describe(contracts.endpoint, () => { simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(mintAmount)], user), - tx.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(1e6)], user), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'request-burn', [Cl.uint(mintAmount)], user), + tx.callPublicFn(contracts.endpoint, 'request-burn', [Cl.uint(1e6)], user), + tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot), tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], bot), tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot), ]); expect(responses[0].result).toBeOk(Cl.bool(true)); expect(responses[1].result).toBeOk( Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) ); expect(responses[2].result).toBeErr(Cl.uint(1)); // not enough funds - expect(responses[3].result).toBeErr(Cl.uint(1006)); // request pending + expect(responses[3].result).toBeErr(Cl.uint(7006)); // request pending expect(responses[4].result).toBeErr(Cl.uint(3000)); // not authorized expect(responses[5].result).toBeOk(Cl.uint(1e6)); // refund 1 stx expect(responses[6].result).toBeOk(Cl.uint(0)); // refund 0 stx - expect(responses[7].result).toBeOk(Cl.uint(mintAmount)); // rebase mintAmount stx - expect(responses[8].result).toBeErr(Cl.uint(1006)); // request pending + expect(responses[7].result).toBeErr(Cl.uint(7006)); // request pending // refund remaining stx after unlock goToNextCycle(); @@ -290,7 +204,7 @@ describe(contracts.endpoint, () => { responses = simnet.mineBlock([ tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot), ]); expect(responses[0].result).toBeOk(Cl.uint(mintAmount - 1e6)); expect(responses[1].result).toBeOk(Cl.bool(true)); @@ -314,12 +228,12 @@ describe(contracts.endpoint, () => { goToNextCycle(); // go to the next cycle simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay - response = simnet.callPublicFn(contracts.rebase1, 'rebase', [], oracle); + response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle); expect(response.result).toBeOk(Cl.uint(0)); - response = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); + response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); expect(response.result).toBeOk(Cl.bool(true)); - response = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(2)], bot); + response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(2)], bot); expect(response.result).toBeOk(Cl.bool(true)); response = simnet.callPublicFn( @@ -351,7 +265,7 @@ describe(contracts.endpoint, () => { ], manager ); - response = simnet.callPublicFn(contracts.rebase1, 'rebase', [], oracle); + response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle); expect(response.result).toBeOk(Cl.uint(100_000_100e6)); response = simnet.callReadOnlyFn( @@ -376,7 +290,7 @@ describe(contracts.endpoint, () => { response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member3`, oracle); response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member4`, oracle); - response = simnet.callPublicFn(contracts.rebase1, 'rebase', [], oracle); + response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle); expect(response.result).toBeOk(Cl.uint(104_000_100e6)); response = simnet.callReadOnlyFn( @@ -399,17 +313,17 @@ describe(contracts.endpoint, () => { it('can set up amm pool', () => { prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); goToNextCycle(); // go to the next cycle - const finaliseErr = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); - expect(finaliseErr.result).toBeErr(Cl.uint(1006)); + const finaliseErr = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); + expect(finaliseErr.result).toBeErr(Cl.uint(7006)); simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), + tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot), tx.callPublicFn( contracts.amm, 'create-pool', @@ -427,4 +341,46 @@ describe(contracts.endpoint, () => { expect(responses[0].result).toBeOk(Cl.bool(true)); expect(responses[1].result).toBeOk(Cl.bool(true)); }); + + it('user can transfer burn nft', () => { + prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); + let response; + + // request and finalize mint for 1m STX + expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1)); + + goToNextRequestCycle(); + expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount)); + goToNextCycle(); + simnet.mineEmptyBlocks(mintDelay + 1); + expect(finalizeMint(1).result).toBeOk(Cl.bool(true)); + + expect(requestBurn(1e6).result).toBeOk( + Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) + ); + + // transfer burn of 1 stx + response = simnet.callPublicFn( + contracts.burnNft, + 'transfer', + [Cl.uint(1), Cl.standardPrincipal(user), Cl.standardPrincipal(bot)], + user + ); + + // bot is now owning the nft + expect(simnet.callReadOnlyFn(contracts.burnNft, 'get-owner', [Cl.uint(1)], user).result).toBeOk( + Cl.some(Cl.standardPrincipal(bot)) + ); + + simnet.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager), + (response = simnet.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot)); + expect(response.result).toBeOk(Cl.bool(true)); + + // check that bot received stx + expect(simnet.getAssetsMap().get('STX')?.get(bot)).toBe(100000001_000_000n); + expect(simnet.getAssetsMap().get('STX')?.get(user)).toBe(99000000_000_000n); + + // check that user has 1m - 1 liquid stx + expect(simnet.getAssetsMap().get('.token-lqstx.lqstx')?.get(user)).toBe(999999_000_000n); + }); }); diff --git a/tests/lqstx-mint-endpoint.test.ts b/tests/lqstx-mint-endpoint.test.ts deleted file mode 100644 index 88c2a93..0000000 --- a/tests/lqstx-mint-endpoint.test.ts +++ /dev/null @@ -1,359 +0,0 @@ - -// SPDX-License-Identifier: BUSL-1.1 - -import { tx } from '@hirosystems/clarinet-sdk'; -import { Cl, ResponseOkCV, UIntCV } from '@stacks/transactions'; -import { describe, expect, it } from 'vitest'; - -const accounts = simnet.getAccounts(); -const user = accounts.get('wallet_1')!; -const oracle = accounts.get('wallet_2')!; -const bot = accounts.get('wallet_3')!; -const manager = accounts.get('wallet_4')!; - -const contracts = { - endpoint: 'lqstx-mint-endpoint-v1-01', - registry: 'lqstx-mint-registry', - vault: 'lqstx-vault', - lqstx: 'token-lqstx', - vlqstx: 'token-vlqstx', - wstx: 'token-wstx', - strategy: 'mock-strategy', - rebase: 'lisa-rebase', - rebase1: 'rebase-mock', - amm: 'amm-swap-pool-v1-1', - wlqstx: 'token-wlqstx', - dao: 'lisa-dao', - boot: 'regtest-boot', - manager: 'mock-strategy-manager', - operators: 'operators', - proposal: 'mock-proposal', -}; -const mintDelay = 144; - -const prepareTest = () => - simnet.mineBlock([ - tx.callPublicFn( - contracts.dao, - 'construct', - [Cl.contractPrincipal(simnet.deployer, contracts.boot)], - simnet.deployer - ), - ]); - -const requestMint = () => - simnet.callPublicFn(contracts.endpoint, 'request-mint', [Cl.uint(100e6)], user); - -const requestBurn = (payload: Buffer) => - simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.uint(100e6)], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(100e6)], user), - ]); - -const getRewardCycle = () => { - return ( - simnet.callReadOnlyFn( - contracts.endpoint, - 'get-reward-cycle', - [Cl.uint(simnet.blockHeight)], - user - ).result as ResponseOkCV - ).value.value; -}; - -const getBlocksToStartOfCycle = (cycle: bigint) => { - return ( - Number( - ( - simnet.callReadOnlyFn( - contracts.endpoint, - 'get-first-burn-block-in-reward-cycle', - [Cl.uint(cycle)], - user - ).result as ResponseOkCV - ).value - ) - simnet.blockHeight - ); -}; - -const goToNextCycle = () => { - const cycle = getRewardCycle(); - const blocksToMine = getBlocksToStartOfCycle(cycle + 1n); - - simnet.mineEmptyBlocks(blocksToMine); -}; - -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)); - }); - - it('can finalize mint', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - - goToNextCycle(); - - const finaliseErr = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); - expect(finaliseErr.result).toBeErr(Cl.uint(1006)); - - simnet.mineEmptyBlocks(mintDelay); - - let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user), - ]); - expect(responses[0].result).toBeOk(Cl.bool(true)); - expect(responses[1].result).toBeErr(Cl.uint(1007)); - }); - - it('can revoke mint', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - - let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user), - ]); - expect(responses[0].result).toBeErr(Cl.uint(1000)); - expect(responses[1].result).toBeOk(Cl.bool(true)); - - goToNextCycle(); - simnet.mineEmptyBlocks(mintDelay); // mint-delay - - responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - ]); - expect(responses[0].result).toBeErr(Cl.uint(1007)); - }); - - it('can request burn', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - goToNextCycle(); - simnet.mineEmptyBlocks(mintDelay); - - const payload = simnet.callReadOnlyFn( - contracts.strategy, - 'create-payload', - [Cl.uint(100e6)], - manager - ).result.buffer; - const responses = requestBurn(payload); - expect(responses[0].result).toBeOk(Cl.uint(0)); - expect(responses[1].result).toBeOk(Cl.bool(true)); - expect(responses[2].result).toBeOk(Cl.uint(100e6)); - expect(responses[3].result).toBeOk(Cl.uint(100e6)); - expect(responses[4].result).toBeOk( - Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) - ); - }); - - it('can finalize burn', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - - goToNextCycle(); - simnet.mineEmptyBlocks(mintDelay); - - const payload = simnet.callReadOnlyFn( - contracts.strategy, - 'create-payload', - [Cl.uint(100e6)], - manager - ).result.buffer; - const burnResponses = requestBurn(payload); - expect(burnResponses[0].result).toBeOk(Cl.uint(0)); - expect(burnResponses[1].result).toBeOk(Cl.bool(true)); - expect(burnResponses[2].result).toBeOk(Cl.uint(100e6)); - expect(burnResponses[3].result).toBeOk(Cl.uint(100e6)); - expect(burnResponses[4].result).toBeOk( - Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) - ); - - const responses = simnet.mineBlock([ - tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.uint(100e6)], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user), - ]); - expect(responses[0].result).toBeOk(Cl.uint(100e6)); - expect(responses[1].result).toBeOk(Cl.uint(100e6)); - expect(responses[2].result).toBeOk(Cl.bool(true)); - expect(responses[3].result).toBeErr(Cl.uint(1007)); - }); - - it('can revoke burn', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - goToNextCycle(); - simnet.mineEmptyBlocks(mintDelay); - - const payload = simnet.callReadOnlyFn( - contracts.strategy, - 'create-payload', - [Cl.uint(100e6)], - manager - ).result.buffer; - const burnResponses = requestBurn(payload); - expect(burnResponses[0].result).toBeOk(Cl.uint(0)); - expect(burnResponses[1].result).toBeOk(Cl.bool(true)); - expect(burnResponses[2].result).toBeOk(Cl.uint(100e6)); - expect(burnResponses[3].result).toBeOk(Cl.uint(100e6)); - expect(burnResponses[4].result).toBeOk( - Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) - ); - - const responses = simnet.mineBlock([ - tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.uint(100e6)], manager), - tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user), - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - ]); - expect(responses[0].result).toBeOk(Cl.uint(100e6)); - expect(responses[1].result).toBeErr(Cl.uint(1000)); - expect(responses[2].result).toBeOk(Cl.bool(true)); - expect(responses[3].result).toBeErr(Cl.uint(1007)); - }); - - it('can request burn and finalized immediately', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - expect(requestMint().result).toBeOk(Cl.uint(1)); - - let response; - response = simnet.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(100e6)], user); - expect(response.result).toBeErr(Cl.uint(1)); // not enough funds - - goToNextCycle(); - simnet.mineEmptyBlocks(mintDelay); - response = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); - expect(response.result).toBeOk(Cl.bool(true)); - response = simnet.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(100e6)], user); - expect(response.result).toBeOk( - Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.buffer(new Uint8Array([1])) }) - ); - }); - - it('can interact with strategies', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - - const cycle = getRewardCycle(); - const blocksToMine = getBlocksToStartOfCycle(cycle + 1n); - simnet.mineEmptyBlocks(blocksToMine - 100); - - let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.uint(100e6)], bot), - tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.uint(100e6)], manager), - ]); - expect(responses[0].result).toBeErr(Cl.uint(1006)); - expect(responses[1].result).toBeErr(Cl.uint(1000)); - expect(responses[2].result).toBeOk(Cl.uint(100e6)); - - simnet.mineEmptyBlocks(99); // go to the next cycle - simnet.mineEmptyBlocks(mintDelay); - - responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.rebase1, 'request-burn', [Cl.uint(100e6)], user), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), - tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.uint(100e6)], bot), - tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.uint(100e6)], manager), - tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.uint(100e6)], manager), - tx.callPublicFn(contracts.rebase1, 'rebase', [], oracle), - tx.callPublicFn(contracts.rebase1, 'finalize-burn', [Cl.uint(1)], bot), - ]); - expect(responses[0].result).toBeOk(Cl.bool(true)); - expect(responses[1].result).toBeOk( - Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') }) - ); - expect(responses[2].result).toBeErr(Cl.uint(1006)); - expect(responses[3].result).toBeErr(Cl.uint(1000)); - expect(responses[4].result).toBeOk(Cl.uint(100e6)); - expect(responses[5].result).toBeErr(Cl.uint(1)); - expect(responses[6].result).toBeOk(Cl.uint(100e6)); - expect(responses[7].result).toBeOk(Cl.bool(true)); - }); - - it('can set up amm pool', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - expect(requestMint().result).toBeOk(Cl.uint(1)); - - goToNextCycle(); - - const finaliseErr = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); - expect(finaliseErr.result).toBeErr(Cl.uint(1006)); - - simnet.mineEmptyBlocks(mintDelay); - - let responses = simnet.mineBlock([ - tx.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot), - tx.callPublicFn( - contracts.amm, - 'create-pool', - [ - Cl.principal(simnet.deployer + '.' + contracts.wstx), - Cl.principal(simnet.deployer + '.' + contracts.wlqstx), - Cl.uint(1e8), - Cl.principal(user), - Cl.uint(1e8), - Cl.uint(1e8), - ], - user - ), - ]); - expect(responses[0].result).toBeOk(Cl.bool(true)); - expect(responses[1].result).toBeOk(Cl.bool(true)); - }); - - it('operator extension works', () => { - prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true))); - - let responses = simnet.mineBlock([ - tx.callPublicFn( - contracts.operators, - 'propose', - [Cl.contractPrincipal(simnet.deployer, contracts.proposal)], - bot - ), - tx.callPublicFn( - contracts.operators, - 'propose', - [Cl.contractPrincipal(simnet.deployer, contracts.proposal)], - simnet.deployer - ), - ]); - expect(responses[0].result).toBeErr(Cl.uint(1001)); - expect(responses[1].result).toBeOk(Cl.bool(false)); - - responses = simnet.mineBlock([ - tx.callPublicFn( - contracts.operators, - 'signal', - [Cl.contractPrincipal(simnet.deployer, contracts.proposal), Cl.bool(true)], - bot - ), - tx.callPublicFn( - contracts.operators, - 'signal', - [Cl.contractPrincipal(simnet.deployer, contracts.proposal), Cl.bool(true)], - manager - ), - ]); - expect(responses[0].result).toBeErr(Cl.uint(1001)); - expect(responses[1].result).toBeOk(Cl.bool(true)); - }); -}); diff --git a/tests/operators.test.ts b/tests/operators.test.ts index 0e9a808..42da7e7 100644 --- a/tests/operators.test.ts +++ b/tests/operators.test.ts @@ -1,46 +1,12 @@ - // SPDX-License-Identifier: BUSL-1.1 import { ParsedTransactionResult, tx } from '@hirosystems/clarinet-sdk'; import { BooleanCV, Cl, IntCV, SomeCV, TupleCV, UIntCV } from '@stacks/transactions'; import { describe, expect, it } from 'vitest'; +import { createClientMockSetup } from './clients/mock-client'; -const accounts = simnet.getAccounts(); -const user = accounts.get('wallet_1')!; -const oracle = accounts.get('wallet_2')!; -const bot = accounts.get('wallet_3')!; -const manager = accounts.get('wallet_4')!; -const operator3 = accounts.get('wallet_5')!; +const { contracts, prepareTest, bot, manager, operator3 } = createClientMockSetup(); -const contracts = { - endpoint: 'lqstx-mint-endpoint-v1-01', - registry: 'lqstx-mint-registry', - vault: 'lqstx-vault', - lqstx: 'token-lqstx', - vlqstx: 'token-vlqstx', - wstx: 'token-wstx', - strategy: 'mock-strategy', - rebase: 'lisa-rebase', - rebase1: 'rebase-mock', - amm: 'amm-swap-pool-v1-1', - wlqstx: 'token-wlqstx', - dao: 'lisa-dao', - boot: 'regtest-boot', - manager: 'mock-strategy-manager', - operators: 'operators', - proposal: 'mock-proposal', - proposal2: 'mock-proposal', -}; - -const prepareTest = () => - simnet.mineBlock([ - tx.callPublicFn( - contracts.dao, - 'construct', - [Cl.contractPrincipal(simnet.deployer, contracts.boot)], - simnet.deployer - ), - ]); const expectProposalDataToBe = (proposedAt: number, signals: number, executed: boolean) => { const proposalData = simnet.getMapEntry( contracts.operators, diff --git a/tests/tokens.test.ts b/tests/tokens.test.ts new file mode 100644 index 0000000..e35be5f --- /dev/null +++ b/tests/tokens.test.ts @@ -0,0 +1,22 @@ +import { Cl } from '@stacks/transactions'; +import { beforeEach, describe, expect } from 'vitest'; +import { createClientMockSetup } from './clients/mock-client'; +import { sip10Tests } from './clients/sip10-client.ts'; +const { goToNextCycle, goToNextRequestCycle, fundStrategy, requestMint, prepareTest, contracts, bot } = createClientMockSetup(); +const mintDelay = 14; + +describe('lisa token', () => { + beforeEach(() => { + prepareTest().map(r => expect(r.result).toBeOk(Cl.bool(true))); + let response = requestMint(100e6); + expect(response.result).toBeOk(Cl.uint(1)); + goToNextRequestCycle(); + expect(fundStrategy(1e6).result).toBeOk(Cl.uint(1e6)); + goToNextCycle(); + simnet.mineEmptyBlocks(mintDelay + 1); + response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot); + expect(response.result).toBeOk(Cl.bool(true)); + }); + + sip10Tests(contracts.lqstx); +});