From bbf4041ff5cc21348a941725a700e7d69bc8aab2 Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 20 Mar 2024 15:22:03 +0100 Subject: [PATCH 1/5] chore: fix error code table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c599a1..939196d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ are in the form `(err uint)` and they are unique across all contracts. | 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) | | @@ -60,7 +61,6 @@ are in the form `(err uint)` and they are unique across all contracts. | lisa-rebase | err-unauthorised | (err u3000) | | | lisa-rebase-v1-02 | err-unauthorised | (err u3000) | | | lqstx-mint-endpoint-v1-02 | err-unauthorised | (err u3000) | | -| lqstx-mint-registry | err-unauthorised | (err u3000) | | | public-pools-strategy-manager | err-unauthorised | (err u3000) | | | token-lisa | err-unauthorised | (err u3000) | | | token-lqstx | err-unauthorised | (err u3000) | | From 44fa4a2a5daa383822062c6991ebd5c0c77f287c Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 20 Mar 2024 15:42:36 +0100 Subject: [PATCH 2/5] chore: add unit test --- scripts/error-codes.ts | 89 +------------------------------------- scripts/lib/error-codes.ts | 87 +++++++++++++++++++++++++++++++++++++ tests/errors.test.ts | 9 ++++ 3 files changed, 98 insertions(+), 87 deletions(-) create mode 100644 scripts/lib/error-codes.ts create mode 100644 tests/errors.test.ts diff --git a/scripts/error-codes.ts b/scripts/error-codes.ts index 9a9d041..1dfdc55 100644 --- a/scripts/error-codes.ts +++ b/scripts/error-codes.ts @@ -1,92 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 import { initSimnet } from '@hirosystems/clarinet-sdk'; -import { readFileSync, writeFileSync } from 'fs'; -const readmeFile = './README.md'; +import { createErrorsTable } from './lib/error-codes.ts'; const manifestFile = './Clarinet.toml'; - -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']; - const simnet = await initSimnet(manifestFile); -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) : ' '; -} - -function createErrorsTable() { - 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 = process.env.EXTRACT_CHECK && readme; - - for (const [contractId, abi] of simnet.getContractsInterfaces()) { - if (isTestContract(contractId)) continue; - console.log(abi); - 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}`); -} - -console.log('start'); -createErrorsTable(); +createErrorsTable(simnet, false); diff --git a/scripts/lib/error-codes.ts b/scripts/lib/error-codes.ts new file mode 100644 index 0000000..04e9adf --- /dev/null +++ b/scripts/lib/error-codes.ts @@ -0,0 +1,87 @@ +// 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, abi] of simnet.getContractsInterfaces()) { + if (isTestContract(contractId)) continue; + console.log(abi); + 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/tests/errors.test.ts b/tests/errors.test.ts new file mode 100644 index 0000000..3bdbac2 --- /dev/null +++ b/tests/errors.test.ts @@ -0,0 +1,9 @@ +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 + }); +}); From 582ef20cb4569cbf0bbfc77b6ecb3279bc2ba5a9 Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 20 Mar 2024 15:42:56 +0100 Subject: [PATCH 3/5] chore: add license --- tests/errors.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/errors.test.ts b/tests/errors.test.ts index 3bdbac2..aa666ae 100644 --- a/tests/errors.test.ts +++ b/tests/errors.test.ts @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: BUSL-1.1 + import { describe, it } from 'vitest'; import { createErrorsTable } from '../scripts/lib/error-codes.ts'; From 9de9f1a454252527be05522c5b52bf74a9a9e7fc Mon Sep 17 00:00:00 2001 From: friedger Date: Thu, 21 Mar 2024 01:36:24 +0100 Subject: [PATCH 4/5] feat: add nft for mint and burn requests --- Clarinet.toml | 14 ++ README.md | 17 ++- contracts/aux/li-stx-burn-nft.clar | 136 ++++++++++++++++++ contracts/aux/li-stx-mint-nft.clar | 136 ++++++++++++++++++ contracts/extensions/lqstx-mint-endpoint.clar | 6 + deployments/default.simnet-plan.yaml | 26 +++- scripts/lib/error-codes.ts | 3 +- tests/lqstx-mint-endpoint.test.ts | 5 +- 8 files changed, 335 insertions(+), 8 deletions(-) create mode 100644 contracts/aux/li-stx-burn-nft.clar create mode 100644 contracts/aux/li-stx-mint-nft.clar diff --git a/Clarinet.toml b/Clarinet.toml index f57bae7..b21ee74 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 @@ -191,6 +197,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 diff --git a/README.md b/README.md index 939196d..e3bc89e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ are in the form `(err uint)` and they are unique across all 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. | @@ -29,8 +30,9 @@ are in the form `(err uint)` and they are unique across all contracts. ### 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) | | @@ -58,6 +60,8 @@ are in the form `(err uint)` and they are unique across all contracts. | 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) | | | lisa-rebase-v1-02 | err-unauthorised | (err u3000) | | | lqstx-mint-endpoint-v1-02 | err-unauthorised | (err u3000) | | @@ -113,11 +117,22 @@ are in the form `(err uint)` and they are unique across all contracts. | 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..f0fe25f --- /dev/null +++ b/contracts/aux/li-stx-burn-nft.clar @@ -0,0 +1,136 @@ +;; li-stx-burn +;; contractType: public + +(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) + +(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-public (mint (id uint) (amount uint)) + (let ((current-balance (get-balance tx-sender))) + (try! (is-dao-or-extension)) + (var-set last-id id) + (map-set token-count tx-sender (+ current-balance u1)) + (nft-mint? li-stx-burn id tx-sender))) + +(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) u1))) + +(define-read-only (get-token-uri (token-id uint)) + (ok (some (concat (concat (var-get ipfs-root) "{id}") ".json")))) + +;; Non-custodial marketplace extras +(use-trait commission-trait 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait.commission) + +(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..534f2ad --- /dev/null +++ b/contracts/aux/li-stx-mint-nft.clar @@ -0,0 +1,136 @@ +;; li-stx-mint +;; contractType: public + +(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) + +(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-public (mint (id uint) (amount uint)) + (let ((current-balance (get-balance tx-sender))) + (try! (is-dao-or-extension)) + (var-set last-id id) + (map-set token-count tx-sender (+ current-balance u1)) + (nft-mint? li-stx-mint id tx-sender))) + +(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) u1))) + +(define-read-only (get-token-uri (token-id uint)) + (ok (some (concat (concat (var-get ipfs-root) "{id}") ".json")))) + +;; Non-custodial marketplace extras +(use-trait commission-trait 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.commission-trait.commission) + +(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/lqstx-mint-endpoint.clar b/contracts/extensions/lqstx-mint-endpoint.clar index be067fe..f906ab9 100644 --- a/contracts/extensions/lqstx-mint-endpoint.clar +++ b/contracts/extensions/lqstx-mint-endpoint.clar @@ -129,6 +129,7 @@ (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)) (print { type: "mint-request", id: request-id, details: request-details }) (ok request-id))) @@ -144,6 +145,7 @@ (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)) (ok true))) (define-public (revoke-burn (request-id uint)) @@ -160,6 +162,7 @@ (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))) + (try! (contract-call? .li-stx-burn-nft burn request-id)) (ok true))) ;; governance calls @@ -207,6 +210,7 @@ (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))) + (try! (contract-call? .li-stx-mint-nft burn request-id)) (ok true))) (define-public (finalize-mint-many (request-ids (list 1000 uint))) @@ -224,6 +228,7 @@ (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)))) + (try! (contract-call? .li-stx-burn-nft mint request-id amount)) (print { type: "burn-request", id: request-id, details: request-details }) (ok { request-id: request-id, status: PENDING }))) @@ -240,6 +245,7 @@ (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)))) + (try! (contract-call? .li-stx-burn-nft burn request-id)) (ok true))) (define-public (finalize-burn-many (request-ids (list 1000 uint))) diff --git a/deployments/default.simnet-plan.yaml b/deployments/default.simnet-plan.yaml index 9dcdf2d..1311056 100644 --- a/deployments/default.simnet-plan.yaml +++ b/deployments/default.simnet-plan.yaml @@ -49,6 +49,16 @@ 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 @@ -295,6 +305,16 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/boot.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 @@ -345,6 +365,9 @@ 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: rebase-1 emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM @@ -355,9 +378,6 @@ plan: emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/rules/rebase-1.clar clarity-version: 2 - epoch: "2.4" - - id: 3 - transactions: - emulated-contract-publish: contract-name: rebase-mock emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM diff --git a/scripts/lib/error-codes.ts b/scripts/lib/error-codes.ts index 04e9adf..bfe942f 100644 --- a/scripts/lib/error-codes.ts +++ b/scripts/lib/error-codes.ts @@ -28,9 +28,8 @@ export function createErrorsTable(simnet: Simnet, extractCheck: boolean) { const compareReadme = extractCheck && readme; - for (const [contractId, abi] of simnet.getContractsInterfaces()) { + for (const [contractId] of simnet.getContractsInterfaces()) { if (isTestContract(contractId)) continue; - console.log(abi); const source = simnet.getContractSource(contractId); if (!source) continue; const errorConstants = source.matchAll(constantErrRegex); diff --git a/tests/lqstx-mint-endpoint.test.ts b/tests/lqstx-mint-endpoint.test.ts index 84d14d2..6cf4eb5 100644 --- a/tests/lqstx-mint-endpoint.test.ts +++ b/tests/lqstx-mint-endpoint.test.ts @@ -398,9 +398,10 @@ describe(contracts.endpoint, () => { console.log(response.events[1].data); expect(response.result).toBeOk(Cl.uint(1)); expect(response.events[0].event).toBe('stx_transfer_event'); - expect(response.events[1].event).toBe('print_event'); + expect(response.events[1].event).toBe('nft_mint_event'); + expect(response.events[2].event).toBe('print_event'); expect( - ((response.events[1].data.value as TupleCV).data.details as TupleCV).data['requested-at'] + ((response.events[2].data.value as TupleCV).data.details as TupleCV).data['requested-at'] ).toBeUint(2); }); }); From 40806356e306874a34b93b8147e3d8f9c9c43923 Mon Sep 17 00:00:00 2001 From: friedger Date: Thu, 21 Mar 2024 01:57:51 +0100 Subject: [PATCH 5/5] chore: add tests for events --- README.md | 4 +--- tests/lqstx-mint-endpoint-with-public-pools.test.ts | 1 - tests/lqstx-mint-endpoint.test.ts | 4 +++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e3bc89e..1034166 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,8 @@ are in the form `(err uint)` and they are unique across all contracts. ### 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) | | @@ -132,7 +131,6 @@ are in the form `(err uint)` and they are unique across all contracts. | 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/tests/lqstx-mint-endpoint-with-public-pools.test.ts b/tests/lqstx-mint-endpoint-with-public-pools.test.ts index b003e53..c454fa3 100644 --- a/tests/lqstx-mint-endpoint-with-public-pools.test.ts +++ b/tests/lqstx-mint-endpoint-with-public-pools.test.ts @@ -250,7 +250,6 @@ describe(contracts.endpoint, () => { ]); 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( diff --git a/tests/lqstx-mint-endpoint.test.ts b/tests/lqstx-mint-endpoint.test.ts index 6cf4eb5..48e50a4 100644 --- a/tests/lqstx-mint-endpoint.test.ts +++ b/tests/lqstx-mint-endpoint.test.ts @@ -254,10 +254,13 @@ describe(contracts.endpoint, () => { simnet.mineEmptyBlocks(mintDelay); response = simnet.callPublicFn(contracts.rebase1, 'finalize-mint', [Cl.uint(1)], bot); expect(response.result).toBeOk(Cl.bool(true)); + expect(response.events[2].event).toBe('nft_burn_event'); 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])) }) ); + expect(response.events[7].event).toBe('nft_mint_event'); + expect(response.events[17].event).toBe('nft_burn_event'); }); it('can interact with strategies', () => { @@ -395,7 +398,6 @@ describe(contracts.endpoint, () => { expect(getRequestCycle()).toBe(2n); const response = requestMint(); - console.log(response.events[1].data); expect(response.result).toBeOk(Cl.uint(1)); expect(response.events[0].event).toBe('stx_transfer_event'); expect(response.events[1].event).toBe('nft_mint_event');