From c9185a1400032780c4e11c58f3de6d14073acbef Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Thu, 28 Sep 2023 19:49:42 +0800 Subject: [PATCH 01/17] chore: add mock section to clarity-bitcoin-v4 copy --- contracts/clarity-bitcoin-v4.clar | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/clarity-bitcoin-v4.clar b/contracts/clarity-bitcoin-v4.clar index 9ccf130..7968b39 100644 --- a/contracts/clarity-bitcoin-v4.clar +++ b/contracts/clarity-bitcoin-v4.clar @@ -369,8 +369,21 @@ nbits: (get uint32 parsed-nbits), nonce: (get uint32 parsed-nonce)}))) +;; (define-read-only (get-bc-h-hash (bh uint)) +;; (get-burn-block-info? header-hash bh)) + +;; MOCK section +(define-constant DEBUG-MODE true) + +(define-map mock-burnchain-header-hashes uint (buff 32)) + +(define-public (mock-add-burnchain-block-header-hash (burn-height uint) (hash (buff 32))) + (ok (map-set mock-burnchain-header-hashes burn-height hash))) + (define-read-only (get-bc-h-hash (bh uint)) - (get-burn-block-info? header-hash bh)) + (if DEBUG-MODE (map-get? mock-burnchain-header-hashes bh) (get-burn-block-info? header-hash bh))) + +;; END MOCK section ;; Verify that a block header hashes to a burnchain header hash at a given height. ;; Returns true if so; false if not. From bb49a1f63a73a45bacb087ff846dc67d5cf3b83b Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Thu, 28 Sep 2023 19:50:35 +0800 Subject: [PATCH 02/17] WIP: clarity-bitcoin-v5, calculate TXID for wtx, additional tx validity checks for parse-tx and parse-wtx --- Clarinet.toml | 28 +++++---- contracts/clarity-bitcoin-v5.clar | 1 + contracts/clarity-bitcoin.clar | 61 +++++++++++-------- .../tests/clarity-bitcoin_segwit_test.clar | 24 ++++---- contracts/tests/clarity-bitcoin_test.clar | 2 +- 5 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 contracts/clarity-bitcoin-v5.clar diff --git a/Clarinet.toml b/Clarinet.toml index eeb5022..dd9582e 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -5,7 +5,6 @@ authors = [] telemetry = true cache_dir = './.cache' requirements = [] - [contracts.clarity-bitcoin] path = 'contracts/clarity-bitcoin.clar' clarity_version = 2 @@ -16,6 +15,21 @@ path = 'contracts/clarity-bitcoin-helper.clar' clarity_version = 2 epoch = 2.1 +[contracts.clarity-bitcoin-v5] +path = 'contracts/clarity-bitcoin-v5.clar' +clarity_version = 2 +epoch = 2.1 + +[contracts.clarity-bitcoin_segwit_test] +path = 'contracts/tests/clarity-bitcoin_segwit_test.clar' +clarity_version = 2 +epoch = 2.1 + +[contracts.clarity-bitcoin_test] +path = 'contracts/tests/clarity-bitcoin_test.clar' +clarity_version = 2 +epoch = 2.1 + [contracts.helper] path = 'contracts/helper.clar' clarity_version = 2 @@ -40,18 +54,6 @@ epoch = 2.1 path = 'contracts/stx-oracle.clar' clarity_version = 2 epoch = 2.1 - - -[contracts.clarity-bitcoin_test] -path = 'contracts/tests/clarity-bitcoin_test.clar' -clarity_version = 2 -epoch = 2.1 - -[contracts.clarity-bitcoin_segwit_test] -path = 'contracts/tests/clarity-bitcoin_segwit_test.clar' -clarity_version = 2 -epoch = 2.1 - [repl.analysis] passes = ['check_checker'] diff --git a/contracts/clarity-bitcoin-v5.clar b/contracts/clarity-bitcoin-v5.clar new file mode 100644 index 0000000..9d55ab4 --- /dev/null +++ b/contracts/clarity-bitcoin-v5.clar @@ -0,0 +1 @@ +;; TODO: to be coped from clarity-bitcoin once v5 is ready. diff --git a/contracts/clarity-bitcoin.clar b/contracts/clarity-bitcoin.clar index 7968b39..e95c821 100644 --- a/contracts/clarity-bitcoin.clar +++ b/contracts/clarity-bitcoin.clar @@ -1,7 +1,5 @@ ;; @contract stateless contract to verify bitcoin transaction -;; @version 4 - -;; version 4 adds support for segwit transactions +;; @version 5 ;; Error codes (define-constant ERR-OUT-OF-BOUNDS u1) @@ -15,6 +13,8 @@ (define-constant ERR-TOO-MANY-WITNESSES u9) (define-constant ERR-INVALID-COMMITMENT u10) (define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) +(define-constant ERR-NOT-SEGWIT-TRANSACTION u12) +(define-constant ERR-LEFTOVER-DATA u13) ;; ;; Helper functions to parse bitcoin transactions @@ -263,11 +263,13 @@ ;; ;; Parses a Bitcoin transaction, with up to 8 inputs and 8 outputs, with scriptSigs of up to 256 bytes each, and with scriptPubKeys up to 128 bytes. +;; It will also calculate and return the TXID if calculate-txid is set to true. ;; Returns a tuple structured as follows on success: ;; (ok { ;; version: uint, ;; tx version ;; segwit-marker: uint, ;; segwit-version: uint, +;; txid: (optional (buff 32)) ;; ins: (list 8 ;; { ;; outpoint: { ;; pointer to the utxo this input consumes @@ -289,7 +291,9 @@ ;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey or scriptSig that's too long to parse. ;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight inputs to read. ;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight outputs to read. -(define-read-only (parse-wtx (tx (buff 4096))) +;; Returns (err ERR-NOT-SEGWIT-TRANSACTION) if tx is not a segwit transaction. +;; Returns (err ERR-LEFTOVER-DATA) if the tx buffer contains leftover data at the end. +(define-read-only (parse-wtx (tx (buff 4096)) (calculate-txid bool)) (let ((ctx { txbuff: tx, index: u0}) (parsed-version (try! (read-uint32 ctx))) (parsed-segwit-marker (try! (read-uint8 (get ctx parsed-version)))) @@ -299,14 +303,24 @@ (parsed-witnesses (try! (read-witnesses (get ctx parsed-txouts) (len (get txins parsed-txins))))) (parsed-locktime (try! (read-uint32 (get ctx parsed-witnesses)))) ) - (ok {version: (get uint32 parsed-version), - segwit-marker: (get uint8 parsed-segwit-marker), - segwit-version: (get uint8 parsed-segwit-version), - ins: (get txins parsed-txins), - outs: (get txouts parsed-txouts), - witnesses: (get witnesses parsed-witnesses), - locktime: (get uint32 parsed-locktime) - }))) + (asserts! (and (is-eq (get uint8 parsed-segwit-marker) u0) (is-eq (get uint8 parsed-segwit-version) u1)) (err ERR-NOT-SEGWIT-TRANSACTION)) + (asserts! (is-eq (len tx) (get index (get ctx parsed-locktime))) (err ERR-LEFTOVER-DATA)) + (ok {version: (get uint32 parsed-version), + segwit-marker: (get uint8 parsed-segwit-marker), + segwit-version: (get uint8 parsed-segwit-version), + ins: (get txins parsed-txins), + outs: (get txouts parsed-txouts), + txid: (if calculate-txid + (some (reverse-buff32 (sha256 (sha256 + (concat + (unwrap-panic (slice? tx u0 u4)) + (concat + (unwrap-panic (slice? tx (get index (get ctx parsed-segwit-version)) (get index (get ctx parsed-txouts)))) + (unwrap-panic (slice? tx (get index (get ctx parsed-witnesses)) (len tx))))))))) + none), + witnesses: (get witnesses parsed-witnesses), + locktime: (get uint32 parsed-locktime) + }))) ;; ;; Parses a Bitcoin transaction, with up to 8 inputs and 8 outputs, with scriptSigs of up to 256 bytes each, and with scriptPubKeys up to 128 bytes. @@ -333,16 +347,18 @@ ;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey or scriptSig that's too long to parse. ;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight inputs to read. ;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight outputs to read. +;; Returns (err ERR-LEFTOVER-DATA) if the tx buffer contains leftover data at the end. (define-read-only (parse-tx (tx (buff 4096))) (let ((ctx { txbuff: tx, index: u0}) - (parsed-version (try! (read-uint32 ctx))) - (parsed-txins (try! (read-txins (get ctx parsed-version)))) - (parsed-txouts (try! (read-txouts (get ctx parsed-txins)))) - (parsed-locktime (try! (read-uint32 (get ctx parsed-txouts))))) - (ok {version: (get uint32 parsed-version), - ins: (get txins parsed-txins), - outs: (get txouts parsed-txouts), - locktime: (get uint32 parsed-locktime)}))) + (parsed-version (try! (read-uint32 ctx))) + (parsed-txins (try! (read-txins (get ctx parsed-version)))) + (parsed-txouts (try! (read-txouts (get ctx parsed-txins)))) + (parsed-locktime (try! (read-uint32 (get ctx parsed-txouts))))) + (asserts! (is-eq (len tx) (get index (get ctx parsed-locktime))) (err ERR-LEFTOVER-DATA)) + (ok {version: (get uint32 parsed-version), + ins: (get txins parsed-txins), + outs: (get txouts parsed-txouts), + locktime: (get uint32 parsed-locktime)}))) ;; Parse a Bitcoin block header. ;; Returns a tuple structured as folowed on success: @@ -369,9 +385,6 @@ nbits: (get uint32 parsed-nbits), nonce: (get uint32 parsed-nonce)}))) -;; (define-read-only (get-bc-h-hash (bh uint)) -;; (get-burn-block-info? header-hash bh)) - ;; MOCK section (define-constant DEBUG-MODE true) @@ -568,4 +581,4 @@ ;; verify witness merkle tree (asserts! (try! (verify-merkle-proof reversed-wtxid witness-merkle-root { tx-index: tx-index, hashes: wproof, tree-depth: tree-depth })) (err ERR-WITNESS-TX-NOT-IN-COMMITMENT)) - (ok wtxid)))) + (ok wtxid)))) \ No newline at end of file diff --git a/contracts/tests/clarity-bitcoin_segwit_test.clar b/contracts/tests/clarity-bitcoin_segwit_test.clar index e733eb3..586b6f4 100644 --- a/contracts/tests/clarity-bitcoin_segwit_test.clar +++ b/contracts/tests/clarity-bitcoin_segwit_test.clar @@ -1,6 +1,8 @@ (define-constant test-contract-principal (as-contract tx-sender)) (define-constant zero-address 'SP000000000000000000002Q6VF78) +(define-constant ERR-NOT-SEGWIT-TRANSACTION u12) + (define-public (add-burnchain-block-header-hash (burn-height uint) (header (buff 80))) (contract-call? .clarity-bitcoin mock-add-burnchain-block-header-hash burn-height (contract-call? .clarity-bitcoin reverse-buff32 (sha256 (sha256 header)))) ) @@ -56,7 +58,7 @@ (witness-merkle-root 0x15424423c2614c23aceec8d732b5330c21ff3a306f52243fbeef47a192c65c86) (witness-reserved-data 0x0000000000000000000000000000000000000000000000000000000000000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact @@ -88,7 +90,7 @@ ;; txid: 28f35a9c67a275e1cf8678c3f030f005b4c85ff8fcbf1742035ec39ebbdb5164 (raw-coinbase-tx 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1603e319251300000062696e67626f6e67210001000000ffffffff02bbb329000000000017a9148746e880ec2156d6b527e1962456cff7720695df870000000000000000266a24aa21a9edcbc79776b99d0cc0c2d597ce40c92958ba98f3adb6e59ed27c3a03d1c1703e1c00000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height @@ -120,7 +122,7 @@ ;; txid: 84f8a86015b0a95763bb16128ff301a60f668356548405178953ab1e4cbff36e (raw-coinbase-tx 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff32034f1a2500045684506404c862b40c0c11c14a6400000000000000000a636b706f6f6c0e2f6d696e65642062792072736b2fffffffff0317ea2b00000000001976a914ec2f9ffaba0d68ea6bd7c25cedfe2ae710938e6088ac0000000000000000266a24aa21a9ede2ee16ef0a8c0fd6ccb8c78f297199f0f143629121801c46b1e1487c0123cb4b00000000000000002a6a52534b424c4f434b3acb9b4841f625100fb87055003991835f3183bff7668865e93e1f1118003a492d00000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact @@ -152,7 +154,7 @@ ;; txid: c770364da721e34eeb1a67f09c986fa5e4f13f9819df727e604691f42f5340a1 (raw-coinbase-tx 0x02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2503831a250000000000000000000000000000000002000000000000073c7500000000000000ffffffff02be402500000000001976a91455a2e914aeb9729b4cd265248cb67a865eae95fd88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (match (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact @@ -187,7 +189,7 @@ ;; txid: e2798f96e3584be98f0b6eb6833dbf8284f95c4bb183f425bf409eb3ecc4bf6b (raw-coinbase-tx 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1b03c917250477664664004000009be40b0000084d617261636f726500000000030000000000000000266a24aa21a9edb675067096401108d0bd081d693fb4adae204ea41a2c444ad79826a2d9f0bb250000000000000000fd80017b226964223a6e756c6c2c22726573756c74223a7b2268617368223a2239346562366639633664633130396239646630396333306237616561613065383538656165626536343831346138363036383962383065633931313634383864222c22636861696e6964223a312c2270726576696f7573626c6f636b68617368223a2232663566306161663139633139623138313433333839663864643330353234323539323937393137613562396330613864353432366362323233373731336364222c22636f696e6261736576616c7565223a3632353030303030302c2262697473223a223230376666666666222c22686569676874223a3437382c225f746172676574223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666666663766222c226d65726b6c655f73697a65223a312c226d65726b6c655f6e6f6e6365223a323539363939363136327d2c226572726f72223a6e756c6c7db87a2500000000001976a914bb94b2b8ce1719201bf0346d79ee0f60c9d2700088ac00000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (match (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact @@ -222,7 +224,7 @@ (witness-merkle-root 0x15424423c2614c23aceec8d732b5330c21ff3a306f52243fbeef47a192c65c86) (witness-reserved-data 0x0000000000000000000000000000000000000000000000000000000000000000) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height @@ -244,16 +246,16 @@ (let ( (segwit-tx 0x020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03016500ffffffff0200f2052a010000002251205444612a122cd09b4b3457d46c149a23d8685fb7d3aac61ea7eee8449555293b0000000000000000266a24aa21a9edab124e70e4a18e72f2ac6f635aebb08862d5b5b1c0519cd9f3222a24a48482560120000000000000000000000000000000000000000000000000000000000000000000000000) (non-segwit-tx 0x02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2503831a250000000000000000000000000000000002000000000000073c7500000000000000ffffffff02be402500000000001976a91455a2e914aeb9729b4cd265248cb67a865eae95fd88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900000000) - (parsed-tx-with-segwit-function (try! (contract-call? .clarity-bitcoin parse-wtx non-segwit-tx))) - (parsed-tx-with-non-segwit-function (try! (contract-call? .clarity-bitcoin parse-tx non-segwit-tx))) + (parsed-tx-with-segwit-function (contract-call? .clarity-bitcoin parse-wtx non-segwit-tx false)) + (parsed-tx-with-non-segwit-function (contract-call? .clarity-bitcoin parse-tx non-segwit-tx)) (correct-parsed-tx { ins: (list { outpoint: { hash: 0x0000000000000000000000000000000000000000000000000000000000000000, index: u4294967295 }, scriptSig: 0x03831a250000000000000000000000000000000002000000000000073c7500000000000000, sequence: u4294967295 }), locktime: u0, outs: (list { scriptPubKey: 0x76a91455a2e914aeb9729b4cd265248cb67a865eae95fd88ac, value: u2441406 } { scriptPubKey: 0x6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9, value: u0 }), version: u2 }) - (wrong-parsed-tx { ins: (list), locktime: u0, outs: (list), segwit-marker: u1, segwit-version: u0, version: u2, witnesses: (list) }) + ;;(wrong-parsed-tx { ins: (list), locktime: u0, outs: (list), segwit-marker: u1, segwit-version: u0, version: u2, witnesses: (list) }) ) (ok (asserts! (and - (is-eq parsed-tx-with-segwit-function wrong-parsed-tx) - (is-eq parsed-tx-with-non-segwit-function correct-parsed-tx) + (is-eq parsed-tx-with-segwit-function (err ERR-NOT-SEGWIT-TRANSACTION)) + (is-eq parsed-tx-with-non-segwit-function (ok correct-parsed-tx)) ) (err u1))) ) diff --git a/contracts/tests/clarity-bitcoin_test.clar b/contracts/tests/clarity-bitcoin_test.clar index 5a4a823..e2effe7 100644 --- a/contracts/tests/clarity-bitcoin_test.clar +++ b/contracts/tests/clarity-bitcoin_test.clar @@ -84,7 +84,7 @@ ;; block id: 00000000096ae97d41a543592c3680477444acdc86c877aeb4832744691cb94b (raw-block-header 0x000000208af1a70ab2ed062c7ac53eb56b053498db50f0d9c41f0dc8a5efcb1b000000007b64b9e16eb97b1fb32977aa00e2cb7418856b1e794e232be4f3b4b0512cb31256845064ffff001dc3cdbab0) (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) (contract-call? .clarity-bitcoin was-tx-mined-compact From fc3a80a853bd7a8bd51301c81849214afd462188 Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 21 Nov 2023 15:38:35 +0100 Subject: [PATCH 03/17] fix: add calculate txid in bitcoin client --- tests/clarity-bitcoin_parse_wtx_test.ts | 2 ++ tests/clients/clarity-bitcoin-client.ts | 4 ++-- tests/utils.ts | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/clarity-bitcoin_parse_wtx_test.ts b/tests/clarity-bitcoin_parse_wtx_test.ts index 7c1315f..a85c39a 100644 --- a/tests/clarity-bitcoin_parse_wtx_test.ts +++ b/tests/clarity-bitcoin_parse_wtx_test.ts @@ -13,6 +13,7 @@ Clarinet.test({ let block = chain.mineBlock([ parseWtx( "0x0200000000010218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d50140a50417be5a056f63e052294cb20643f83038d5cd90e2f90c1ad3f80180026cb99d78cd4480fadbbc5b9cad5fb2248828fb21549e7cb3f7dbd7aefd2d541bd34f0140acde555b7689eae41d5ccf872bb32a270893bdaa1defc828b76c282f6c87fc387d7d4343c5f7288cfd9aa5da0765c7740ca97e44a0205a1abafa279b530d5fe36d182500", + true, deployer ), ]); @@ -51,6 +52,7 @@ Clarinet.test({ }, ], version: 2, + txid: "3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084" }); }, }); diff --git a/tests/clients/clarity-bitcoin-client.ts b/tests/clients/clarity-bitcoin-client.ts index 3470da0..468ab27 100644 --- a/tests/clients/clarity-bitcoin-client.ts +++ b/tests/clients/clarity-bitcoin-client.ts @@ -8,8 +8,8 @@ export function parseTx(tx: string, deployer: Account) { return Tx.contractCall("clarity-bitcoin", "parse-tx", [tx], deployer.address); } -export function parseWtx(wtx: string, deployer: Account) { - return Tx.contractCall("clarity-bitcoin", "parse-wtx", [wtx], deployer.address); +export function parseWtx(wtx: string, calculateTxid: boolean, deployer: Account) { + return Tx.contractCall("clarity-bitcoin", "parse-wtx", [wtx, types.bool(calculateTxid)], deployer.address); } export function parseBlockHeader(headerBuff: Uint8Array, deployer: Account) { diff --git a/tests/utils.ts b/tests/utils.ts index 2d13566..bdbd9e6 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -35,6 +35,7 @@ export interface TxObject { scriptPubKey: string; value: number; }[]; + txid?: string; } export function expectHeaderObject( @@ -85,4 +86,8 @@ export function expectTxObject(block: any, expectedTxObject: TxObject) { ); outObject.value.expectUint(expectedTxObject.outs[index].value); } + + if (expectedTxObject.txid) { + resultTxObject.txid.expectSome().expectBuff(hexToBytes(expectedTxObject.txid)); + } } From 329119edb0b21d893e2e6574db219e0b3cee3a3b Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 11:36:46 +0100 Subject: [PATCH 04/17] chore: use clarinet-sdk (WIP) --- .gitignore | 9 + .vscode/settings.json | 7 +- package-lock.json | 1812 +++++++++++++++++++++ package.json | 22 + tests/clarity-bitcoin.test.ts | 456 ++++++ tests/clarity-bitcoin_parse.test.ts | 207 +++ tests/clarity-bitcoin_parse_test.ts | 222 --- tests/clarity-bitcoin_parse_wtx.test.ts | 58 + tests/clarity-bitcoin_parse_wtx_test.ts | 58 - tests/clarity-bitcoin_test.ts | 420 ----- tests/clients/clarity-bitcoin-client.ts | 37 +- tests/deps.ts | 22 - tests/send-to-first-input-compact.test.ts | 50 + tests/send-to-first-input-compact_test.ts | 47 - tests/send-to-first-input.test.ts | 56 + tests/send-to-first-input_test.ts | 54 - tests/utils.ts | 75 +- tsconfig.json | 25 + vitest.config.js | 36 + 19 files changed, 2798 insertions(+), 875 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/clarity-bitcoin.test.ts create mode 100644 tests/clarity-bitcoin_parse.test.ts delete mode 100644 tests/clarity-bitcoin_parse_test.ts create mode 100644 tests/clarity-bitcoin_parse_wtx.test.ts delete mode 100644 tests/clarity-bitcoin_parse_wtx_test.ts delete mode 100644 tests/clarity-bitcoin_test.ts delete mode 100644 tests/deps.ts create mode 100644 tests/send-to-first-input-compact.test.ts delete mode 100644 tests/send-to-first-input-compact_test.ts create mode 100644 tests/send-to-first-input.test.ts delete mode 100644 tests/send-to-first-input_test.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.js diff --git a/.gitignore b/.gitignore index 3302c38..3d8893c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,12 @@ contracts/clarity-bitcoin-v2.clar .test .coverage + +# Ignore Node and NPM files. Added by the clarinet-sdk migration. +logs +*.log +npm-debug.log* +coverage +*.info +costs-reports.json +node_modules \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 94da4be..d9910c3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ - { - "deno.enable": true, - "files.eol": "\n" -} + "deno.enable": false, + "files.eol": "\n" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..006fe8d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1812 @@ +{ + "name": "clarity-bitcoin-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "clarity-bitcoin-tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@hirosystems/clarinet-sdk": "^1.1.0", + "@stacks/transactions": "^6.9.0", + "chokidar-cli": "^3.0.0", + "typescript": "^5.2.2", + "vite": "^4.4.9", + "vitest": "^0.34.4", + "vitest-environment-clarinet": "^1.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hirosystems/clarinet-sdk": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk/-/clarinet-sdk-1.1.0.tgz", + "integrity": "sha512-O4iP+eqc2jtbCJcndC22l12ODIi8GxwUcWhWaltvnPBn+PXqCLxDqNU78C6iDCfPp/Ro2fcJy9z27KNqnu+A9g==", + "dependencies": { + "@hirosystems/clarinet-sdk-wasm": "^1.1.0", + "@stacks/transactions": "^6.9.0", + "kolorist": "^1.8.0", + "prompts": "^2.4.2", + "vitest": "^0.34.5", + "yargs": "^17.7.2" + }, + "bin": { + "clarinet-sdk": "dist/cjs/bin/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@hirosystems/clarinet-sdk-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk-wasm/-/clarinet-sdk-wasm-1.1.0.tgz", + "integrity": "sha512-hGf2Ib6qYVnhV2+idW1GuOsh1Fom4fhp+QYjxHmfGQvx9ptSb037/4YVlep+jbO4hKXHHF2uQJgKMRPwVrtN2g==" + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@stacks/common": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.8.1.tgz", + "integrity": "sha512-ewL9GLZNQYa5a/3K4xSHlHIgHkD4rwWW/QEaPId8zQIaL+1O9qCaF4LX9orNQeOmEk8kvG0x2xGV54fXKCZeWQ==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^18.0.4" + } + }, + "node_modules/@stacks/network": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.8.1.tgz", + "integrity": "sha512-n8M25pPbLqpSBctabtsLOTBlmPvm9EPQpTI//x7HLdt5lEjDXxauEQt0XGSvDUZwecrmztqt9xNxlciiGApRBw==", + "dependencies": { + "@stacks/common": "^6.8.1", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/transactions": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.9.0.tgz", + "integrity": "sha512-hSs9+0Ew++GwMZMgPObOx0iVCQRxkiCqI+DHdPEikAmg2utpyLh2/txHOjfSIkQHvcBfJJ6O5KphmxDP4gUqiA==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.8.1", + "@stacks/network": "^6.8.1", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/@types/bn.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==" + }, + "node_modules/@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/node": { + "version": "18.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.12.tgz", + "integrity": "sha512-G7slVfkwOm7g8VqcEF1/5SXiMjP3Tbt+pXDU3r/qhlM2KkGm786DUD4xyMA2QzEElFrv/KZV9gjygv4LnkpbMQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c32check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/c32check/-/c32check-2.0.0.tgz", + "integrity": "sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==", + "dependencies": { + "@noble/hashes": "^1.1.2", + "base-x": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz", + "integrity": "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==", + "dependencies": { + "chokidar": "^3.5.2", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "yargs": "^13.3.0" + }, + "bin": { + "chokidar": "index.js" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/chokidar-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/chokidar-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/chokidar-cli/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/chokidar-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "node_modules/std-env": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.5.0.tgz", + "integrity": "sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==" + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/vite": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/vitest-environment-clarinet": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vitest-environment-clarinet/-/vitest-environment-clarinet-1.0.3.tgz", + "integrity": "sha512-h/FeWPiEBS4a359Y8ZNo8nsftsfEoyLtZpJdnvDggDzcEUNkAsssU4tQzLp+KPm2VohAleqjFGSYMOGRbgLtDA==", + "peerDependencies": { + "@hirosystems/clarinet-sdk": "1", + "vitest": "0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2724e81 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "clarity-bitcoin-tests", + "version": "1.0.0", + "description": "Run unit tests on this project.", + "private": true, + "scripts": { + "test": "vitest run", + "test:report": "vitest run -- --coverage --costs", + "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\"" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@hirosystems/clarinet-sdk": "^1.1.0", + "@stacks/transactions": "^6.9.0", + "chokidar-cli": "^3.0.0", + "typescript": "^5.2.2", + "vite": "^4.4.9", + "vitest": "^0.34.4", + "vitest-environment-clarinet": "^1.0.0" + } +} diff --git a/tests/clarity-bitcoin.test.ts b/tests/clarity-bitcoin.test.ts new file mode 100644 index 0000000..494841b --- /dev/null +++ b/tests/clarity-bitcoin.test.ts @@ -0,0 +1,456 @@ +import { describe, expect, it } from "vitest"; +import { hexToBytes } from "./utils.ts"; +import { tx as Tx } from "@hirosystems/clarinet-sdk"; +import { verifyMerkleProof, Error } from "./clients/clarity-bitcoin-client.ts"; +import { Cl } from "@stacks/transactions"; +const accounts = simnet.getAccounts(); +const chain = simnet; + +describe("Bitcoin library", () => { + it("Ensure that tx ids are correctly constructed", () => { + const deployer = accounts.get("deployer")!; + let response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "get-txid", + [ + Cl.bufferFromHex( + "02000000019b69251560ea1143de610b3c6630dcf94e12000ceba7d40b136bfb67f5a9e4eb000000006b483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5feffffff022864f203000000001976a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788acb1103955000000001976a9149049b676cf05040103135c7342bcc713a816700688ac3bc50700" + ), + ], + deployer + ); + + expect(response.result).toBeBuff( + hexToBytes( + "74d350ca44c324f4643274b98801f9a023b2b8b72e8e895879fd9070a68f7f1f" + ) + ); + }); + + it("Ensure that buffers are correctly reversed", () => { + const deployer = accounts.get("deployer")!; + let response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "reverse-buff32", + [ + Cl.bufferFromHex( + "74d350ca44c324f4643274b98801f9a023b2b8b72e8e895879fd9070a68f7f1f" + ), + ], + deployer + ); + expect(response.result).toBeBuff( + hexToBytes( + "1f7f8fa67090fd7958898e2eb7b8b223a0f90188b9743264f424c344ca50d374" + ) + ); + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "reverse-buff32", + [ + Cl.bufferFromHex( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + ], + deployer + ); + expect(response.result).toBeBuff( + hexToBytes( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + ); + + try { + simnet.callReadOnlyFn( + "clarity-bitcoin", + "reverse-buff32", + [Cl.bufferFromHex("01")], + deployer + ); + } catch (e: any) { + expect(e.toString()).toBe( + 'Contract call error: clarity-bitcoin::reverse-buff32(0x01) -> Runtime Error: Runtime error while interpreting ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.contract-call: Runtime(UnwrapFailure, Some([FunctionIdentifier { identifier: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.clarity-bitcoin:reverse-buff32" }, FunctionIdentifier { identifier: "_native_:special_as_max_len" }, FunctionIdentifier { identifier: "_native_:special_concat" }, FunctionIdentifier { identifier: "_native_:special_as_max_len" }, FunctionIdentifier { identifier: "_native_:native_unwrap" }]))' + ); + } + }); + + it("Ensure that merkle proof can be validated", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + // txid + hexToBytes( + "cb2e23bc96049cb71489f7e98817888a927b618e9d21700120c59b4f762f16f1" + ), + // reversed merkle root + hexToBytes( + "c9c02be3c9d6a3d97f048d19ffff34817ffa54e7eec51bac79bca5807f64375a" + ), + { + hashes: [ + // sibling txid (in block 150000) + hexToBytes( + "f1162f764f9bc5200170219d8e617b928a881788e9f78914b79c0496bc232ecb" + ), + // 1 intermediate double-sha256 hashes + hexToBytes( + "7614716e165ae0cada0d4cd994bb195ca8010fd17c388825ca4c81843d14f9d8" + ), + ], + txIndex: 2, // this transaction is at index 6 in the block (starts from 0) + treeDepth: 2, // merkle tree depth (must be given because we can't infer leaf/non-leaf nodes) + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(true)); + }); + it("Ensure that merkle proof can be validated", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" + ), + { + hashes: [ + // reversed sibling txid (in block 150000) + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + // 3 intermediate double-sha256 hashes + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, // this transaction is at index 6 in the block (starts from 0) + treeDepth: 4, // merkle tree depth (must be given because we can't infer leaf/non-leaf nodes) + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(true)); + }); + it("Ensure that merkle proof with wrong txid is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + // CORRUPTED + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5aed46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2e6f130bbe" + ), + { + hashes: [ + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, + treeDepth: 4, + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(false)); + }); + it("Ensure that merkle proof with wrong merkle root is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + // CORRUPTED + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2e6f130bbe" + ), + { + hashes: [ + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, + treeDepth: 4, + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(false)); + }); + + it("Ensure that merkle proof with wrong tree is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" + ), + { + hashes: [ + // CORRUPTED + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee58" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, + treeDepth: 4, + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(false)); + }); + + it("Ensure that merkle proof with wrong tx index is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" + ), + { + hashes: [ + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + // CORRUPTED + txIndex: 7, + treeDepth: 4, + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(false)); + }); + + it("Ensure that merkle proof with smaller tree depth is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" + ), + { + hashes: [ + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, + // CORRUPTED + treeDepth: 3, + }, + deployer + ); + + expect(response.result).toBeOk(Cl.bool(false)); + }); + it("Ensure that merkle proof with larger tree depth is detected", () => { + const deployer = accounts.get("deployer")!; + let response = verifyMerkleProof( + hexToBytes( + "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" + ), + hexToBytes( + "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" + ), + { + hashes: [ + hexToBytes( + "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" + ), + hexToBytes( + "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" + ), + hexToBytes( + "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" + ), + hexToBytes( + "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" + ), + ], + txIndex: 6, + // TOO LONG + treeDepth: 5, + }, + deployer + ); + + expect(response.result).toBeErr(Cl.uint(Error.ERR_PROOF_TOO_SHORT)); + }); + it("Ensure is-bit-set determines if the bit in a uint is set to 1", () => { + const deployer = accounts.get("deployer")!; + let response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(10), Cl.uint(0)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(10), Cl.uint(1)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(51), Cl.uint(2)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(51), Cl.uint(3)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(255), Cl.uint(7)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(0), Cl.uint(0)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(255), Cl.uint(0)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(255), Cl.uint(7)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(128), Cl.uint(7)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(240), Cl.uint(4)], + deployer + ); + expect(response.result).toBeBool(true); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(42), Cl.uint(2)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(255), Cl.uint(8)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(1), Cl.uint(1)], + deployer + ); + expect(response.result).toBeBool(false); + + response = simnet.callReadOnlyFn( + "clarity-bitcoin", + "is-bit-set", + [Cl.uint(1), Cl.uint(0)], + deployer + ); + expect(response.result).toBeBool(true); + }); +}); diff --git a/tests/clarity-bitcoin_parse.test.ts b/tests/clarity-bitcoin_parse.test.ts new file mode 100644 index 0000000..abd0a39 --- /dev/null +++ b/tests/clarity-bitcoin_parse.test.ts @@ -0,0 +1,207 @@ +import { describe, expect, it } from "vitest"; +import { hexToBytes, expectHeaderObject, expectTxObject } from "./utils.ts"; +import { parseTx, parseBlockHeader } from "./clients/clarity-bitcoin-client.ts"; +import { ResponseCV } from "@stacks/transactions"; + +const accounts = simnet.getAccounts(); +const chain = simnet; + +describe("Parse bitcoin txs", () => { + it("Ensure that simple bitcoin txs can be parsed"), + () => { + const deployer = accounts.get("deployer")!; + let response = parseTx( + "02000000019b69251560ea1143de610b3c6630dcf94e12000ceba7d40b136bfb67f5a9e4eb000000006b483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5feffffff022864f203000000001976a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788acb1103955000000001976a9149049b676cf05040103135c7342bcc713a816700688ac3bc50700", + deployer + ); + + expectTxObject(response.result as ResponseCV, { + ins: [ + { + outpoint: { + hash: "ebe4a9f567fb6b130bd4a7eb0c00124ef9dc30663c0b61de4311ea601525699b", + index: 0, + }, + scriptSig: + "483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5", + sequence: 4294967294, + }, + ], + locktime: 509243, + outs: [ + { + scriptPubKey: "76a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788ac", + value: 66217000, + }, + { + scriptPubKey: "76a9149049b676cf05040103135c7342bcc713a816700688ac", + value: 1429803185, + }, + ], + version: 2, + }); + + response = parseTx( + "01000000011111111111111111111111111111111111111111111111111111111111111112000000006b483045022100eba8c0a57c1eb71cdfba0874de63cf37b3aace1e56dcbd61701548194a79af34022041dd191256f3f8a45562e5d60956bb871421ba69db605716250554b23b08277b012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d000000000040000000000000000536a4c5069645b22222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333333404142435051606162637071fa39300000000000001976a914000000000000000000000000000000000000000088ac39300000000000001976a914000000000000000000000000000000000000000088aca05b0000000000001976a9140be3e286a15ea85882761618e366586b5574100d88ac00000000", + deployer + ); + + expectTxObject(response.result as ResponseCV, { + version: 1, + locktime: 0, + ins: [ + { + outpoint: { + hash: "1211111111111111111111111111111111111111111111111111111111111111", + index: 0, + }, + scriptSig: + "483045022100eba8c0a57c1eb71cdfba0874de63cf37b3aace1e56dcbd61701548194a79af34022041dd191256f3f8a45562e5d60956bb871421ba69db605716250554b23b08277b012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0", + sequence: 0, + }, + ], + outs: [ + { + scriptPubKey: + "6a4c5069645b22222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333333404142435051606162637071fa", + value: 0, + }, + { + scriptPubKey: "76a914000000000000000000000000000000000000000088ac", + value: 12345, + }, + { + scriptPubKey: "76a914000000000000000000000000000000000000000088ac", + value: 12345, + }, + { + scriptPubKey: "76a9140be3e286a15ea85882761618e366586b5574100d88ac", + value: 23456, + }, + ], + }); + + response = parseTx( + "01000000011111111111111111111111111111111111111111111111111111111111111112000000006a473044022037d0b9d4e98eab190522acf5fb8ea8e89b6a4704e0ac6c1883d6ffa629b3edd30220202757d710ec0fb940d1715e02588bb2150110161a9ee08a83b750d961431a8e012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d000000000020000000000000000396a3769645e2222222222222222222222222222222222222222a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a39300000000000001976a9140be3e286a15ea85882761618e366586b5574100d88ac00000000", + deployer + ); + + expectTxObject(response.result as ResponseCV, { + version: 1, + locktime: 0, + ins: [ + { + outpoint: { + hash: "1211111111111111111111111111111111111111111111111111111111111111", + index: 0, + }, + scriptSig: + "473044022037d0b9d4e98eab190522acf5fb8ea8e89b6a4704e0ac6c1883d6ffa629b3edd30220202757d710ec0fb940d1715e02588bb2150110161a9ee08a83b750d961431a8e012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0", + sequence: 0, + }, + ], + outs: [ + { + scriptPubKey: + "6a3769645e2222222222222222222222222222222222222222a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a", + value: 0, + }, + { + scriptPubKey: "76a9140be3e286a15ea85882761618e366586b5574100d88ac", + value: 12345, + }, + ], + }); + response = parseTx( + "02000000019f9069891e7c64089afc0fd54b58c2044a8f61dc23d0b70abd8a1a4eee3bb36e010000006a47304402204c882028c0eb0dde2297fe3b5873abb2c0868938d63141705bb89025b08130b302204d0cd527a0ef4260ca95babc977f9e97ba41f947a4d16dba8dee823dcaff790e0121030c82347d355523ff0a4ee5c45d8dea6423e3b41d5aecaf1372af7596002ea49afdffffff0838180000000000001976a91400eb31b8bd6003f946887129468a16570b68845288ac38180000000000001976a914124d44a6ddbee55099615560ba8d7d53fc02257a88ac38180000000000001976a91481ee7e1dcb06cd261a26e6309117568f0b5d9f4988ac38180000000000001976a914973945b46e1548d4da32d96e5e09c8aa02103ff888ac38180000000000001976a914dbd958d812af104b5501d242c5cda84b3549382888ac38180000000000001976a914e03a8ffbc18b05583f804b68de97ab683705ec7f88ac10270000000000001976a9140b650f021ff2ae2165594ae51c7b1fa88f719e5f88ac8cf11800000000001976a9144e1f6f57aa65cc6d394e227b665335cf07f897a888ac95f92400", + deployer + ); + + expectTxObject(response.result as ResponseCV, { + version: 2, + locktime: 2423189, + ins: [ + { + outpoint: { + hash: "6eb33bee4e1a8abd0ab7d023dc618f4a04c2584bd50ffc9a08647c1e8969909f", + index: 1, + }, + scriptSig: + "47304402204c882028c0eb0dde2297fe3b5873abb2c0868938d63141705bb89025b08130b302204d0cd527a0ef4260ca95babc977f9e97ba41f947a4d16dba8dee823dcaff790e0121030c82347d355523ff0a4ee5c45d8dea6423e3b41d5aecaf1372af7596002ea49a", + sequence: 4294967293, + }, + ], + outs: [ + { + scriptPubKey: "76a91400eb31b8bd6003f946887129468a16570b68845288ac", + value: 6200, + }, + { + scriptPubKey: "76a914124d44a6ddbee55099615560ba8d7d53fc02257a88ac", + value: 6200, + }, + { + scriptPubKey: "76a91481ee7e1dcb06cd261a26e6309117568f0b5d9f4988ac", + value: 6200, + }, + { + scriptPubKey: "76a914973945b46e1548d4da32d96e5e09c8aa02103ff888ac", + value: 6200, + }, + { + scriptPubKey: "76a914dbd958d812af104b5501d242c5cda84b3549382888ac", + value: 6200, + }, + { + scriptPubKey: "76a914e03a8ffbc18b05583f804b68de97ab683705ec7f88ac", + value: 6200, + }, + { + scriptPubKey: "76a9140b650f021ff2ae2165594ae51c7b1fa88f719e5f88ac", + value: 10000, + }, + { + scriptPubKey: "76a9144e1f6f57aa65cc6d394e227b665335cf07f897a888ac", + value: 1634700, + }, + ], + }); + }; + + it("Ensure that bitcoin headers can be parsed"), + () => { + const deployer = accounts.get("deployer")!; + let response = parseBlockHeader( + hexToBytes( + "000000203c437224480966081c2b14afac79e58207d996c8ac9d32000000000000000000847a4c2c77c8ecf0416ca07c2dc038414f14135017e18525f85cacdeedb54244e0d6b958df620218c626368a" + ), + deployer + ); + expectHeaderObject(response.result, { + version: 536870912, + parent: + "000000000000000000329dacc896d90782e579acaf142b1c086609482472433c", + merkleRoot: + "4442b5eddeac5cf82585e1175013144f4138c02d7ca06c41f0ecc8772c4c7a84", + timestamp: 1488574176, + nbits: 402809567, + nonce: 2318804678, + }); + response = parseBlockHeader( + hexToBytes( + "0020952c929316de1469df3abaff26835b0e73f45317f437b431e5e53a000000000000005c10dbda435580e08e54cd53f1504ee02a147a5d3e646a75131f9eff2e732492e838a56226d5461972717cf7" + ), + deployer + ); + expectHeaderObject(response.result, { + version: 747970560, + parent: + "000000000000003ae5e531b437f41753f4730e5b8326ffba3adf6914de169392", + merkleRoot: + "9224732eff9e1f13756a643e5d7a142ae04e50f153cd548ee0805543dadb105c", + timestamp: 1654995176, + nbits: 424072486, + nonce: 4152127858, + }); + }; +}); diff --git a/tests/clarity-bitcoin_parse_test.ts b/tests/clarity-bitcoin_parse_test.ts deleted file mode 100644 index 69fc082..0000000 --- a/tests/clarity-bitcoin_parse_test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { Clarinet, Tx, Chain, Account, types, assertEquals } from "./deps.ts"; -import { hexToBytes, expectHeaderObject, expectTxObject } from "./utils.ts"; -import { - parseTx, - parseBlockHeader, -} from "./clients/clarity-bitcoin-client.ts"; - -Clarinet.test({ - name: "Ensure that simple bitcoin txs can be parsed", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - parseTx( - "0x02000000019b69251560ea1143de610b3c6630dcf94e12000ceba7d40b136bfb67f5a9e4eb000000006b483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5feffffff022864f203000000001976a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788acb1103955000000001976a9149049b676cf05040103135c7342bcc713a816700688ac3bc50700", - deployer - ), - ]); - - expectTxObject(block, { - ins: [ - { - outpoint: { - hash: "ebe4a9f567fb6b130bd4a7eb0c00124ef9dc30663c0b61de4311ea601525699b", - index: 0, - }, - scriptSig: - "483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5", - sequence: 4294967294, - }, - ], - locktime: 509243, - outs: [ - { - scriptPubKey: "76a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788ac", - value: 66217000, - }, - { - scriptPubKey: "76a9149049b676cf05040103135c7342bcc713a816700688ac", - value: 1429803185, - }, - ], - version: 2, - }); - - block = chain.mineBlock([ - parseTx( - "0x01000000011111111111111111111111111111111111111111111111111111111111111112000000006b483045022100eba8c0a57c1eb71cdfba0874de63cf37b3aace1e56dcbd61701548194a79af34022041dd191256f3f8a45562e5d60956bb871421ba69db605716250554b23b08277b012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d000000000040000000000000000536a4c5069645b22222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333333404142435051606162637071fa39300000000000001976a914000000000000000000000000000000000000000088ac39300000000000001976a914000000000000000000000000000000000000000088aca05b0000000000001976a9140be3e286a15ea85882761618e366586b5574100d88ac00000000", - deployer - ), - ]); - - expectTxObject(block, { - version: 1, - locktime: 0, - ins: [ - { - outpoint: { - hash: "1211111111111111111111111111111111111111111111111111111111111111", - index: 0, - }, - scriptSig: - "483045022100eba8c0a57c1eb71cdfba0874de63cf37b3aace1e56dcbd61701548194a79af34022041dd191256f3f8a45562e5d60956bb871421ba69db605716250554b23b08277b012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0", - sequence: 0, - }, - ], - outs: [ - { - scriptPubKey: - "6a4c5069645b22222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333333404142435051606162637071fa", - value: 0, - }, - { - scriptPubKey: "76a914000000000000000000000000000000000000000088ac", - value: 12345, - }, - { - scriptPubKey: "76a914000000000000000000000000000000000000000088ac", - value: 12345, - }, - { - scriptPubKey: "76a9140be3e286a15ea85882761618e366586b5574100d88ac", - value: 23456, - }, - ], - }); - - block = chain.mineBlock([ - parseTx( - "0x01000000011111111111111111111111111111111111111111111111111111111111111112000000006a473044022037d0b9d4e98eab190522acf5fb8ea8e89b6a4704e0ac6c1883d6ffa629b3edd30220202757d710ec0fb940d1715e02588bb2150110161a9ee08a83b750d961431a8e012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d000000000020000000000000000396a3769645e2222222222222222222222222222222222222222a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a39300000000000001976a9140be3e286a15ea85882761618e366586b5574100d88ac00000000", - deployer - ), - ]); - - expectTxObject(block, { - version: 1, - locktime: 0, - ins: [ - { - outpoint: { - hash: "1211111111111111111111111111111111111111111111111111111111111111", - index: 0, - }, - scriptSig: - "473044022037d0b9d4e98eab190522acf5fb8ea8e89b6a4704e0ac6c1883d6ffa629b3edd30220202757d710ec0fb940d1715e02588bb2150110161a9ee08a83b750d961431a8e012102d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0", - sequence: 0, - }, - ], - outs: [ - { - scriptPubKey: - "6a3769645e2222222222222222222222222222222222222222a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a", - value: 0, - }, - { - scriptPubKey: "76a9140be3e286a15ea85882761618e366586b5574100d88ac", - value: 12345, - }, - ], - }); - block = chain.mineBlock([ - parseTx( - "0x02000000019f9069891e7c64089afc0fd54b58c2044a8f61dc23d0b70abd8a1a4eee3bb36e010000006a47304402204c882028c0eb0dde2297fe3b5873abb2c0868938d63141705bb89025b08130b302204d0cd527a0ef4260ca95babc977f9e97ba41f947a4d16dba8dee823dcaff790e0121030c82347d355523ff0a4ee5c45d8dea6423e3b41d5aecaf1372af7596002ea49afdffffff0838180000000000001976a91400eb31b8bd6003f946887129468a16570b68845288ac38180000000000001976a914124d44a6ddbee55099615560ba8d7d53fc02257a88ac38180000000000001976a91481ee7e1dcb06cd261a26e6309117568f0b5d9f4988ac38180000000000001976a914973945b46e1548d4da32d96e5e09c8aa02103ff888ac38180000000000001976a914dbd958d812af104b5501d242c5cda84b3549382888ac38180000000000001976a914e03a8ffbc18b05583f804b68de97ab683705ec7f88ac10270000000000001976a9140b650f021ff2ae2165594ae51c7b1fa88f719e5f88ac8cf11800000000001976a9144e1f6f57aa65cc6d394e227b665335cf07f897a888ac95f92400", - deployer - ), - ]); - - expectTxObject(block, { - version: 2, - locktime: 2423189, - ins: [ - { - outpoint: { - hash: "6eb33bee4e1a8abd0ab7d023dc618f4a04c2584bd50ffc9a08647c1e8969909f", - index: 1, - }, - scriptSig: - "47304402204c882028c0eb0dde2297fe3b5873abb2c0868938d63141705bb89025b08130b302204d0cd527a0ef4260ca95babc977f9e97ba41f947a4d16dba8dee823dcaff790e0121030c82347d355523ff0a4ee5c45d8dea6423e3b41d5aecaf1372af7596002ea49a", - sequence: 4294967293, - }, - ], - outs: [ - { - scriptPubKey: - "76a91400eb31b8bd6003f946887129468a16570b68845288ac", - value: 6200, - }, - { - scriptPubKey: "76a914124d44a6ddbee55099615560ba8d7d53fc02257a88ac", - value: 6200, - }, - { - scriptPubKey: "76a91481ee7e1dcb06cd261a26e6309117568f0b5d9f4988ac", - value: 6200, - }, - { - scriptPubKey: "76a914973945b46e1548d4da32d96e5e09c8aa02103ff888ac", - value: 6200, - }, - { - scriptPubKey: "76a914dbd958d812af104b5501d242c5cda84b3549382888ac", - value: 6200, - }, - { - scriptPubKey: "76a914e03a8ffbc18b05583f804b68de97ab683705ec7f88ac", - value: 6200, - }, - { - scriptPubKey: "76a9140b650f021ff2ae2165594ae51c7b1fa88f719e5f88ac", - value: 10000, - }, - { - scriptPubKey: "76a9144e1f6f57aa65cc6d394e227b665335cf07f897a888ac", - value: 1634700, - }, - ], - }); - - }, -}); - -Clarinet.test({ - name: "Ensure that bitcoin headers can be parsed", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - parseBlockHeader( - hexToBytes( - "000000203c437224480966081c2b14afac79e58207d996c8ac9d32000000000000000000847a4c2c77c8ecf0416ca07c2dc038414f14135017e18525f85cacdeedb54244e0d6b958df620218c626368a" - ), - deployer - ), - ]); - expectHeaderObject(block, { - version: 536870912, - parent: - "000000000000000000329dacc896d90782e579acaf142b1c086609482472433c", - merkleRoot: - "4442b5eddeac5cf82585e1175013144f4138c02d7ca06c41f0ecc8772c4c7a84", - timestamp: 1488574176, - nbits: 402809567, - nonce: 2318804678, - }); - block = chain.mineBlock([ - parseBlockHeader( - hexToBytes( - "0020952c929316de1469df3abaff26835b0e73f45317f437b431e5e53a000000000000005c10dbda435580e08e54cd53f1504ee02a147a5d3e646a75131f9eff2e732492e838a56226d5461972717cf7" - ), - deployer - ), - ]); - expectHeaderObject(block, { - version: 747970560, - parent: - "000000000000003ae5e531b437f41753f4730e5b8326ffba3adf6914de169392", - merkleRoot: - "9224732eff9e1f13756a643e5d7a142ae04e50f153cd548ee0805543dadb105c", - timestamp: 1654995176, - nbits: 424072486, - nonce: 4152127858, - }); - }, -}); diff --git a/tests/clarity-bitcoin_parse_wtx.test.ts b/tests/clarity-bitcoin_parse_wtx.test.ts new file mode 100644 index 0000000..c4d2ada --- /dev/null +++ b/tests/clarity-bitcoin_parse_wtx.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { hexToBytes, expectHeaderObject, expectTxObject } from "./utils.ts"; +import { + parseTx, + parseBlockHeader, + parseWtx, +} from "./clients/clarity-bitcoin-client.ts"; +import { ResponseCV } from "@stacks/transactions"; + +const accounts = simnet.getAccounts(); +const chain = simnet; + +describe("Parse bitcoin txs with whitness data", () => { + it("Ensure that segwit bitcoin txs can be parsed", () => { + const deployer = accounts.get("deployer")!; + const response = parseWtx( + "0200000000010218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d50140a50417be5a056f63e052294cb20643f83038d5cd90e2f90c1ad3f80180026cb99d78cd4480fadbbc5b9cad5fb2248828fb21549e7cb3f7dbd7aefd2d541bd34f0140acde555b7689eae41d5ccf872bb32a270893bdaa1defc828b76c282f6c87fc387d7d4343c5f7288cfd9aa5da0765c7740ca97e44a0205a1abafa279b530d5fe36d182500", + true, + deployer + ); + + expectTxObject(response.result as ResponseCV, { + ins: [ + { + outpoint: { + hash: "4b7c557b26b6aaaaaff36fc6e4c4df3593b655bd42715424651102324405f918", + index: 3, + }, + scriptSig: "", + sequence: 0, + }, + { + outpoint: { + hash: "ac2849cc91f6c475e023191a0b6907616c768e45a56c62a2b71a323910dfdbe0", + index: 0, + }, + scriptSig: "", + sequence: 0, + }, + ], + locktime: 2431085, + outs: [ + { + scriptPubKey: + "00208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd1468818836", + value: 500000, + }, + { + scriptPubKey: + "512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d5", + value: 19291964, + }, + ], + version: 2, + txid: "3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084", + }); + }); +}); diff --git a/tests/clarity-bitcoin_parse_wtx_test.ts b/tests/clarity-bitcoin_parse_wtx_test.ts deleted file mode 100644 index a85c39a..0000000 --- a/tests/clarity-bitcoin_parse_wtx_test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Clarinet, Tx, Chain, Account, types, assertEquals } from "./deps.ts"; -import { hexToBytes, expectHeaderObject, expectTxObject } from "./utils.ts"; -import { - parseTx, - parseBlockHeader, - parseWtx, -} from "./clients/clarity-bitcoin-client.ts"; - -Clarinet.test({ - name: "Ensure that segwit bitcoin txs can be parsed", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - parseWtx( - "0x0200000000010218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d50140a50417be5a056f63e052294cb20643f83038d5cd90e2f90c1ad3f80180026cb99d78cd4480fadbbc5b9cad5fb2248828fb21549e7cb3f7dbd7aefd2d541bd34f0140acde555b7689eae41d5ccf872bb32a270893bdaa1defc828b76c282f6c87fc387d7d4343c5f7288cfd9aa5da0765c7740ca97e44a0205a1abafa279b530d5fe36d182500", - true, - deployer - ), - ]); - console.log(block.receipts[0].result); - - expectTxObject(block, { - ins: [ - { - outpoint: { - hash: "4b7c557b26b6aaaaaff36fc6e4c4df3593b655bd42715424651102324405f918", - index: 3, - }, - scriptSig: - "", - sequence: 0, - }, - { - outpoint: { - hash: "ac2849cc91f6c475e023191a0b6907616c768e45a56c62a2b71a323910dfdbe0", - index: 0, - }, - scriptSig: - "", - sequence: 0, - }, - ], - locktime: 2431085, - outs: [ - { - scriptPubKey: "00208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd1468818836", - value: 500000, - }, - { - scriptPubKey: "512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d5", - value: 19291964, - }, - ], - version: 2, - txid: "3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084" - }); - }, -}); diff --git a/tests/clarity-bitcoin_test.ts b/tests/clarity-bitcoin_test.ts deleted file mode 100644 index 1f9bcd0..0000000 --- a/tests/clarity-bitcoin_test.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { Clarinet, Tx, Chain, Account, types, assertEquals } from "./deps.ts"; -import { hexToBytes } from "./utils.ts"; -import { verifyMerkleProof, Error } from "./clients/clarity-bitcoin-client.ts"; - -Clarinet.test({ - name: "Ensure that tx ids are correctly constructed", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - Tx.contractCall( - "clarity-bitcoin", - "get-txid", - [ - "0x02000000019b69251560ea1143de610b3c6630dcf94e12000ceba7d40b136bfb67f5a9e4eb000000006b483045022100a52f6c484072528334ac4aa5605a3f440c47383e01bc94e9eec043d5ad7e2c8002206439555804f22c053b89390958083730d6a66c1b711f6b8669a025dbbf5575bd012103abc7f1683755e94afe899029a8acde1480716385b37d4369ba1bed0a2eb3a0c5feffffff022864f203000000001976a914a2420e28fbf9b3bd34330ebf5ffa544734d2bfc788acb1103955000000001976a9149049b676cf05040103135c7342bcc713a816700688ac3bc50700", - ], - deployer.address - ), - ]); - - block.receipts[0].result.expectBuff( - hexToBytes( - "74d350ca44c324f4643274b98801f9a023b2b8b72e8e895879fd9070a68f7f1f" - ) - ); - }, -}); - - -Clarinet.test({ - name: "Ensure that buffers are correctly reversed", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - Tx.contractCall( - "clarity-bitcoin", - "reverse-buff32", - [ - "0x74d350ca44c324f4643274b98801f9a023b2b8b72e8e895879fd9070a68f7f1f", - ], - deployer.address - ), - Tx.contractCall( - "clarity-bitcoin", - "reverse-buff32", - [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - ], - deployer.address - ), - Tx.contractCall( - "clarity-bitcoin", - "reverse-buff32", - [ - "0x01", - ], - deployer.address - ), - ]); - - block.receipts[0].result.expectBuff( - hexToBytes( - "1f7f8fa67090fd7958898e2eb7b8b223a0f90188b9743264f424c344ca50d374" - ) - ); - block.receipts[1].result.expectBuff( - hexToBytes( - "0000000000000000000000000000000000000000000000000000000000000000" - ) - ); - // Runtime error - assertEquals(block.receipts[2], undefined) - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof can be validated", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - // txid - hexToBytes( - "cb2e23bc96049cb71489f7e98817888a927b618e9d21700120c59b4f762f16f1" - ), - // reversed merkle root - hexToBytes( - "c9c02be3c9d6a3d97f048d19ffff34817ffa54e7eec51bac79bca5807f64375a" - ), - { - hashes: [ - // sibling txid (in block 150000) - hexToBytes( - "f1162f764f9bc5200170219d8e617b928a881788e9f78914b79c0496bc232ecb" - ), - // 1 intermediate double-sha256 hashes - hexToBytes( - "7614716e165ae0cada0d4cd994bb195ca8010fd17c388825ca4c81843d14f9d8" - ), - ], - txIndex: 2, // this transaction is at index 6 in the block (starts from 0) - treeDepth: 2, // merkle tree depth (must be given because we can't infer leaf/non-leaf nodes) - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(true); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof can be validated", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" - ), - { - hashes: [ - // reversed sibling txid (in block 150000) - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - // 3 intermediate double-sha256 hashes - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, // this transaction is at index 6 in the block (starts from 0) - treeDepth: 4, // merkle tree depth (must be given because we can't infer leaf/non-leaf nodes) - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(true); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with wrong txid is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - // CORRUPTED - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5aed46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2e6f130bbe" - ), - { - hashes: [ - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, - treeDepth: 4, - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(false); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with wrong merkle root is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - // CORRUPTED - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2e6f130bbe" - ), - { - hashes: [ - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, - treeDepth: 4, - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(false); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with wrong tree is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" - ), - { - hashes: [ - // CORRUPTED - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee58" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, - treeDepth: 4, - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(false); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with wrong tx index is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" - ), - { - hashes: [ - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - // CORRUPTED - txIndex: 7, - treeDepth: 4, - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(false); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with smaller tree depth is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" - ), - { - hashes: [ - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, - // CORRUPTED - treeDepth: 3, - }, - deployer - ), - ]); - - block.receipts[0].result.expectOk().expectBool(false); - }, -}); - -Clarinet.test({ - name: "Ensure that merkle proof with larger tree depth is detected", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - verifyMerkleProof( - hexToBytes( - "25c6a1f8c0b5be2bee1e8dd3478b4ec8f54bbc3742eaf90bfb5afd46cf217ad9" - ), - hexToBytes( - "b152eca4364850f3424c7ac2b337d606c5ca0a3f96f1554f8db33d2f6f130bbe" - ), - { - hashes: [ - hexToBytes( - "ae1e670bdbf8ab984f412e6102c369aeca2ced933a1de74712ccda5edaf4ee57" - ), - hexToBytes( - "efc2b3db87ff4f00c79dfa8f732a23c0e18587a73a839b7710234583cdd03db9" - ), - hexToBytes( - "f1b6fe8fc2ab800e6d76ee975a002d3e67a60b51a62085a07289505b8d03f149" - ), - hexToBytes( - "e827331b1fe7a2689fbc23d14cd21317c699596cbca222182a489322ece1fa74" - ), - ], - txIndex: 6, - // TOO LONG - treeDepth: 5, - }, - deployer - ), - ]); - - block.receipts[0].result.expectErr().expectUint(Error.ERR_PROOF_TOO_SHORT); - }, -}); - -Clarinet.test({ - name: "Ensure is-bit-set determines if the bit in a uint is set to 1", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - let block = chain.mineBlock([ - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(10),types.uint(0)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(10),types.uint(1)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(51),types.uint(2)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(51),types.uint(3)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(255),types.uint(7)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(0),types.uint(0)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(255),types.uint(0)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(255),types.uint(7)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(128),types.uint(7)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(240),types.uint(4)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(42),types.uint(2)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(255),types.uint(8)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(1),types.uint(1)],deployer.address), - Tx.contractCall("clarity-bitcoin","is-bit-set",[types.uint(1),types.uint(0)],deployer.address), - ]); - block.receipts[0].result.expectBool(false); - block.receipts[1].result.expectBool(true); - block.receipts[2].result.expectBool(false); - block.receipts[3].result.expectBool(false); - block.receipts[4].result.expectBool(true); - block.receipts[5].result.expectBool(false); - block.receipts[6].result.expectBool(true); - block.receipts[7].result.expectBool(true); - block.receipts[8].result.expectBool(true); - block.receipts[9].result.expectBool(true); - block.receipts[10].result.expectBool(false); - block.receipts[11].result.expectBool(false); - block.receipts[12].result.expectBool(false); - block.receipts[13].result.expectBool(true); - }, -}); diff --git a/tests/clients/clarity-bitcoin-client.ts b/tests/clients/clarity-bitcoin-client.ts index 468ab27..289eb3a 100644 --- a/tests/clients/clarity-bitcoin-client.ts +++ b/tests/clients/clarity-bitcoin-client.ts @@ -1,23 +1,24 @@ -import { Tx, Account, types } from "../deps.ts"; +import { tx as Tx} from "@hirosystems/clarinet-sdk"; +import { Cl, callReadOnlyFunction } from "@stacks/transactions"; export const Error = { ERR_PROOF_TOO_SHORT: 8, }; -export function parseTx(tx: string, deployer: Account) { - return Tx.contractCall("clarity-bitcoin", "parse-tx", [tx], deployer.address); +export function parseTx(tx: string, deployer: string) { + return simnet.callReadOnlyFn("clarity-bitcoin", "parse-tx", [Cl.bufferFromHex(tx)], deployer); } -export function parseWtx(wtx: string, calculateTxid: boolean, deployer: Account) { - return Tx.contractCall("clarity-bitcoin", "parse-wtx", [wtx, types.bool(calculateTxid)], deployer.address); +export function parseWtx(wtx: string, calculateTxid: boolean, deployer: string) { + return simnet.callReadOnlyFn("clarity-bitcoin", "parse-wtx", [Cl.bufferFromHex(wtx), Cl.bool(calculateTxid)], deployer); } -export function parseBlockHeader(headerBuff: Uint8Array, deployer: Account) { - return Tx.contractCall( +export function parseBlockHeader(headerBuff: Uint8Array, deployer: string) { + return simnet.callReadOnlyFn( "clarity-bitcoin", "parse-block-header", - [types.buff(headerBuff)], - deployer.address + [Cl.buffer(headerBuff)], + deployer ); } @@ -29,21 +30,21 @@ export function verifyMerkleProof( txIndex: number; treeDepth: number; }, - deployer: Account + deployer: string ) { const reverseTxId = txId.reverse(); - return Tx.contractCall( + return simnet.callReadOnlyFn( "clarity-bitcoin", "verify-merkle-proof", [ - types.buff(reverseTxId), - types.buff(merkleRoot), - types.tuple({ - hashes: types.list(merkleProof.hashes.map((h) => types.buff(h))), - "tx-index": types.uint(merkleProof.txIndex), - "tree-depth": types.uint(merkleProof.treeDepth), + Cl.buffer(reverseTxId), + Cl.buffer(merkleRoot), + Cl.tuple({ + hashes: Cl.list(merkleProof.hashes.map((h) => Cl.buffer(h))), + "tx-index": Cl.uint(merkleProof.txIndex), + "tree-depth": Cl.uint(merkleProof.treeDepth), }), ], - deployer.address + deployer ); } diff --git a/tests/deps.ts b/tests/deps.ts deleted file mode 100644 index 364cce2..0000000 --- a/tests/deps.ts +++ /dev/null @@ -1,22 +0,0 @@ -export type { - Account, - ReadOnlyFn, -} from "https://deno.land/x/clarinet@v1.5.4/index.ts"; - -export { - Clarinet, - Chain, - Tx, - types, -} from "https://deno.land/x/clarinet@v1.5.4/index.ts"; - -export { - assertEquals, - assertObjectMatch, -} from "https://deno.land/std@0.182.0/testing/asserts.ts"; - -export { - beforeEach, - describe, - it, -} from "https://deno.land/x/test_suite@0.16.1/mod.ts"; diff --git a/tests/send-to-first-input-compact.test.ts b/tests/send-to-first-input-compact.test.ts new file mode 100644 index 0000000..96bea8f --- /dev/null +++ b/tests/send-to-first-input-compact.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; +import { hexToBytes } from "./utils.ts"; +import { Cl, makeContractCall, principalCV } from "@stacks/transactions"; +import { tx as Tx } from "@hirosystems/clarinet-sdk"; + +const accounts = simnet.getAccounts(); +const chain = simnet; + +describe("Send to first input compact", () => { + it("Ensure that scriptSig is converted to principal", () => { + const deployer = accounts.get("deployer")!; + const response = chain.callReadOnlyFn( + "send-to-first-input-compact", + "p2pkh-to-principal", + [ + Cl.bufferFromHex( + "473044022017e2af6e1308d431365deeb5739d41a909cf0d61a9c0e48f3ae5b0bd6544bfc5022066e73dd26d71d824552b034b322603cce8b936912b99f4f3df512e502bd7c11e012103d7b3bc2d0b4b72a845c469c9fee3c8cf475a2f237e379d7f75a4f463f7bd6ebd" + ), + ], + deployer + ); + + expect(response.result).toBeSome( + principalCV("ST2X7X1A0649S3BJR0DEB58NQ73E24FVWPPVK11WA") + ); + }); + + it("Ensure that users can't sent incomplete proofs", () => { + const deployer = accounts.get("deployer")!; + const block = chain.mineBlock([ + Tx.callPublicFn( + "send-to-first-input-compact", + "send-to-first-input", + [ + Cl.uint(1), + Cl.buffer(hexToBytes("00")), + Cl.buffer(hexToBytes("01")), + Cl.tuple({ + "tx-index": Cl.uint(1), + hashes: Cl.list([]), + "tree-depth": Cl.uint(1), + }), + ], + deployer + ), + ]); + + expect(block[0].result).toBeErr(Cl.uint(1)); + }); +}); diff --git a/tests/send-to-first-input-compact_test.ts b/tests/send-to-first-input-compact_test.ts deleted file mode 100644 index 4d243a8..0000000 --- a/tests/send-to-first-input-compact_test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Clarinet, Tx, Chain, Account, types } from "./deps.ts"; -import { hexToBytes } from "./utils.ts"; - -Clarinet.test({ - name: "Ensure that scriptSig is converted to principal", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - const response = chain.callReadOnlyFn( - "send-to-first-input-compact", - "p2pkh-to-principal", - [ - "0x473044022017e2af6e1308d431365deeb5739d41a909cf0d61a9c0e48f3ae5b0bd6544bfc5022066e73dd26d71d824552b034b322603cce8b936912b99f4f3df512e502bd7c11e012103d7b3bc2d0b4b72a845c469c9fee3c8cf475a2f237e379d7f75a4f463f7bd6ebd", - ], - deployer.address - ); - - response.result - .expectSome() - .expectPrincipal("ST2X7X1A0649S3BJR0DEB58NQ73E24FVWPPVK11WA"); - }, -}); - -Clarinet.test({ - name: "Ensure that users can't sent incomplete proofs", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - const block = chain.mineBlock([ - Tx.contractCall( - "send-to-first-input-compact", - "send-to-first-input", - [ - types.uint(1), - types.buff(hexToBytes("00")), - types.buff(hexToBytes("01")), - types.tuple({ - "tx-index": types.uint(1), - hashes: types.list([]), - "tree-depth": types.uint(1), - }), - ], - deployer.address - ), - ]); - - block.receipts[0].result.expectErr().expectUint(1); - }, -}); diff --git a/tests/send-to-first-input.test.ts b/tests/send-to-first-input.test.ts new file mode 100644 index 0000000..0eac109 --- /dev/null +++ b/tests/send-to-first-input.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "vitest"; +import { hexToBytes } from "./utils.ts"; +import { Cl } from "@stacks/transactions"; +import { tx as Tx } from "@hirosystems/clarinet-sdk"; +const accounts = simnet.getAccounts(); +const chain = simnet; + +describe("Send to first input", () => { + it("Ensure that scriptSig is converted to principal", () => { + const deployer = accounts.get("deployer")!; + const response = chain.callReadOnlyFn( + "send-to-first-input", + "p2pkh-to-principal", + [ + Cl.bufferFromHex( + "473044022017e2af6e1308d431365deeb5739d41a909cf0d61a9c0e48f3ae5b0bd6544bfc5022066e73dd26d71d824552b034b322603cce8b936912b99f4f3df512e502bd7c11e012103d7b3bc2d0b4b72a845c469c9fee3c8cf475a2f237e379d7f75a4f463f7bd6ebd" + ), + ], + deployer + ); + + expect(response.result).toBeSome( + Cl.standardPrincipal("ST2X7X1A0649S3BJR0DEB58NQ73E24FVWPPVK11WA") + ); + }); + + it("Ensure that users can't sent incomplete proofs", () => { + const deployer = accounts.get("deployer")!; + const block = chain.mineBlock([ + Tx.callPublicFn( + "send-to-first-input", + "send-to-first-input", + [ + Cl.uint(1), + Cl.buffer(hexToBytes("00")), + Cl.tuple({ + version: Cl.buffer(hexToBytes("00")), + parent: Cl.buffer(hexToBytes("00")), + "merkle-root": Cl.buffer(hexToBytes("00")), + timestamp: Cl.buffer(hexToBytes("00")), + nbits: Cl.buffer(hexToBytes("00")), + nonce: Cl.buffer(hexToBytes("00")), + }), + Cl.tuple({ + "tx-index": Cl.uint(1), + hashes: Cl.list([]), + "tree-depth": Cl.uint(1), + }), + ], + deployer + ), + ]); + + expect(block[0].result).toBeErr(Cl.uint(1)); + }); +}); diff --git a/tests/send-to-first-input_test.ts b/tests/send-to-first-input_test.ts deleted file mode 100644 index f4debcb..0000000 --- a/tests/send-to-first-input_test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Clarinet, Tx, Chain, Account, types } from "./deps.ts"; -import { hexToBytes } from "./utils.ts"; - -Clarinet.test({ - name: "Ensure that scriptSig is converted to principal", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - const response = chain.callReadOnlyFn( - "send-to-first-input", - "p2pkh-to-principal", - [ - "0x473044022017e2af6e1308d431365deeb5739d41a909cf0d61a9c0e48f3ae5b0bd6544bfc5022066e73dd26d71d824552b034b322603cce8b936912b99f4f3df512e502bd7c11e012103d7b3bc2d0b4b72a845c469c9fee3c8cf475a2f237e379d7f75a4f463f7bd6ebd", - ], - deployer.address - ); - - response.result - .expectSome() - .expectPrincipal("ST2X7X1A0649S3BJR0DEB58NQ73E24FVWPPVK11WA"); - }, -}); - -Clarinet.test({ - name: "Ensure that users can't sent incomplete proofs", - async fn(chain: Chain, accounts: Map) { - const deployer = accounts.get("deployer")!; - const block = chain.mineBlock([ - Tx.contractCall( - "send-to-first-input", - "send-to-first-input", - [ - types.uint(1), - types.buff(hexToBytes("00")), - types.tuple({ - version: types.buff(hexToBytes("00")), - parent: types.buff(hexToBytes("00")), - "merkle-root": types.buff(hexToBytes("00")), - timestamp: types.buff(hexToBytes("00")), - nbits: types.buff(hexToBytes("00")), - nonce: types.buff(hexToBytes("00")), - }), - types.tuple({ - "tx-index": types.uint(1), - hashes: types.list([]), - "tree-depth": types.uint(1), - }), - ], - deployer.address - ), - ]); - - block.receipts[0].result.expectErr().expectUint(1); - }, -}); diff --git a/tests/utils.ts b/tests/utils.ts index bdbd9e6..2f642d3 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,3 +1,15 @@ +import { ParsedTransactionResult } from "@hirosystems/clarinet-sdk"; +import { + Cl, + ClarityType, + ClarityValue, + ListCV, + ResponseCV, + ResponseOkCV, + TupleCV, +} from "@stacks/transactions"; +import { expect } from "vitest"; + export function hexToBytes(hexString: string) { if (hexString) { return Uint8Array.from( @@ -39,7 +51,7 @@ export interface TxObject { } export function expectHeaderObject( - block: any, + result: ClarityValue, expectedHeaderObject: { version: number; parent: string; @@ -49,45 +61,48 @@ export function expectHeaderObject( nonce: number; } ) { - const headerObject = block.receipts[0].result.expectOk().expectTuple(); - headerObject.version.expectUint(expectedHeaderObject.version); - headerObject.parent.expectBuff(hexToBytes(expectedHeaderObject.parent)); - headerObject["merkle-root"].expectBuff( - hexToBytes(expectedHeaderObject.merkleRoot) + expect(result).toBeOk( + Cl.tuple({ + version: Cl.uint(expectedHeaderObject.version), + parent: Cl.buffer(hexToBytes(expectedHeaderObject.parent)), + "merkle-root": Cl.buffer(hexToBytes(expectedHeaderObject.merkleRoot)), + timestamp: Cl.uint(expectedHeaderObject.timestamp), + nbits: Cl.uint(expectedHeaderObject.nbits), + nonce: Cl.uint(expectedHeaderObject.nonce), + }) ); - headerObject.timestamp.expectUint(expectedHeaderObject.timestamp); - headerObject.nbits.expectUint(expectedHeaderObject.nbits); - headerObject.nonce.expectUint(expectedHeaderObject.nonce); } -export function expectTxObject(block: any, expectedTxObject: TxObject) { - const resultTxObject = block.receipts[0].result.expectOk().expectTuple(); - resultTxObject.version.expectUint(expectedTxObject.version); - resultTxObject.locktime.expectUint(expectedTxObject.locktime); +export function expectTxObject(result: ResponseCV, expectedTxObject: TxObject) { + expect(result.type).toBe(ClarityType.ResponseOk); + const resultTxObject = (result.value as TupleCV).data; - for (let index = 0; index < expectedTxObject.ins.length; index++) { - const insObject = resultTxObject.ins.expectList()[index].expectTuple(); - const outpoint = insObject.outpoint.expectTuple(); - outpoint.hash.expectBuff( - hexToBytes(expectedTxObject.ins[index].outpoint.hash) - ); - outpoint.index.expectUint(expectedTxObject.ins[index].outpoint.index); + expect(resultTxObject.version).toBeUint(expectedTxObject.version); + expect(resultTxObject.locktime).toBeUint(expectedTxObject.locktime); - insObject.scriptSig.expectBuff( - hexToBytes(expectedTxObject.ins[index].scriptSig) - ); - insObject.sequence.expectUint(expectedTxObject.ins[index].sequence); + for (let index = 0; index < expectedTxObject.ins.length; index++) { + expect((resultTxObject.ins as ListCV).list[index]).toBeTuple({ + outpoint: Cl.tuple({ + hash: Cl.buffer(hexToBytes(expectedTxObject.ins[index].outpoint.hash)), + index: Cl.uint(expectedTxObject.ins[index].outpoint.index), + }), + scriptSig: Cl.buffer(hexToBytes(expectedTxObject.ins[index].scriptSig)), + sequence: Cl.uint(expectedTxObject.ins[index].sequence), + }); } for (let index = 0; index < expectedTxObject.outs.length; index++) { - const outObject = resultTxObject.outs.expectList()[index].expectTuple(); - outObject.scriptPubKey.expectBuff( - hexToBytes(expectedTxObject.outs[index].scriptPubKey) - ); - outObject.value.expectUint(expectedTxObject.outs[index].value); + expect((resultTxObject.outs as ListCV).list[index]).toBeTuple({ + scriptPubKey: Cl.buffer( + hexToBytes(expectedTxObject.outs[index].scriptPubKey) + ), + value: Cl.uint(expectedTxObject.outs[index].value), + }); } if (expectedTxObject.txid) { - resultTxObject.txid.expectSome().expectBuff(hexToBytes(expectedTxObject.txid)); + expect(resultTxObject.txid).toBeSome( + Cl.buffer(hexToBytes(expectedTxObject.txid)) + ); } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..aa218f6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext"], + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src", + "tests" + ] +} diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..8d01c50 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,36 @@ +/// + +import { defineConfig } from "vite"; +import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest"; + +/* + In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet. + + The `vitest-environment-clarinet` will initialise the clarinet-sdk + and make the `simnet` object available globally in the test files. + + `vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things: + - run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports. + - load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`) + + The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --` + - vitest run -- --manifest ./Clarinet.toml # pass a custom path + - vitest run -- --coverage --costs # collect coverage and cost reports +*/ + +export default defineConfig({ + test: { + environment: "clarinet", // use vitest-environment-clarinet + singleThread: true, + setupFiles: [ + vitestSetupFilePath, + // custom setup files can be added here + ], + environmentOptions: { + clarinet: { + ...getClarinetVitestsArgv(), + // add or override options + }, + }, + }, +}); From 076b0a9514c64cf5e782e46197787458449ac826 Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 12:24:49 +0100 Subject: [PATCH 05/17] chore: use clarinet-sdk for clar tests --- .../tests/clarity-bitcoin_segwit_test.clar | 29 +++++++---- contracts/tests/clarity-bitcoin_test.clar | 51 ++++++++++++------- tests/clar.test.ts | 45 ++++++++++++++++ 3 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 tests/clar.test.ts diff --git a/contracts/tests/clarity-bitcoin_segwit_test.clar b/contracts/tests/clarity-bitcoin_segwit_test.clar index 586b6f4..f4d501a 100644 --- a/contracts/tests/clarity-bitcoin_segwit_test.clar +++ b/contracts/tests/clarity-bitcoin_segwit_test.clar @@ -61,7 +61,7 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) - (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height raw-tx raw-block-header @@ -72,7 +72,9 @@ witness-reserved-data raw-coinbase-tx (list 0x5f4a8858a112953111d5f94605c4dd8d04690eeeb9ffe2f435475d95943f2f3f 0x3348bf81aae79941662a902206b3ed2d285713668ab134c9e113548daea596fc) - ) + ))) + (asserts! (is-eq result (ok 0xc62ea13f720175da9c93507db2b586b1d0a84faea386bf448a5bc470c37d1104)) (err "expected txid")) + (ok true)) ) ) @@ -92,7 +94,7 @@ (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) - (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height raw-tx raw-block-header @@ -103,11 +105,12 @@ witness-reserved-data raw-coinbase-tx (list 0x012e8988aa58e53ac700c22257237716f642aea5ad45c3f6420eb010bf2b37f9 0x10b71df80cc37bd92c746690a980a7f4187e6dbf8b973ac938f1b1b9d1ef25b6 0x897a09fa66156b753366b38236fd40fe7920db114722b3d7762826bf4930750c 0x2cf51c5e81f70b72c37fd8eccd8d3446c7c69c4d568acd23c9949c25bc1d3fb4 0x7d45568289180def6b91cdf6f89c692f3798872948b582a9f6fd5cc471cb67e0 0xe5fefbaf926e706c4ef331d172dcb589dfd6b997bee4ba6ddee4c7c6e2918549 0x097ae87e24dae74bb00a2bfe0aa3c14537ce04b048285ce69d98301bd9ddee8d) - ) + ))) + (asserts! (is-eq result (ok 0xfea94d1e37eee8642ebbd6f5b43f6d092d9ac197f921f4598117a833dbe55b68)) (err "expected txid")) + (ok true)) ) ) - ;; @name verify segwit transaction where OP_RETURN is in output[0] ;; arbitrary segwit transaction (define-public (test-was-wtx-mined-internal-3) @@ -125,7 +128,7 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) - (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height raw-tx raw-block-header @@ -136,7 +139,9 @@ witness-reserved-data raw-coinbase-tx (list 0xfc63a531dfb491d91ddf08dfe0d5b2406a97be193ae90fa44e2fca0b8a30d02f 0x33274cc92f8b980272688e01114cc2944fb661d1aa3a658c7d29675a46a4d5ad 0x1172bf0943aad7bc580aaab5f5d356b1c172f11c297ccf0077515309906352f2 0x2af4fd00ac79b65c0c508fbf44c22d1cf5acb084770079a94bd72ac816cfceb8 0xed4ca325a1800f0dfb2ab9d63761ecb358c014424e684c820d0d87ace45474a1 0xd3292e0e550420e500f29663dfc8ef632dbcb119c8a1ddf49aa3d32ecad83084 0x6369b65eea600edbd69b56386be9269f9662ca3f384a0ca21922ac03d2936102) - ) + ))) + (asserts! (is-eq result (ok 0xbf986c2f7c53f7fc6f87ec605d4e4ff58aa1f4558fa4bfa7bc692d64277168da)) (err "expected txid")) + (ok true)) ) ) @@ -170,7 +175,7 @@ (list 0x12b32c51b0b4f3e42b234e791e6b851874cbe200aa7729b31f7bb96ee1b9647b) ) ok-res (err u1) - err-res (if (is-eq err-res ERR-PROOF-TOO-SHORT) (ok err-res) (err err-res)) + err-res (if (is-eq err-res ERR-PROOF-TOO-SHORT) (ok true) (err err-res)) ) ) ) @@ -205,7 +210,7 @@ (list 0x3d52480061d7634fa8060430cf86d8de3f577499a2056f5ff80cc36918a78dcc 0x41dd33b4cffe074cc8263f98da7c81006521eb2cc8712028fa491efed619cffb) ) ok-res (err u1) - err-res (if (is-eq err-res ERR-VARSLICE-TOO-LONG) (ok err-res) (err err-res)) + err-res (if (is-eq err-res ERR-VARSLICE-TOO-LONG) (ok true) (err err-res)) ) ) ) @@ -226,7 +231,7 @@ (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) - (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact burnchain-block-height raw-tx raw-block-header @@ -237,7 +242,9 @@ witness-reserved-data raw-coinbase-tx (list 0x5f4a8858a112953111d5f94605c4dd8d04690eeeb9ffe2f435475d95943f2f3f 0x3348bf81aae79941662a902206b3ed2d285713668ab134c9e113548daea596fc) - ) + ))) + (asserts! (is-eq result (ok 0xc62ea13f720175da9c93507db2b586b1d0a84faea386bf448a5bc470c37d1104)) (err "expected txid")) + (ok true)) ) ) diff --git a/contracts/tests/clarity-bitcoin_test.clar b/contracts/tests/clarity-bitcoin_test.clar index e2effe7..291ec3b 100644 --- a/contracts/tests/clarity-bitcoin_test.clar +++ b/contracts/tests/clarity-bitcoin_test.clar @@ -28,7 +28,7 @@ (define-public (test-was-tx-mined-internal-1) (let ( (burnchain-block-height u2431087) - ;; txid: 3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084 + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) @@ -38,23 +38,26 @@ ;; prepare ;; (unwrap-panic (add-burnchain-block-header-hash burnchain-block-height raw-block-header)) - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u3, tree-depth: u2, hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) + ;; @name verify segwit transaction where OP_RETURN is in output[0] ;; arbitrary segwit transaction (define-public (test-was-tx-mined-internal-2) (let ( (burnchain-block-height u2431459) - ;; txid: 0edafd2b2e9374ede142d1b60f884b47aa7a4ae0a5d46aa9c1d63d2e8e27087d + (txid 0x0edafd2b2e9374ede142d1b60f884b47aa7a4ae0a5d46aa9c1d63d2e8e27087d) (raw-tx 0x0200000001cbab643067979ec7a7c74bf49af775b20203df78e31b83b2d25510fe5897872c0000000000feffffff02b0631d0000000000160014f55fa4fafee59cccde7d6204029d388480d354b6ce32599300000000160014cc9957eaabf54d9fa7d1c0ae64bd0f95e0bd0cdfe2192500) ;; block id: 000000004daafb5977a88c7111da740e77cf6dd5a417f343ed01374676266c36 (raw-block-header 0x000000207277c3a739ad34a3873a3c9d755f0766e918f4b37adcfe455d079b93000000000bad83579d633d37aa1e2bb725b8a5bad35509374454f1adad8a4de140066d74dbf64e64ffff001d6616bfe2) @@ -62,24 +65,27 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) ) ;; prepare - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u8, tree-depth: u7, hashes: (list 0xbcbc1fe72ca5d67f74099ac4f851cd6a266d2a42fa6c9a6244b11adf6a2f13fb 0x4348ff70e7b2132e0b6044f960ec25a5f7966f5d6182827c9359039a7654218e 0x6fed856ef1075c15a1318eeb512349ceb6a5115953990369c9cb03d65a550468 0xfcaccdf24d540b6ec02568efaefc5df5b04e249ecaaf95a9170c9b72e7b8f46d 0x7d45568289180def6b91cdf6f89c692f3798872948b582a9f6fd5cc471cb67e0 0xe5fefbaf926e706c4ef331d172dcb589dfd6b997bee4ba6ddee4c7c6e2918549 0x097ae87e24dae74bb00a2bfe0aa3c14537ce04b048285ce69d98301bd9ddee8d)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) + ;; @name verify segwit transaction where OP_RETURN is in output[0] ;; arbitrary segwit transaction (define-public (test-was-tx-mined-internal-3) (let ( (burnchain-block-height u2431567) - ;; txid: 2fd0308a0bca2f4ea40fe93a19be976a40b2d5e0df08df1dd991b4df31a563fc + (txid 0x2fd0308a0bca2f4ea40fe93a19be976a40b2d5e0df08df1dd991b4df31a563fc) (raw-tx 0x02000000017d406bb6466e0da97778f55ece77ab6becc415c930dbe618e81e8dae53ba914400000000171600143e8d4581104393d916566886ae01e26be8f8975afeffffff0276410300000000001600143d5f37543d2916547fe9b1242322b22f6e46d6929c9291d8000000001600145b3298860caeb3302f09e58b7cc3cf58d0a674044e1a2500) ;; block id: 00000000096ae97d41a543592c3680477444acdc86c877aeb4832744691cb94b (raw-block-header 0x000000208af1a70ab2ed062c7ac53eb56b053498db50f0d9c41f0dc8a5efcb1b000000007b64b9e16eb97b1fb32977aa00e2cb7418856b1e794e232be4f3b4b0512cb31256845064ffff001dc3cdbab0) @@ -87,14 +93,16 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) ) - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u1, tree-depth: u7, hashes: (list 0x6ef3bf4c1eab5389170584545683660fa601f38f1216bb6357a9b01560a8f884 0x33274cc92f8b980272688e01114cc2944fb661d1aa3a658c7d29675a46a4d5ad 0x1172bf0943aad7bc580aaab5f5d356b1c172f11c297ccf0077515309906352f2 0x2af4fd00ac79b65c0c508fbf44c22d1cf5acb084770079a94bd72ac816cfceb8 0xed4ca325a1800f0dfb2ab9d63761ecb358c014424e684c820d0d87ace45474a1 0xd3292e0e550420e500f29663dfc8ef632dbcb119c8a1ddf49aa3d32ecad83084 0x6369b65eea600edbd69b56386be9269f9662ca3f384a0ca21922ac03d2936102)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) @@ -102,7 +110,7 @@ (define-public (test-was-tx-mined-internal-4) (let ( (burnchain-block-height u2431619) - ;; txid: c770364da721e34eeb1a67f09c986fa5e4f13f9819df727e604691f42f5340a1 + (txid 0xc770364da721e34eeb1a67f09c986fa5e4f13f9819df727e604691f42f5340a1) (raw-tx 0x02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2503831a250000000000000000000000000000000002000000000000073c7500000000000000ffffffff02be402500000000001976a91455a2e914aeb9729b4cd265248cb67a865eae95fd88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900000000) ;; block id: (raw-block-header 0x00c0c8306c887f5b2248021d1a6662aa684ca46975a21685800192b3f4e5c8b400000000a140532ff49146607e72df19983ff1e4a56f989cf0671aeb4ee321a74d3670c75c545164695a20192b1ab0c7) @@ -110,22 +118,25 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) ) - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u0, tree-depth: u1, hashes: (list)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) + ;; @name OP_RETURN is too large. Fails to parse transaction (define-public (test-was-tx-mined-internal-5) (let ( (burnchain-block-height u2430921) - ;; txid: e2798f96e3584be98f0b6eb6833dbf8284f95c4bb183f425bf409eb3ecc4bf6b + (txid 0xe2798f96e3584be98f0b6eb6833dbf8284f95c4bb183f425bf409eb3ecc4bf6b) (raw-tx 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1b03c917250477664664004000009be40b0000084d617261636f726500000000030000000000000000266a24aa21a9edb675067096401108d0bd081d693fb4adae204ea41a2c444ad79826a2d9f0bb250000000000000000fd80017b226964223a6e756c6c2c22726573756c74223a7b2268617368223a2239346562366639633664633130396239646630396333306237616561613065383538656165626536343831346138363036383962383065633931313634383864222c22636861696e6964223a312c2270726576696f7573626c6f636b68617368223a2232663566306161663139633139623138313433333839663864643330353234323539323937393137613562396330613864353432366362323233373731336364222c22636f696e6261736576616c7565223a3632353030303030302c2262697473223a223230376666666666222c22686569676874223a3437382c225f746172676574223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666666663766222c226d65726b6c655f73697a65223a312c226d65726b6c655f6e6f6e6365223a323539363939363136327d2c226572726f72223a6e756c6c7db87a2500000000001976a914bb94b2b8ce1719201bf0346d79ee0f60c9d2700088ac00000000) ;; block id: 000000000000000011958fb8368d946487bde267f7cb78256ede44ec14e38d87 (raw-block-header 0x00004020070e3e8245969a60d47d780670d9e05dbbd860927341dda51d000000000000007ecc2f605412dddfe6e5c7798ec114004e6eda96f7045baf653c26ded334cfe27766466488a127199541c0a6) @@ -133,14 +144,16 @@ (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) ) - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u0, tree-depth: u2, hashes: (list 0x3d52480061d7634fa8060430cf86d8de3f577499a2056f5ff80cc36918a78dcc 0x41dd33b4cffe074cc8263f98da7c81006521eb2cc8712028fa491efed619cffb)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) @@ -151,7 +164,7 @@ (define-public (test-was-tx-mined-internal-6) (let ( (burnchain-block-height u2431087) - ;; txid: 3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084 + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) @@ -160,14 +173,16 @@ ) ;; prepare - (contract-call? .clarity-bitcoin was-tx-mined-compact + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx raw-block-header {tx-index: u3, tree-depth: u2, hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} - ) + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) ) ) diff --git a/tests/clar.test.ts b/tests/clar.test.ts new file mode 100644 index 0000000..fa6a0bb --- /dev/null +++ b/tests/clar.test.ts @@ -0,0 +1,45 @@ +import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; +import { Cl, cvToString } from "@stacks/transactions"; +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); + +describe("Clarity tests", () => { + it("Run all test functions", () => { + simnet.getContractsInterfaces().forEach((contract, name) => { + if (!name.endsWith("_test")) { + return; + } + + simnet.getContractAST(name).expressions.forEach((expr) => { + //console.log(JSON.stringify(expr)); + }); + + const prepare = contract.functions.findIndex((f) => f.name === "prepare"); + + let block: ParsedTransactionResult[]; + + contract.functions.forEach((fn) => { + if (!fn.name.startsWith("test")) { + return; + } + + if (prepare) { + block = simnet.mineBlock([ + tx.callPublicFn(name, "prepare", [], accounts.get("deployer")!), + tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), + ]); + console.log(name, fn.name, cvToString(block[1].result)); + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeOk(Cl.bool(true)); + } else { + block = simnet.mineBlock([ + tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), + ]); + console.log(name, fn.name, cvToString(block[0].result)); + expect(block[0].result).toBeOk(Cl.bool(true)); + } + }); + }); + }); +}); From 1445dda3a998618621bc782c0545be8d22f58e4a Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 13:29:18 +0100 Subject: [PATCH 06/17] chore: make one test per function --- tests/clar.test.ts | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/tests/clar.test.ts b/tests/clar.test.ts index fa6a0bb..04044f0 100644 --- a/tests/clar.test.ts +++ b/tests/clar.test.ts @@ -3,41 +3,38 @@ import { Cl, cvToString } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; const accounts = simnet.getAccounts(); +simnet.getContractsInterfaces().forEach((contract, name) => { + if (!name.endsWith("_test")) { + return; + } + describe(name, () => { + const prepare = contract.functions.findIndex((f) => f.name === "prepare"); + let block: ParsedTransactionResult[]; -describe("Clarity tests", () => { - it("Run all test functions", () => { - simnet.getContractsInterfaces().forEach((contract, name) => { - if (!name.endsWith("_test")) { + contract.functions.forEach((fn) => { + if (!fn.name.startsWith("test-")) { return; } - simnet.getContractAST(name).expressions.forEach((expr) => { - //console.log(JSON.stringify(expr)); - }); - - const prepare = contract.functions.findIndex((f) => f.name === "prepare"); - - let block: ParsedTransactionResult[]; - - contract.functions.forEach((fn) => { - if (!fn.name.startsWith("test")) { - return; - } - + it(fn.name, () => { if (prepare) { block = simnet.mineBlock([ tx.callPublicFn(name, "prepare", [], accounts.get("deployer")!), tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), ]); - console.log(name, fn.name, cvToString(block[1].result)); expect(block[0].result).toBeOk(Cl.bool(true)); - expect(block[1].result).toBeOk(Cl.bool(true)); + expect( + block[1].result, + `${name}, ${fn.name}, ${cvToString(block[0].result)}` + ).toBeOk(Cl.bool(true)); } else { block = simnet.mineBlock([ tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), ]); - console.log(name, fn.name, cvToString(block[0].result)); - expect(block[0].result).toBeOk(Cl.bool(true)); + expect( + block[0].result, + `${name}, ${fn.name}, ${cvToString(block[0].result)}` + ).toBeOk(Cl.bool(true)); } }); }); From b50b53e0d5b52a2cb1a235cb907a4ddc57a5975c Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 15:52:28 +0100 Subject: [PATCH 07/17] feat: add test for was-tx-mined --- .../tests/clarity-bitcoin_segwit_test.clar | 3 +- contracts/tests/clarity-bitcoin_test.clar | 79 +++++++++++++++++-- scripts/coverage-report.sh | 2 +- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/contracts/tests/clarity-bitcoin_segwit_test.clar b/contracts/tests/clarity-bitcoin_segwit_test.clar index f4d501a..26b1956 100644 --- a/contracts/tests/clarity-bitcoin_segwit_test.clar +++ b/contracts/tests/clarity-bitcoin_segwit_test.clar @@ -1,8 +1,6 @@ (define-constant test-contract-principal (as-contract tx-sender)) (define-constant zero-address 'SP000000000000000000002Q6VF78) -(define-constant ERR-NOT-SEGWIT-TRANSACTION u12) - (define-public (add-burnchain-block-header-hash (burn-height uint) (header (buff 80))) (contract-call? .clarity-bitcoin mock-add-burnchain-block-header-hash burn-height (contract-call? .clarity-bitcoin reverse-buff32 (sha256 (sha256 header)))) ) @@ -280,3 +278,4 @@ (define-constant ERR-TOO-MANY-WITNESSES u9) (define-constant ERR-INVALID-COMMITMENT u10) (define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) +(define-constant ERR-NOT-SEGWIT-TRANSACTION u12) diff --git a/contracts/tests/clarity-bitcoin_test.clar b/contracts/tests/clarity-bitcoin_test.clar index 291ec3b..4f30a12 100644 --- a/contracts/tests/clarity-bitcoin_test.clar +++ b/contracts/tests/clarity-bitcoin_test.clar @@ -35,9 +35,6 @@ (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) ) - ;; prepare - ;; (unwrap-panic (add-burnchain-block-header-hash burnchain-block-height raw-block-header)) - (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact burnchain-block-height raw-tx @@ -46,7 +43,7 @@ tree-depth: u2, hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} ))) - (asserts! (is-eq result (ok txid)) (err "expected txid")) + (asserts! (is-eq result (ok txid)) (err "expected txid")) (ok true)) ) ) @@ -73,7 +70,7 @@ tree-depth: u7, hashes: (list 0xbcbc1fe72ca5d67f74099ac4f851cd6a266d2a42fa6c9a6244b11adf6a2f13fb 0x4348ff70e7b2132e0b6044f960ec25a5f7966f5d6182827c9359039a7654218e 0x6fed856ef1075c15a1318eeb512349ceb6a5115953990369c9cb03d65a550468 0xfcaccdf24d540b6ec02568efaefc5df5b04e249ecaaf95a9170c9b72e7b8f46d 0x7d45568289180def6b91cdf6f89c692f3798872948b582a9f6fd5cc471cb67e0 0xe5fefbaf926e706c4ef331d172dcb589dfd6b997bee4ba6ddee4c7c6e2918549 0x097ae87e24dae74bb00a2bfe0aa3c14537ce04b048285ce69d98301bd9ddee8d)} ))) - (asserts! (is-eq result (ok txid)) (err "expected txid")) + (asserts! (is-eq result (ok txid)) (err "expected txid")) (ok true)) ) ) @@ -101,7 +98,7 @@ tree-depth: u7, hashes: (list 0x6ef3bf4c1eab5389170584545683660fa601f38f1216bb6357a9b01560a8f884 0x33274cc92f8b980272688e01114cc2944fb661d1aa3a658c7d29675a46a4d5ad 0x1172bf0943aad7bc580aaab5f5d356b1c172f11c297ccf0077515309906352f2 0x2af4fd00ac79b65c0c508fbf44c22d1cf5acb084770079a94bd72ac816cfceb8 0xed4ca325a1800f0dfb2ab9d63761ecb358c014424e684c820d0d87ace45474a1 0xd3292e0e550420e500f29663dfc8ef632dbcb119c8a1ddf49aa3d32ecad83084 0x6369b65eea600edbd69b56386be9269f9662ca3f384a0ca21922ac03d2936102)} ))) - (asserts! (is-eq result (ok txid)) (err "expected txid")) + (asserts! (is-eq result (ok txid)) (err "expected txid")) (ok true)) ) ) @@ -126,7 +123,7 @@ tree-depth: u1, hashes: (list)} ))) - (asserts! (is-eq result (ok txid)) (err "expected txid")) + (asserts! (is-eq result (ok txid)) (err "expected txid")) (ok true)) ) ) @@ -186,6 +183,73 @@ ) ) + +;; @name verify transaction with header object +(define-public (test-was-tx-mined-with-header-object-1) + (let ( + (burnchain-block-height u2431087) + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) + (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) + (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) + (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) + ) + ;; prepare + + (let ((result (contract-call? .clarity-bitcoin was-tx-mined + burnchain-block-height + raw-tx + {merkle-root: 0x9160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f, + version: 0x0000a020, + nbits: 0x88a12719, + nonce: 0x9842cec7, + timestamp: 0x18b84864, + parent: 0x65bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d00000000000000, + } + {tx-index: u3, + tree-depth: u2, + hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} + ))) + (asserts! (is-eq result (ok txid)) (err "expected txid")) + (ok true)) + ) +) + + +;; @name verify transaction with header object but wrong version +(define-public (test-was-tx-mined-with-header-object-2) + (let ( + (burnchain-block-height u2431087) + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) + (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) + (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) + (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) + ) + ;; prepare + + (let ((result (contract-call? .clarity-bitcoin was-tx-mined + burnchain-block-height + raw-tx + {merkle-root: 0x9160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f, + version: 0x00000000, + nbits: 0x88a12719, + nonce: 0x9842cec7, + timestamp: 0x18b84864, + parent: 0x65bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d00000000000000, + } + {tx-index: u3, + tree-depth: u2, + hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} + ))) + (asserts! (is-eq result (err ERR-HEADER-HEIGHT-MISMATCH)) (err "expected ERR-HEADER-HEIGHT-MISMATCH")) + (ok true)) + ) +) + + (define-constant ERR-OUT-OF-BOUNDS u1) (define-constant ERR-TOO-MANY-TXINS u2) (define-constant ERR-TOO-MANY-TXOUTS u3) @@ -197,3 +261,4 @@ (define-constant ERR-TOO-MANY-WITNESSES u9) (define-constant ERR-INVALID-COMMITMENT u10) (define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) +(define-constant ERR-LEFTOVER-DATA u13) diff --git a/scripts/coverage-report.sh b/scripts/coverage-report.sh index 8a9950f..dbfe29f 100755 --- a/scripts/coverage-report.sh +++ b/scripts/coverage-report.sh @@ -1,3 +1,3 @@ #!/bin/sh -genhtml .coverage/lcov.info --branch-coverage -o .coverage/ +genhtml lcov.info --branch-coverage -o .coverage/ open .coverage/index.html From 655b7615180c66f00291f73052ed166068779059 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Wed, 22 Nov 2023 17:33:12 +0100 Subject: [PATCH 08/17] fix: coverage low hanging fruit --- contracts/clarity-bitcoin.clar | 62 +++++++++++++--------------------- package-lock.json | 2 +- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/contracts/clarity-bitcoin.clar b/contracts/clarity-bitcoin.clar index e95c821..f4812f8 100644 --- a/contracts/clarity-bitcoin.clar +++ b/contracts/clarity-bitcoin.clar @@ -104,8 +104,7 @@ (define-read-only (reverse-buff32 (input (buff 32))) (unwrap-panic (as-max-len? (concat (reverse-buff16 (unwrap-panic (as-max-len? (unwrap-panic (slice? input u16 u32)) u16))) - (reverse-buff16 (unwrap-panic (as-max-len? (unwrap-panic (slice? input u0 u16)) u16))) - ) u32))) + (reverse-buff16 (unwrap-panic (as-max-len? (unwrap-panic (slice? input u0 u16)) u16)))) u32))) ;; Reads a little-endian hash -- consume the next 32 bytes, and reverse them. ;; Returns (ok { hashslice: (buff 32), ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index. @@ -116,8 +115,7 @@ (txbuff (get txbuff old-ctx)) (hash-le (unwrap-panic (as-max-len? (unwrap! - (slice? txbuff slice-start target-index) (err ERR-OUT-OF-BOUNDS)) - u32)))) + (slice? txbuff slice-start target-index) (err ERR-OUT-OF-BOUNDS)) u32)))) (ok {hashslice: (reverse-buff32 hash-le), ctx: { txbuff: txbuff, index: target-index}}))) @@ -137,27 +135,24 @@ sequence: uint})} uint))) (let ((state (unwrap! result result))) - (if (< u0 (get remaining state)) - (let ((remaining (get remaining state)) - (ctx (get ctx state)) - (parsed-hash (try! (read-hashslice ctx))) - (parsed-index (try! (read-uint32 (get ctx parsed-hash)))) - (parsed-scriptSig (try! (read-varslice (get ctx parsed-index)))) - (parsed-sequence (try! (read-uint32 (get ctx parsed-scriptSig)))) - (new-ctx (get ctx parsed-sequence))) - (ok {ctx: new-ctx, - remaining: (- remaining u1), - txins: (unwrap! - (as-max-len? - (append (get txins state) - { outpoint: { - hash: (get hashslice parsed-hash), - index: (get uint32 parsed-index) }, - scriptSig: (unwrap! (as-max-len? (get varslice parsed-scriptSig) u256) (err ERR-VARSLICE-TOO-LONG)), - sequence: (get uint32 parsed-sequence)}) - u8) - (err ERR-TOO-MANY-TXINS))})) - (ok state)))) + (let ((remaining (get remaining state)) + (ctx (get ctx state)) + (parsed-hash (try! (read-hashslice ctx))) + (parsed-index (try! (read-uint32 (get ctx parsed-hash)))) + (parsed-scriptSig (try! (read-varslice (get ctx parsed-index)))) + (parsed-sequence (try! (read-uint32 (get ctx parsed-scriptSig)))) + (new-ctx (get ctx parsed-sequence))) + (ok {ctx: new-ctx, + remaining: (- remaining u1), + txins: (unwrap! + (as-max-len? + (append (get txins state) { outpoint: { + hash: (get hashslice parsed-hash), + index: (get uint32 parsed-index) }, + scriptSig: (unwrap! (as-max-len? (get varslice parsed-scriptSig) u256) (err ERR-VARSLICE-TOO-LONG)), + sequence: (get uint32 parsed-sequence)}) u8) + (err ERR-TOO-MANY-TXINS))})) + )) ;; Read a transaction's inputs. ;; Returns (ok { txins: (list { ... }), remaining: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index in ctx to point to the start of the tx outputs. @@ -191,8 +186,7 @@ (as-max-len? (append (get txouts state) { value: (get uint64 parsed-value), - scriptPubKey: (unwrap! (as-max-len? (get varslice parsed-script) u128) (err ERR-VARSLICE-TOO-LONG))}) - u8) + scriptPubKey: (unwrap! (as-max-len? (get varslice parsed-script) u128) (err ERR-VARSLICE-TOO-LONG))}) u8) (err ERR-TOO-MANY-TXOUTS))}))) ;; Read all transaction outputs in a transaction. Update the index to point to the first byte after the outputs, if all goes well. @@ -219,8 +213,7 @@ (ok {ctx: new-ctx, items: (unwrap! (as-max-len? - (append (get items state) (unwrap! (as-max-len? (get varslice parsed-item) u128) (err ERR-VARSLICE-TOO-LONG))) - u8) + (append (get items state) (unwrap! (as-max-len? (get varslice parsed-item) u128) (err ERR-VARSLICE-TOO-LONG))) u8) (err ERR-TOO-MANY-WITNESSES))}))) ;; Read the next witness data, and update the index in ctx to point to the next witness. @@ -252,15 +245,6 @@ (define-read-only (read-witnesses (ctx { txbuff: (buff 4096), index: uint }) (num-txins uint)) (fold read-next-witness (bool-list-of-len num-txins) (ok { ctx: ctx, witnesses: (list) }))) -;; -;; Helper functions for smart contract that want to use information of a Bitcoin transaction -;; - -;; Check whether the transaction has witness data or not -;; Returns true if marker/txin counter is 0x00, false otherwise. -(define-read-only (has-witness-data (tx (buff 4096))) - (is-eq (element-at? tx u4) (some 0x00))) - ;; ;; Parses a Bitcoin transaction, with up to 8 inputs and 8 outputs, with scriptSigs of up to 256 bytes each, and with scriptPubKeys up to 128 bytes. ;; It will also calculate and return the TXID if calculate-txid is set to true. @@ -354,6 +338,8 @@ (parsed-txins (try! (read-txins (get ctx parsed-version)))) (parsed-txouts (try! (read-txouts (get ctx parsed-txins)))) (parsed-locktime (try! (read-uint32 (get ctx parsed-txouts))))) + ;; check if it is a non-segwit transaction? + ;; at least check what happens (asserts! (is-eq (len tx) (get index (get ctx parsed-locktime))) (err ERR-LEFTOVER-DATA)) (ok {version: (get uint32 parsed-version), ins: (get txins parsed-txins), diff --git a/package-lock.json b/package-lock.json index 006fe8d..d377f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "clarity-bitcoin-tests", "version": "1.0.0", - "license": "ISC", + "license": "MIT", "dependencies": { "@hirosystems/clarinet-sdk": "^1.1.0", "@stacks/transactions": "^6.9.0", From ee32cd8101be47b484ff438dd48a334421400771 Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 19:46:09 +0100 Subject: [PATCH 09/17] feat: add more tests --- Clarinet.toml | 5 ++ .../tests/clarity-bitcoin-read_test.clar | 76 +++++++++++++++++++ .../tests/clarity-bitcoin_segwit_test.clar | 15 ++++ contracts/tests/clarity-bitcoin_test.clar | 54 +++++++++++++ tests/clar.test.ts | 13 +++- tests/clarity-bitcoin_parse_wtx.test.ts | 1 - 6 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 contracts/tests/clarity-bitcoin-read_test.clar diff --git a/Clarinet.toml b/Clarinet.toml index dd9582e..5a70b35 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -30,6 +30,11 @@ path = 'contracts/tests/clarity-bitcoin_test.clar' clarity_version = 2 epoch = 2.1 +[contracts.clarity-bitcoin-read_test] +path = 'contracts/tests/clarity-bitcoin-read_test.clar' +clarity_version = 2 +epoch = 2.1 + [contracts.helper] path = 'contracts/helper.clar' clarity_version = 2 diff --git a/contracts/tests/clarity-bitcoin-read_test.clar b/contracts/tests/clarity-bitcoin-read_test.clar new file mode 100644 index 0000000..d7bd3d3 --- /dev/null +++ b/contracts/tests/clarity-bitcoin-read_test.clar @@ -0,0 +1,76 @@ +(define-public (test-read-varints-8-zero) + (let ((result (contract-call? .clarity-bitcoin read-varint {index: u0, txbuff: 0x00112233445566}))) + (asserts! (is-ok result) (err result)) + (asserts! (is-eq (unwrap-panic result) {varint: u0, ctx: {index: u1, txbuff: 0x00112233445566}}) (err result)) + (ok true)) +) + +(define-public (test-read-varints-8-252) + (let ((result (contract-call? .clarity-bitcoin read-varint {index: u0, txbuff: 0xfc112233445566}))) + (asserts! (is-ok result) (err result)) + (asserts! (is-eq (unwrap-panic result) {varint: u252, ctx: {index: u1, txbuff: 0xfc112233445566}}) (err result)) + (ok true)) +) + + +(define-public (test-read-varints-16) + (let ((result (contract-call? .clarity-bitcoin read-varint {index: u0, txbuff: 0xfd112233445566}))) + (asserts! (is-ok result) (err result)) + (asserts! (is-eq (unwrap-panic result) {varint: u8721, ctx: {index: u3, txbuff: 0xfd112233445566}}) (err result)) + (ok true)) +) +(define-public (test-read-varints-32) + (let ((result (contract-call? .clarity-bitcoin read-varint {index: u0, txbuff: 0xfe112233445566}))) + (asserts! (is-ok result) (err result)) + (asserts! (is-eq (unwrap-panic result) {varint: u1144201745, ctx: {index: u5, txbuff: 0xfe112233445566}}) (err result)) + (ok true)) +) + +(define-public (test-read-varints-64) + (let ((result (contract-call? .clarity-bitcoin read-varint {index: u0, txbuff: 0xff112233445566778899aa}))) + (asserts! (is-ok result) (err result)) + (asserts! (is-eq (unwrap-panic result) {varint: u9833440827789222417, ctx: {index: u9, txbuff: 0xff112233445566778899aa}}) (err result)) + (ok true)) +) + +;; @name try to read txins of tx with 10 inputs +;; txid: 22fbb0e9488bbd79363511cf70e736f434c7ced6f0c9c678a2c9f638007d3823 +(define-public (test-read-txins) + (let ( + (tx 0x0100000000010b9b9c7ede9e627e28554c89299ace1f059e1db31e4e39944a8f1c7c93d26d96360100000000ffffffff8ba6d6b2b1bffbc659461219ee3a1b97a760a912dd599393920218f79e6a02430000000000ffffffff047b5457e71c6591b0c93660cfb1543d1208711678083a1751abdda8c2bca3a700000000fc004730440220631aad9885f4fe575163be069df6227ee36dd20d925f77dbbd12fdc4a48bc6e3022065befd89086f3a6b2115e62c0ba4ce50cfb4e866b3f7c72e9cd1064fb0629fb3014730440220647c0399beaee128b38b0bdb386a1f9854626191fbe515c5634782242eea1300022012f2388191c4bf89d3f00c8ae365ccc876d5f871916a6bb71d9f2a0dce8ef628014c6952210218d1c463e57f7f185c0c373022a99e53d07d37749965a9d811d69ecdc6eb666b2103fa1d2244418b5e5463bc29ca64b854521b11c80f2e429b2ab05d37ddb7bcf54d2103d618ce6aa14beaaa2164e8c74d16885a646512cca06f66f31181bab3469ada2a53aeffffffff44f20fbc918679c4d226ac57b4825ca7bc95e6b5d0adcce96c146be54d9db8d200000000fdfd00004730440220138672ad8b72c9eb76f268f3ea35719ca388b9b52c7a8523f88429d86671a766022037d4308f6d6db452eac0b57a454c1c2573adccd10ae63edc98c6f4bab9503b0f0148304502210099bc54b48d6cd39d1171229030b1d0705f9799ffb920d60a2bbb8c5be6faa0b3022033ffda30601e8ce472c2b782ba8af3f38e1085e1fa0072e5a64491b180687390014c6952210218d67a00a5404754f06b225aa2dc5ce5df249d24e36f76667a0b8feecb2aa39d2103002653b65b53018ad5d433115f602a5f70fa0c4dd773dc1c6c247a23a4435b0b2103366e7bc89aa358957097839f22a636d9eaf536375d100a44cdfa4083a5d921d853aefffffffffa105c600ad67e50a467f0245db6d928915c903fc45bd923f15341c2498428ec00000000fdfe0000483045022100d429a59020c6a58ba20a3580a8efa9e22c94a4cb28646af0c933b536d5db4e5502205f87bebca92d3a8bd90b293c7b59e795ea0ff8a1abe9912ba3e96bdd58f6539901483045022100f66785b825aa03bb7671f63429c0a86f887843dfa005a012bf75024a2b057ae802206d252832d5698fc7ff9b02efba306a55b77cc717b84d8ceed5a7c874daf01a29014c69522103357db6cc594a55a2c2be7b47ce438ba1e9869879695aa90a583dcdfeea2234ed2103daa551c0f44cbc90ba02a5f01047e6b8cba3381f49e0f91d00a8a4aeba88e22f21030e9d5a1fc1c27af5e7eb9d7fbab1aedf752471b087dde50969b19f04429e4e1653aeffffffff2e2fba20f1e0f7821cde712d085ca87b2ad6c96e35e019854c1acf230905a4b500000000fc004730440220707b29cf4f7dbee9c433102ae38ada2934fb0b6d58967eead2d2516e8ac47809022079bc462c5833381a5dc94d3aeef8a9c2ca4fe3a96db2ac1c52c4f778929c542001473044022009645b4491469952d45297e237404b79905348e32edf278ff3c28f91d6db321902202fc8c95144e367d2fcc88c066bd8516a2b06849329ece8917ac08262650c5c80014c6952210240e0419ab49aa200c4f8e3f2d377ea16d2a15f5ce49f07c64be25ed33502191321031565b10587835ef1a5ed4c08b7b977a397689c8a6884fe1b2504ae6cc6378cfb2102f74938e94cc396bb0516507a651618cfa804a0021fb704267327fb127b56b3d953aeffffffff153813cf1edb1ce5aa0cd2725a2aef0a35b57e840b40fe670e06fac7838103b700000000fdfd000047304402202e39a6bbe002d52e4fbf2edee4a819a9555b6cd686cece0ea82decefb90df4380220183903206941857696a4e31f40ce6d2a1bae53a47df38c894462f8192bc05c1701483045022100e7fd464e73fd7a8bc7075800897a0465ccd99e6087036061ed2758f2689c393202207ff9e22dba62c55da198fc2f307285e51cb48818f7ab2749e8bddeb90dc6e1c4014c6952210240e0419ab49aa200c4f8e3f2d377ea16d2a15f5ce49f07c64be25ed33502191321031565b10587835ef1a5ed4c08b7b977a397689c8a6884fe1b2504ae6cc6378cfb2102f74938e94cc396bb0516507a651618cfa804a0021fb704267327fb127b56b3d953aeffffffffbc4c173252561d7706007ecf8d0b409fa2f040ba63ade2ec13826440f00bc8bc0d00000000fffffffff3c5c010e892ec8ac7f4eb267abdd32329a3ccd8241907bec91f8c97d0c2125b00000000fc004730440220640d8ec19fbb1ead9da35e3182ac4b3848be64f9159ff29c6aebec7138c20c4a022058c9d5106a975e5516cfce4ecf1bb4e308ca5a221ab9a021c87c7387570dc62a0147304402207850e395673d505c90f0869eff20f67736bace235ca961aeb6faca1c7a87105c02200d7addca0c0b20bbb502cd2d94c70b9917c1bf9b321c1a5a74b2eb651af286e8014c6952210240e0419ab49aa200c4f8e3f2d377ea16d2a15f5ce49f07c64be25ed33502191321031565b10587835ef1a5ed4c08b7b977a397689c8a6884fe1b2504ae6cc6378cfb2102f74938e94cc396bb0516507a651618cfa804a0021fb704267327fb127b56b3d953aeffffffffe3860e47856525159bddba008da8fd749ef54463d2d4f3c3ea4c0839512a0a1201000000fdfe0000483045022100ec03eb5ec26f7d85f28294747654876f06da29f32bfc28620fb4c0b2f77d8189022063c1910aef2921a94de3e8e61acb44154754c91eb6f96322dba3d47c9ad9625701483045022100c0e675b453943126c7c51e697d08b34450145a035587071845284c36707163f902202ef35e679db969e3287ff53927c1a923655ddc0c421cea0b90926e98c16b9664014c69522103539605c087f1acd23035fe55aa30d0961c98684238b317ee719e5a7623f5d4ce2102e77b4104f41d484aea11ea3551dba6f62f26b14855518b00e03c491821defb9a2103e2fde0764ea302bcefbf5691bf282527850c39f094fc039272fcbdc14d5e8e8753aeffffffff411cffafb443f0f6c36bf321eee06dcd03395809e851ac85f80d04cc682958e30800000000ffffffff0a00375f000000000017a914cdd60c80f6f574da0e499a8006b29b5007d434a687af5aa40000000000160014a00faf9355cee3ce7d997829307b688023cc3aea60186bee000000001976a91447ecb7761bd21abffd0671b3d82e65e59c25f3fc88acd02c0c00000000001600142bdf779ef8474704335bad6c684d38cec27dc91d8e2b2900000000001976a914f83b8ede0aa1c75aa677467d8103fa5568f06a9d88ac386d050000000000225120d2db3e3cf9bc85abb8e25babf79a67f7f7454d364e27cf516f853d88a7fecdfdd7bb67020000000016001493f4264fbe4bb88b82366f34f3bd22bae8aa91e0a0860100000000001976a914fe22c69bb4db54bb23cf15370e12114034e3061f88ac69362a0000000000160014cb76d3719e0f8c6e933c93acb32b5b9f7b328e0fbf70bd0100000000220020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec0400473044022005bcd7ffa1641e624e6414886feadea7cee437017d289361e8bfd6e64d30c182022039327af6722461683e3bf98b7551c3cabd77a01cf3ad3f7458b6ba213d2d8b98014830450221009074a3aab0031e43fa15256b1e5ad3f736b296db31998ca2fe784d3790a8192902202af32944b58415ce7e31184e4db8ab6d53124c99dc6cffa17486c2136bc7030601695221039859358416537517aeedeb3c28ffbf7a467bb78f35340e2595a608559950924721037ce9d5c70c0e38bbba66df7075d20738948058e62d69c578cb45114317e25ee1210389be2227f2ba88e9994830871e0e672d3fac4d68361b262a4b066246e53306f053ae04004830450221009f77b8ebdbaeef367d8e97797ad8a695f44c3b2930926afbd0d4205fa93dc4a6022040b6bd55883857b6c9eae1201da3abd7152734a62e5deb7c9eeeb7af508b301d01483045022100a6f02384815a5a0a954bdfaa19b829a9a85d8a7d83999f73ba7fdc0d00c4e6d502204aff2524aa8a1519678ba3c898db07b1846c84b5d7f0ef66061b1b6af02f4b2701695221039859358416537517aeedeb3c28ffbf7a467bb78f35340e2595a608559950924721037ce9d5c70c0e38bbba66df7075d20738948058e62d69c578cb45114317e25ee1210389be2227f2ba88e9994830871e0e672d3fac4d68361b262a4b066246e53306f053ae000000000004004830450221009909c9b2c7008a996985b056b7516e2d5ff150c747282a628e2e3a254dee6d2402203eee2719df04f287815c712d1f6ef7f8cc9f14a45e784d7c9688efb549d180610147304402203b56107a5248c8ab4287be690623169c531cbf9e2771895f840c4010a1881c7b02200f02e110838bdaad4e5a20e6df35d78bb9a94bc0897543263298187b4183a10801695221026064e5b88c4fff7dba7dc0300db8dbfc1faff14f9ddbaacbcaa4f70124de0e93210331870350912385ca9a9d537e9cf9d80c6c9558e31d654f82f3164fdc5955e9642103c7b133a0f463a501d8c58c8eb8c7b6e9e4ddfb7d4a7bf6365a4732201569bc8353ae00000400483045022100b35603383f85ddd5327c79e8a79d08ea34953731f70b311b16caa00a6440e7c3022074da71aa45e8e3e0ca942dfcdd63cf341577f738f222d11bd59d000d3318ec1401483045022100dd824d429a6d317bcabcd1bb31953ac8ea76dbd69eaef368d0b23c15b000c44302203cb15f10229de8d4cca99dc0a79622752ef685af68800eba86e6a679bc7bb3b901695221026064e5b88c4fff7dba7dc0300db8dbfc1faff14f9ddbaacbcaa4f70124de0e93210331870350912385ca9a9d537e9cf9d80c6c9558e31d654f82f3164fdc5955e9642103c7b133a0f463a501d8c58c8eb8c7b6e9e4ddfb7d4a7bf6365a4732201569bc8353ae00000000) + (result (contract-call? .clarity-bitcoin read-txins {index: u6, txbuff: tx}))) + (asserts! (is-eq result (err ERR-TOO-MANY-TXINS)) (err result)) + (ok true)) +) + +;; @name try to read txouts of tx with 10 outputs +;; txid: 39edd81aa374a3fc590b4f118efb02273593caa2ba941559b6e56e6f3309cbd4 +(define-public (test-read-txouts) + (let ( + (tx 0x01000000000103aa981c0c66b5f29c9125136bf1fcc1d4b06b990f49a2bf3f4da8ed9a0901c62600000000fc0047304402204deede93a9d1fafbc08160042b5a577e2977b4d57ee827469d78672eae660bd9022069735e1249d2988f31e7fa665f6c5611b7220ae2c040ddbbaf682312a95cfd5401473044022071181566837a7483909fc9d39b8e6ff2c7219fa0d9f84938cc4f560d4b700ff102200215ffa6633ed432d93749be7527ad4bda6545543dfaae3fed4dc4327822e419014c6952210301286b902a17167257aa4e6b7378c42039d05d5c856844835446c149d0e874ae2102b4d90713d1c291b9a24c10e75220d0e1e12bf30a5e8669e181a90c4f1079bc8121020d79724c15771923cf9957d7053bd80abe0a6aece4835775087c8e72a63c34fd53aeffffffffab5a4f02c3b740a47a7b6fee9125657432cbd73e4627151cc4e1b088c7e101ba0300000000ffffffffb43a6d06c64e2a105bc2a339d802a8544c78ccd54a6804385efaa0868047b0d800000000fc0047304402207fb4a9c5ccc8abb0cb68398e54b6548d03824b9af6046327c1ee68ed7fe51a810220347a62c094ed225cba0d3f831461c222be39dc6d6a61a31a36c2c8a4e98aea4c0147304402202dd1424b3cf00c9c6bc05f3034cd1774864d0f419e73edba0f59ee392ab989a302200cb5002787613354fb5749dcad37387ab96e89f6b5db4dc963fea3952e314f0e014c695221025d2ade738149943d25628f8eac89019b7de29cd48f315b97ae33d66e73fbaec82103091d8e82ba9e1537a49d0890f6a7398c091eb10dd24057aac399d80b70f5179e210391a811ec40a7ca4efe671078f013b1d8308788f67da52720162306f9e9a624e553aeffffffff0a9c3e02000000000017a914afca0d408ae72adc9873d7aecf6606666fdf44d787f1b101000000000017a9147cad3c4095aec2b59fa0ea9a7fb55ba0c081c6e287af364d0c0000000017a914e498426ebfdbc21204e60682482ab1443d0b4fea8700093d0000000000160014c3270c47826bd88a1a6ae9aa4dbcb7aa3a1fa645c0c62d00000000001976a91403c0718ebc2bb7961859dc5dd1f0495d199067fe88ac8688670000000000160014cb5385b7ab0e68d90292951667c391e3b0947b96f40f0b000000000017a9140e5c035423d49cdd7d806feabf6362d07b9331fc87e01b18000000000017a9145f4110da3b787fb6ddd75c8b26c495c0c9bfc4db87485904000000000017a9141abe89eac94cde04b90de1605ae112c92054f03f870fa6520100000000220020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec0004004830450221009b03a576d52fb1c4cf24deb5deb3d443a052acd3e6d770af4f3fab4bd8764ed002202c02a4f1d98cade0d1bb5d9e33e2472329ed670c48b5a22b4004cacd50c8dc5101473044022036573e0eddbfec14ab741425f8f36a11116cf612d4eb64de6c2e7acb38b6a1a102207fab226452d0d09beaebfeb321080d5d5de0696b9a79921ea6df196686aa973d01695221026064e5b88c4fff7dba7dc0300db8dbfc1faff14f9ddbaacbcaa4f70124de0e93210331870350912385ca9a9d537e9cf9d80c6c9558e31d654f82f3164fdc5955e9642103c7b133a0f463a501d8c58c8eb8c7b6e9e4ddfb7d4a7bf6365a4732201569bc8353ae0000000000) + (result (contract-call? .clarity-bitcoin read-txouts {index: u634, txbuff: tx}))) + (asserts! (is-eq result (err ERR-TOO-MANY-TXOUTS)) (err result)) + (ok true)) +) + +;; @name try to read witnesses of tx with 0 witnesses +(define-public (test-read-next-witness) + (let ( + (tx 0x00) + (result (contract-call? .clarity-bitcoin read-next-witness false (ok {ctx: {index: u0, txbuff: tx}, witnesses:(list)})))) + (asserts! (is-eq result (ok {ctx: {index: u1, txbuff: tx}, witnesses: (list (list))})) (err result)) + (ok true)) +) + +(define-constant ERR-OUT-OF-BOUNDS u1) +(define-constant ERR-TOO-MANY-TXINS u2) +(define-constant ERR-TOO-MANY-TXOUTS u3) +(define-constant ERR-VARSLICE-TOO-LONG u4) +(define-constant ERR-BAD-HEADER u5) +(define-constant ERR-HEADER-HEIGHT-MISMATCH u6) +(define-constant ERR-INVALID-MERKLE-PROOF u7) +(define-constant ERR-PROOF-TOO-SHORT u8) +(define-constant ERR-TOO-MANY-WITNESSES u9) +(define-constant ERR-INVALID-COMMITMENT u10) +(define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) +(define-constant ERR-LEFTOVER-DATA u13) diff --git a/contracts/tests/clarity-bitcoin_segwit_test.clar b/contracts/tests/clarity-bitcoin_segwit_test.clar index 26b1956..df3eec2 100644 --- a/contracts/tests/clarity-bitcoin_segwit_test.clar +++ b/contracts/tests/clarity-bitcoin_segwit_test.clar @@ -246,6 +246,20 @@ ) ) +;; @name verify segwit transaction with left over data +(define-public (test-parse-wtx) + (let ( + (burnchain-block-height u2431087) + ;; txid: 3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084 + (raw-tx 0x0200000000010218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d50140a50417be5a056f63e052294cb20643f83038d5cd90e2f90c1ad3f80180026cb99d78cd4480fadbbc5b9cad5fb2248828fb21549e7cb3f7dbd7aefd2d541bd34f0140acde555b7689eae41d5ccf872bb32a270893bdaa1defc828b76c282f6c87fc387d7d4343c5f7288cfd9aa5da0765c7740ca97e44a0205a1abafa279b530d5fe36d182500ffffff) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + ) + (let ((result (contract-call? .clarity-bitcoin parse-wtx raw-tx false))) + (asserts! (is-eq result (err ERR-LEFTOVER-DATA)) (err "expected ERR-LEFTOVER-DATA")) + (ok true)) + ) +) + ;; @name parse-wtx is unable to parse a non-segwit transaction (returns unpredictable values) (define-public (test-parse-wtx-on-non-segwit-1) (let ( @@ -279,3 +293,4 @@ (define-constant ERR-INVALID-COMMITMENT u10) (define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) (define-constant ERR-NOT-SEGWIT-TRANSACTION u12) +(define-constant ERR-LEFTOVER-DATA u13) diff --git a/contracts/tests/clarity-bitcoin_test.clar b/contracts/tests/clarity-bitcoin_test.clar index 4f30a12..06b59b3 100644 --- a/contracts/tests/clarity-bitcoin_test.clar +++ b/contracts/tests/clarity-bitcoin_test.clar @@ -183,6 +183,60 @@ ) ) +;; @name verify segwit transaction where merkle proof is wrong +;; arbitrary segwit transaction +(define-public (test-was-tx-mined-internal-7) + (let ( + (burnchain-block-height u2431087) + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) + (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) + (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) + (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) + ) + ;; prepare + + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact + burnchain-block-height + raw-tx + raw-block-header + {tx-index: u3, + tree-depth: u2, + hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8)} + ))) + (asserts! (is-eq result (err ERR-INVALID-MERKLE-PROOF)) (err "expected ERR-INVALID-MERKLE-PROOF")) + (ok true)) + ) +) + + +;; @name verify segwit transaction with left over data +(define-public (test-parse-tx) + (let ( + (burnchain-block-height u2431087) + ;; 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084 + 0xffffff + (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500ffffff) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + ) + (let ((result (contract-call? .clarity-bitcoin parse-tx raw-tx))) + (asserts! (is-eq result (err ERR-LEFTOVER-DATA)) (err "expected ERR-LEFTOVER-DATA")) + (ok true)) + ) +) + +;; @name verify block header with invalid block +(define-public (test-verify-block-header) + (let ( + (burnchain-block-height u0) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) + ) + (let ((result (contract-call? .clarity-bitcoin verify-block-header raw-block-header burnchain-block-height))) + (asserts! (is-eq result false) (err "expected invalid block header")) + (ok true)) + ) +) ;; @name verify transaction with header object (define-public (test-was-tx-mined-with-header-object-1) diff --git a/tests/clar.test.ts b/tests/clar.test.ts index 04044f0..52ca561 100644 --- a/tests/clar.test.ts +++ b/tests/clar.test.ts @@ -1,5 +1,5 @@ import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; -import { Cl, cvToString } from "@stacks/transactions"; +import { Cl, ClarityType, cvToString } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; const accounts = simnet.getAccounts(); @@ -8,7 +8,8 @@ simnet.getContractsInterfaces().forEach((contract, name) => { return; } describe(name, () => { - const prepare = contract.functions.findIndex((f) => f.name === "prepare"); + const prepare = + contract.functions.findIndex((f) => f.name === "prepare") >= 0; let block: ParsedTransactionResult[]; contract.functions.forEach((fn) => { @@ -23,7 +24,10 @@ simnet.getContractsInterfaces().forEach((contract, name) => { tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), ]); expect(block[0].result).toBeOk(Cl.bool(true)); - expect( + + if (block[1].result.type === ClarityType.ResponseErr) { + console.log(cvToString(block[1].result)); + } expect( block[1].result, `${name}, ${fn.name}, ${cvToString(block[0].result)}` ).toBeOk(Cl.bool(true)); @@ -31,6 +35,9 @@ simnet.getContractsInterfaces().forEach((contract, name) => { block = simnet.mineBlock([ tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), ]); + if (block[0].result.type === ClarityType.ResponseErr) { + console.log(cvToString(block[0].result)); + } expect( block[0].result, `${name}, ${fn.name}, ${cvToString(block[0].result)}` diff --git a/tests/clarity-bitcoin_parse_wtx.test.ts b/tests/clarity-bitcoin_parse_wtx.test.ts index c4d2ada..a88a389 100644 --- a/tests/clarity-bitcoin_parse_wtx.test.ts +++ b/tests/clarity-bitcoin_parse_wtx.test.ts @@ -8,7 +8,6 @@ import { import { ResponseCV } from "@stacks/transactions"; const accounts = simnet.getAccounts(); -const chain = simnet; describe("Parse bitcoin txs with whitness data", () => { it("Ensure that segwit bitcoin txs can be parsed", () => { From 8e78d252ed9fd2765517cd7c6d3195729f23d0ac Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 20:17:50 +0100 Subject: [PATCH 10/17] chore: make inner-merkle-proof-verify private --- contracts/clarity-bitcoin.clar | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/contracts/clarity-bitcoin.clar b/contracts/clarity-bitcoin.clar index f4812f8..b79ae27 100644 --- a/contracts/clarity-bitcoin.clar +++ b/contracts/clarity-bitcoin.clar @@ -411,22 +411,19 @@ ;; * if the ith bit is 0, then cur-hash is hashed before the next proof-hash (cur-hash is "left"). ;; * if the ith bit is 1, then the next proof-hash is hashed before cur-hash (cur-hash is "right"). ;; The proof verifies if cur-hash is equal to root-hash, and we're out of proof-hashes to check. -(define-read-only (inner-merkle-proof-verify (ctr uint) (state { path: uint, root-hash: (buff 32), proof-hashes: (list 14 (buff 32)), tree-depth: uint, cur-hash: (buff 32), verified: bool})) - (if (get verified state) - state - (if (>= ctr (get tree-depth state)) - (merge state { verified: false}) - (let ((path (get path state)) - (is-left (is-bit-set path ctr)) - (proof-hashes (get proof-hashes state)) - (cur-hash (get cur-hash state)) - (root-hash (get root-hash state)) - - (h1 (if is-left (unwrap-panic (element-at proof-hashes ctr)) cur-hash)) - (h2 (if is-left cur-hash (unwrap-panic (element-at proof-hashes ctr)))) - (next-hash (sha256 (sha256 (concat h1 h2)))) - (is-verified (and (is-eq (+ u1 ctr) (len proof-hashes)) (is-eq next-hash root-hash)))) - (merge state { cur-hash: next-hash, verified: is-verified}))))) +;; Note, ctr is expected to be < (len proof-hashes), verified can be true only if ctr + 1 == (len proof-hashes). +(define-private (inner-merkle-proof-verify (ctr uint) (state { path: uint, root-hash: (buff 32), proof-hashes: (list 14 (buff 32)), tree-depth: uint, cur-hash: (buff 32), verified: bool})) + (let ((path (get path state)) + (is-left (is-bit-set path ctr)) + (proof-hashes (get proof-hashes state)) + (cur-hash (get cur-hash state)) + (root-hash (get root-hash state)) + + (h1 (if is-left (unwrap-panic (element-at proof-hashes ctr)) cur-hash)) + (h2 (if is-left cur-hash (unwrap-panic (element-at proof-hashes ctr)))) + (next-hash (sha256 (sha256 (concat h1 h2)))) + (is-verified (and (is-eq (+ u1 ctr) (len proof-hashes)) (is-eq next-hash root-hash)))) + (merge state { cur-hash: next-hash, verified: is-verified}))) ;; Verify a Merkle proof, given the _reversed_ txid of a transaction, the merkle root of its block, and a proof consisting of: ;; * The index in the block where the transaction can be found (starting from 0), @@ -567,4 +564,4 @@ ;; verify witness merkle tree (asserts! (try! (verify-merkle-proof reversed-wtxid witness-merkle-root { tx-index: tx-index, hashes: wproof, tree-depth: tree-depth })) (err ERR-WITNESS-TX-NOT-IN-COMMITMENT)) - (ok wtxid)))) \ No newline at end of file + (ok wtxid)))) From 79c765b9c7e6254f621250476f89e7cb3c578117 Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 21:40:36 +0100 Subject: [PATCH 11/17] chore: add test for bitcoin helper lib --- Clarinet.toml | 5 +++++ .../tests/clarity-bitcoin-helper_test.clar | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 contracts/tests/clarity-bitcoin-helper_test.clar diff --git a/Clarinet.toml b/Clarinet.toml index 5a70b35..3f17206 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -40,6 +40,11 @@ path = 'contracts/helper.clar' clarity_version = 2 epoch = 2.1 +[contracts.clarity-bitcoin-helper_test] +path = 'contracts/tests/clarity-bitcoin-helper_test.clar' +clarity_version = 2 +epoch = 2.1 + [contracts.send-to-first-input] path = 'contracts/examples/send-to-first-input.clar' clarity_version = 2 diff --git a/contracts/tests/clarity-bitcoin-helper_test.clar b/contracts/tests/clarity-bitcoin-helper_test.clar new file mode 100644 index 0000000..db4d83d --- /dev/null +++ b/contracts/tests/clarity-bitcoin-helper_test.clar @@ -0,0 +1,17 @@ +;; @name concat tx with empty data +(define-public (test-concat-tx-empty) + (let ((result (contract-call? .clarity-bitcoin-helper concat-tx + {ins: (list), outs: (list), version: 0x, locktime: 0x}))) + (asserts! (is-eq result 0x0000) (err result)) + (ok true))) + +;; @name concat tx with most data possible per in and out +(define-public (test-concat-tx-max-1) + (let ((result (contract-call? .clarity-bitcoin-helper concat-tx + {ins: (list {outpoint: {hash: 0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00, index: 0x00112233}, scriptSig: 0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00, sequence: 0x11223344} + ), + outs: (list {value: 0x0011223344556677, scriptPubKey: 0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00}), + version: 0x11223344, locktime: 0x44332211}))) + (asserts! (is-eq result 0x1122334401112233445566778899aabbccddeeff00112233445566778899aabbccddeeff000011223380112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff001122334401001122334455667780112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff0044332211) (err result)) + (ok true))) + From 4bb320a7bd2c6aeb222227889c612c7c8fc2c9fa Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 21:54:01 +0100 Subject: [PATCH 12/17] feat: remove non-compact version --- Clarinet.toml | 5 -- contracts/clarity-bitcoin.clar | 11 --- contracts/examples/send-to-first-input.clar | 31 ------- contracts/helper.clar | 3 - .../tests/clarity-bitcoin-helper_test.clar | 13 +++ contracts/tests/clarity-bitcoin_test.clar | 90 +++++-------------- tests/send-to-first-input.test.ts | 56 ------------ 7 files changed, 37 insertions(+), 172 deletions(-) delete mode 100644 contracts/examples/send-to-first-input.clar delete mode 100644 tests/send-to-first-input.test.ts diff --git a/Clarinet.toml b/Clarinet.toml index 3f17206..c6fa731 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -45,11 +45,6 @@ path = 'contracts/tests/clarity-bitcoin-helper_test.clar' clarity_version = 2 epoch = 2.1 -[contracts.send-to-first-input] -path = 'contracts/examples/send-to-first-input.clar' -clarity_version = 2 -epoch = 2.1 - [contracts.send-to-first-input-compact] path = 'contracts/examples/send-to-first-input-compact.clar' clarity_version = 2 diff --git a/contracts/clarity-bitcoin.clar b/contracts/clarity-bitcoin.clar index b79ae27..85ecfee 100644 --- a/contracts/clarity-bitcoin.clar +++ b/contracts/clarity-bitcoin.clar @@ -490,17 +490,6 @@ (let ((block (unwrap! (parse-block-header header) (err ERR-BAD-HEADER)))) (was-tx-mined-internal height tx header (get merkle-root block) proof))) -;; Determine whether or not a Bitcoin transaction was mined in a prior Bitcoin block. -;; with the given header object and merkle proof. -;; Returns (ok txid) if tx was mined else -;; returns (err u1) if the header is invalid or -;; returns (err u2) if the proof is invalid. -(define-read-only (was-tx-mined (height uint) (tx (buff 4096)) - (header { version: (buff 4), parent: (buff 32), merkle-root: (buff 32), timestamp: (buff 4), nbits: (buff 4), nonce: (buff 4) }) - (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) - (was-tx-mined-internal height tx (contract-call? .clarity-bitcoin-helper concat-header header) (reverse-buff32 (get merkle-root header)) proof)) - - ;; Private function to verify block header and merkle proof. ;; This function must only be called with the merkle root of the provided header. ;; Use was-tx-mined-compact with header as a buffer or diff --git a/contracts/examples/send-to-first-input.clar b/contracts/examples/send-to-first-input.clar deleted file mode 100644 index aed398b..0000000 --- a/contracts/examples/send-to-first-input.clar +++ /dev/null @@ -1,31 +0,0 @@ -(define-constant SATS-PER-STX u1000) -(define-constant err-not-found (err u404)) -(define-constant err-unsupported-tx (err u500)) -(define-constant err-out-not-found (err u501)) -(define-constant err-in-not-found (err u502)) - - -;; TODO get price from miners -(define-read-only (sats-to-stx (sats uint)) - (/ sats SATS-PER-STX)) - -;; for compressed public keys -(define-read-only (p2pkh-to-principal (scriptSig (buff 256))) - (let ((pk (unwrap! (as-max-len? (unwrap! (slice? scriptSig (- (len scriptSig) u33) (len scriptSig)) none) u33) none))) - (some (unwrap! (principal-of? pk) none)))) - -(define-public (send-to-first-input (height uint) (tx (buff 1024)) - (header { version: (buff 4), parent: (buff 32), merkle-root: (buff 32), timestamp: (buff 4), nbits: (buff 4), nonce: (buff 4) }) - (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) - (let ( - ;; extract parts of Bitcoin transaction - (tx-obj (try! (contract-call? .clarity-bitcoin parse-tx tx))) - (id-of-mined-tx (try! (contract-call? .clarity-bitcoin was-tx-mined height tx header proof))) - (first-output (unwrap! (element-at (get outs tx-obj) u0) err-out-not-found)) - (first-input (unwrap! (element-at (get ins tx-obj) u0) err-in-not-found))) - ;; TODO check whether the tx-sender is the same as the first output - - ;; transfer stx to first-input - (stx-transfer? (sats-to-stx (get value first-output)) - tx-sender - (unwrap! (p2pkh-to-principal (get scriptSig first-input)) err-unsupported-tx)))) diff --git a/contracts/helper.clar b/contracts/helper.clar index 2b9046d..50426a5 100644 --- a/contracts/helper.clar +++ b/contracts/helper.clar @@ -10,9 +10,6 @@ (define-public (verify-mp (reverse-tx-id (buff 32)) (merkle-root (buff 32)) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) (contract-call? .clarity-bitcoin verify-merkle-proof reverse-tx-id merkle-root proof)) -(define-public (was-tx-mined (height uint) (tx (buff 1024)) (header { version: (buff 4), parent: (buff 32), merkle-root: (buff 32), timestamp: (buff 4), nbits: (buff 4), nonce: (buff 4) }) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) - (contract-call? .clarity-bitcoin was-tx-mined height tx header proof)) - (define-public (was-tx-mined-compact (height uint) (tx (buff 1024)) (header (buff 80)) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) (contract-call? .clarity-bitcoin was-tx-mined-compact height tx header proof)) diff --git a/contracts/tests/clarity-bitcoin-helper_test.clar b/contracts/tests/clarity-bitcoin-helper_test.clar index db4d83d..dda6ede 100644 --- a/contracts/tests/clarity-bitcoin-helper_test.clar +++ b/contracts/tests/clarity-bitcoin-helper_test.clar @@ -15,3 +15,16 @@ (asserts! (is-eq result 0x1122334401112233445566778899aabbccddeeff00112233445566778899aabbccddeeff000011223380112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff001122334401001122334455667780112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff0044332211) (err result)) (ok true))) +;; @name concat header +;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 +(define-public (test-concat-header) + (let ((result (contract-call? .clarity-bitcoin-helper concat-header + {merkle-root: 0x9160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f, + version: 0x0000a020, + nbits: 0x88a12719, + nonce: 0x9842cec7, + timestamp: 0x18b84864, + parent: 0x65bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d00000000000000 + }))) + (asserts! (is-eq result 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) (err result)) + (ok true))) diff --git a/contracts/tests/clarity-bitcoin_test.clar b/contracts/tests/clarity-bitcoin_test.clar index 06b59b3..91a3c56 100644 --- a/contracts/tests/clarity-bitcoin_test.clar +++ b/contracts/tests/clarity-bitcoin_test.clar @@ -210,6 +210,30 @@ ) ) +;; @name verify transaction with wrong block height +;; arbitrary segwit transaction +(define-public (test-was-tx-mined-internal-8) + (let ( + (burnchain-block-height u1) + (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) + (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) + ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 + (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) + (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) + (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) + ) + (let ((result (contract-call? .clarity-bitcoin was-tx-mined-compact + burnchain-block-height + raw-tx + raw-block-header + {tx-index: u3, + tree-depth: u2, + hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} + ))) + (asserts! (is-eq result (err ERR-HEADER-HEIGHT-MISMATCH)) (err "expected ERR-HEADER-HEIGHT-MISMATCH")) + (ok true)) + ) +) ;; @name verify segwit transaction with left over data (define-public (test-parse-tx) @@ -238,72 +262,6 @@ ) ) -;; @name verify transaction with header object -(define-public (test-was-tx-mined-with-header-object-1) - (let ( - (burnchain-block-height u2431087) - (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) - (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) - ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 - (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) - (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) - ) - ;; prepare - - (let ((result (contract-call? .clarity-bitcoin was-tx-mined - burnchain-block-height - raw-tx - {merkle-root: 0x9160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f, - version: 0x0000a020, - nbits: 0x88a12719, - nonce: 0x9842cec7, - timestamp: 0x18b84864, - parent: 0x65bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d00000000000000, - } - {tx-index: u3, - tree-depth: u2, - hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} - ))) - (asserts! (is-eq result (ok txid)) (err "expected txid")) - (ok true)) - ) -) - - -;; @name verify transaction with header object but wrong version -(define-public (test-was-tx-mined-with-header-object-2) - (let ( - (burnchain-block-height u2431087) - (txid 0x3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084) - (raw-tx 0x020000000218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d56d182500) - ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86 - (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7) - (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) - (parsed-tx (contract-call? .clarity-bitcoin parse-tx raw-tx)) - ) - ;; prepare - - (let ((result (contract-call? .clarity-bitcoin was-tx-mined - burnchain-block-height - raw-tx - {merkle-root: 0x9160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f, - version: 0x00000000, - nbits: 0x88a12719, - nonce: 0x9842cec7, - timestamp: 0x18b84864, - parent: 0x65bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d00000000000000, - } - {tx-index: u3, - tree-depth: u2, - hashes: (list 0x3313f803502a6f9a89ac09ff9e8f9d8032aa7c35cc6d1679487622e944c8ccb8 0xc4e620f495d8a30d8d919fc148fe55c8873b4aefe43116bc6ef895aa51572215)} - ))) - (asserts! (is-eq result (err ERR-HEADER-HEIGHT-MISMATCH)) (err "expected ERR-HEADER-HEIGHT-MISMATCH")) - (ok true)) - ) -) - - (define-constant ERR-OUT-OF-BOUNDS u1) (define-constant ERR-TOO-MANY-TXINS u2) (define-constant ERR-TOO-MANY-TXOUTS u3) diff --git a/tests/send-to-first-input.test.ts b/tests/send-to-first-input.test.ts deleted file mode 100644 index 0eac109..0000000 --- a/tests/send-to-first-input.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { hexToBytes } from "./utils.ts"; -import { Cl } from "@stacks/transactions"; -import { tx as Tx } from "@hirosystems/clarinet-sdk"; -const accounts = simnet.getAccounts(); -const chain = simnet; - -describe("Send to first input", () => { - it("Ensure that scriptSig is converted to principal", () => { - const deployer = accounts.get("deployer")!; - const response = chain.callReadOnlyFn( - "send-to-first-input", - "p2pkh-to-principal", - [ - Cl.bufferFromHex( - "473044022017e2af6e1308d431365deeb5739d41a909cf0d61a9c0e48f3ae5b0bd6544bfc5022066e73dd26d71d824552b034b322603cce8b936912b99f4f3df512e502bd7c11e012103d7b3bc2d0b4b72a845c469c9fee3c8cf475a2f237e379d7f75a4f463f7bd6ebd" - ), - ], - deployer - ); - - expect(response.result).toBeSome( - Cl.standardPrincipal("ST2X7X1A0649S3BJR0DEB58NQ73E24FVWPPVK11WA") - ); - }); - - it("Ensure that users can't sent incomplete proofs", () => { - const deployer = accounts.get("deployer")!; - const block = chain.mineBlock([ - Tx.callPublicFn( - "send-to-first-input", - "send-to-first-input", - [ - Cl.uint(1), - Cl.buffer(hexToBytes("00")), - Cl.tuple({ - version: Cl.buffer(hexToBytes("00")), - parent: Cl.buffer(hexToBytes("00")), - "merkle-root": Cl.buffer(hexToBytes("00")), - timestamp: Cl.buffer(hexToBytes("00")), - nbits: Cl.buffer(hexToBytes("00")), - nonce: Cl.buffer(hexToBytes("00")), - }), - Cl.tuple({ - "tx-index": Cl.uint(1), - hashes: Cl.list([]), - "tree-depth": Cl.uint(1), - }), - ], - deployer - ), - ]); - - expect(block[0].result).toBeErr(Cl.uint(1)); - }); -}); From 1c49308a08b7286603ed7dcd5192fe247bf6a73f Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 22 Nov 2023 22:16:16 +0100 Subject: [PATCH 13/17] chore: add tests for invalid wproof --- .../tests/clarity-bitcoin_segwit_test.clar | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/contracts/tests/clarity-bitcoin_segwit_test.clar b/contracts/tests/clarity-bitcoin_segwit_test.clar index df3eec2..5872a19 100644 --- a/contracts/tests/clarity-bitcoin_segwit_test.clar +++ b/contracts/tests/clarity-bitcoin_segwit_test.clar @@ -178,9 +178,45 @@ ) ) -;; @name OP_RETURN is too large. Fails to parse segwit transaction + +;; @name verify segwit transaction where the incorrect wproof with zeros only ;; arbitrary segwit transaction (define-public (test-was-wtx-mined-internal-5) + (let ( + (burnchain-block-height u2431567) + ;; txid: 2fd0308a0bca2f4ea40fe93a19be976a40b2d5e0df08df1dd991b4df31a563fc + (raw-tx 0x020000000001017d406bb6466e0da97778f55ece77ab6becc415c930dbe618e81e8dae53ba914400000000171600143e8d4581104393d916566886ae01e26be8f8975afeffffff0276410300000000001600143d5f37543d2916547fe9b1242322b22f6e46d6929c9291d8000000001600145b3298860caeb3302f09e58b7cc3cf58d0a6740402463043021f53943d5951726d73a952473b49aa44dde64446f106749806d6478b0bdc053102200863a4f25914f3032afa8549f329962e8ecc7c575a1aefc51102a566ff5ece70012103742fe8a7244ab8384b3d533eb19138e2758c2b7a2e351c9ec2b8912cf4e046c24e1a2500) + ;; block id: 00000000096ae97d41a543592c3680477444acdc86c877aeb4832744691cb94b + (raw-block-header 0x000000208af1a70ab2ed062c7ac53eb56b053498db50f0d9c41f0dc8a5efcb1b000000007b64b9e16eb97b1fb32977aa00e2cb7418856b1e794e232be4f3b4b0512cb31256845064ffff001dc3cdbab0) + (witness-merkle-root 0xb2ea7fb39beae6b5a225c85cb7087561909e1d17bba74de3592a3c1da3944983) + (witness-reserved-data 0x0000000000000000000000000000000000000000000000000000000000000000) + ;; txid: 84f8a86015b0a95763bb16128ff301a60f668356548405178953ab1e4cbff36e + (raw-coinbase-tx 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff32034f1a2500045684506404c862b40c0c11c14a6400000000000000000a636b706f6f6c0e2f6d696e65642062792072736b2fffffffff0317ea2b00000000001976a914ec2f9ffaba0d68ea6bd7c25cedfe2ae710938e6088ac0000000000000000266a24aa21a9ede2ee16ef0a8c0fd6ccb8c78f297199f0f143629121801c46b1e1487c0123cb4b00000000000000002a6a52534b424c4f434b3acb9b4841f625100fb87055003991835f3183bff7668865e93e1f1118003a492d00000000) + (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header)) + (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx false)) + ) + + (let ((result (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact + burnchain-block-height + raw-tx + raw-block-header + u1 + u7 + (list 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000) + witness-merkle-root + witness-reserved-data + raw-coinbase-tx + (list 0xfc63a531dfb491d91ddf08dfe0d5b2406a97be193ae90fa44e2fca0b8a30d02f 0x33274cc92f8b980272688e01114cc2944fb661d1aa3a658c7d29675a46a4d5ad 0x1172bf0943aad7bc580aaab5f5d356b1c172f11c297ccf0077515309906352f2 0x2af4fd00ac79b65c0c508fbf44c22d1cf5acb084770079a94bd72ac816cfceb8 0xed4ca325a1800f0dfb2ab9d63761ecb358c014424e684c820d0d87ace45474a1 0xd3292e0e550420e500f29663dfc8ef632dbcb119c8a1ddf49aa3d32ecad83084 0x6369b65eea600edbd69b56386be9269f9662ca3f384a0ca21922ac03d2936102) + ))) + (asserts! (is-eq result (err ERR-WITNESS-TX-NOT-IN-COMMITMENT)) (err result)) + (ok true) + ) + ) +) + +;; @name OP_RETURN is too large. Fails to parse segwit transaction +;; arbitrary segwit transaction +(define-public (test-was-wtx-mined-internal-6) (let ( (burnchain-block-height u2430921) ;; txid: c770364da721e34eeb1a67f09c986fa5e4f13f9819df727e604691f42f5340a1 @@ -215,7 +251,7 @@ ;; @name verify segwit transaction where OP_RETURN is in output[1] ;; arbitrary segwit transaction -(define-public (test-was-wtx-mined-internal-6) +(define-public (test-was-wtx-mined-internal-7) (let ( (burnchain-block-height u2431087) ;; txid: 3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084 From f6854333d6c924362f466db12599b54ac25d9b00 Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 28 Nov 2023 12:45:11 +0100 Subject: [PATCH 14/17] feat: add function annotations for unit tests --- tests/clar.test.ts | 151 ++++++++++++++++----- tests/utils/clarity-parser.ts | 240 ++++++++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+), 31 deletions(-) create mode 100644 tests/utils/clarity-parser.ts diff --git a/tests/clar.test.ts b/tests/clar.test.ts index 52ca561..45d8f20 100644 --- a/tests/clar.test.ts +++ b/tests/clar.test.ts @@ -1,49 +1,138 @@ import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; import { Cl, ClarityType, cvToString } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; +import { + FunctionAnnotations, + extractTestAnnotations, +} from "./utils/clarity-parser"; + +function isTestContract(contractName: string) { + return ( + contractName.substring(contractName.length - 5) === "_test" && + contractName.substring(contractName.length - 10) !== "_flow_test" + ); +} const accounts = simnet.getAccounts(); -simnet.getContractsInterfaces().forEach((contract, name) => { - if (!name.endsWith("_test")) { +simnet.getContractsInterfaces().forEach((contract, contractFQN) => { + if (!isTestContract(contractFQN)) { return; } - describe(name, () => { - const prepare = + + describe(contractFQN, () => { + const hasDefaultPrepareFunction = contract.functions.findIndex((f) => f.name === "prepare") >= 0; - let block: ParsedTransactionResult[]; - contract.functions.forEach((fn) => { - if (!fn.name.startsWith("test-")) { + contract.functions.forEach((functionCall) => { + const functionName = functionCall.name; + if (!functionName.startsWith("test-")) { return; } + const source = simnet.getContractSource(contractFQN)!; + const annotations: any = extractTestAnnotations(source); + const functionAnnotations: FunctionAnnotations = + annotations[functionName] || {}; + + const mineBlocksBefore = + parseInt(annotations["mine-blocks-before"] as string) || 0; - it(fn.name, () => { - if (prepare) { - block = simnet.mineBlock([ - tx.callPublicFn(name, "prepare", [], accounts.get("deployer")!), - tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), - ]); - expect(block[0].result).toBeOk(Cl.bool(true)); - - if (block[1].result.type === ClarityType.ResponseErr) { - console.log(cvToString(block[1].result)); - } expect( - block[1].result, - `${name}, ${fn.name}, ${cvToString(block[0].result)}` - ).toBeOk(Cl.bool(true)); + it(`${functionCall.name}${ + functionAnnotations.name ? `: ${functionAnnotations.name}` : "" + }`, () => { + if (hasDefaultPrepareFunction && !functionAnnotations.prepare) + functionAnnotations.prepare = "prepare"; + if (functionAnnotations["no-prepare"]) + delete functionAnnotations.prepare; + + const callerAddress = functionAnnotations.caller + ? annotations.caller[0] === "'" + ? `${(annotations.caller as string).substring(1)}` + : accounts.get(annotations.caller)! + : accounts.get("deployer")!; + + if (functionAnnotations.prepare) { + mineBlockWithPrepareAndTestFunctionCall( + contractFQN, + functionAnnotations.prepare as string, + mineBlocksBefore, + functionName, + callerAddress + ); } else { - block = simnet.mineBlock([ - tx.callPublicFn(name, fn.name, [], accounts.get("deployer")!), - ]); - if (block[0].result.type === ClarityType.ResponseErr) { - console.log(cvToString(block[0].result)); - } - expect( - block[0].result, - `${name}, ${fn.name}, ${cvToString(block[0].result)}` - ).toBeOk(Cl.bool(true)); + mineBlockWithTestFunctionCall( + contractFQN, + mineBlocksBefore, + functionName, + callerAddress + ); } }); }); }); }); +function mineBlockWithPrepareAndTestFunctionCall( + contractFQN: string, + prepareFunctionName: string, + mineBlocksBefore: number, + functionName: string, + callerAddress: string +) { + if (mineBlocksBefore > 0) { + let block = simnet.mineBlock([ + tx.callPublicFn( + contractFQN, + prepareFunctionName, + [], + accounts.get("deployer")! + ), + ]); + expectOkTrue(block, contractFQN, prepareFunctionName, 0); + simnet.mineEmptyBlocks(mineBlocksBefore - 1); + + block = simnet.mineBlock([ + tx.callPublicFn(contractFQN, functionName, [], callerAddress), + ]); + + expectOkTrue(block, contractFQN, functionName, 0); + } else { + let block = simnet.mineBlock([ + tx.callPublicFn( + contractFQN, + prepareFunctionName, + [], + accounts.get("deployer")! + ), + tx.callPublicFn(contractFQN, functionName, [], callerAddress), + ]); + expectOkTrue(block, contractFQN, prepareFunctionName, 0); + expectOkTrue(block, contractFQN, functionName, 1); + } +} + +function mineBlockWithTestFunctionCall( + contractFQN: string, + mineBlocksBefore: number, + functionName: string, + callerAddress: string +) { + simnet.mineEmptyBlocks(mineBlocksBefore); + const block = simnet.mineBlock([ + tx.callPublicFn(contractFQN, functionName, [], callerAddress), + ]); + expectOkTrue(block, contractFQN, functionName, 0); +} + +function expectOkTrue( + block: ParsedTransactionResult[], + contractFQN: string, + functionName: string, + index: number = 0 +) { + if (block[index].result.type === ClarityType.ResponseErr) { + console.log(cvToString(block[index].result)); + } + expect( + block[index].result, + `${contractFQN}, ${functionName}, ${cvToString(block[index].result)}` + ).toBeOk(Cl.bool(true)); +} diff --git a/tests/utils/clarity-parser.ts b/tests/utils/clarity-parser.ts new file mode 100644 index 0000000..5e3ef95 --- /dev/null +++ b/tests/utils/clarity-parser.ts @@ -0,0 +1,240 @@ +export type FunctionAnnotations = { [key: string]: string | boolean }; +export type FunctionBody = { + callAnnotations: FunctionAnnotations[]; + callInfo: CallInfo; +}[]; + +export type ContractCall = { + callAnnotations: FunctionAnnotations; + callInfo: CallInfo; +}; + +export type CallInfo = { + contractName: string; + functionName: string; + args: { type: string; value: string }[]; +}; + +const functionRegex = + /^([ \t]{0,};;[ \t]{0,}@[^()]+?)\n[ \t]{0,}\(define-public[\s]+\((.+?)[ \t|)]/gm; +const annotationsRegex = /^;;[ \t]{1,}@([a-z-]+)(?:$|[ \t]+?(.+?))$/; + +/** + * Parser function for normal unit tests. + * + * Takes the whole contract source and returns an object containing + * the function annotations for each function + * @param contractSource + * @returns + */ +export function extractTestAnnotations(contractSource: string) { + const functionAnnotations: any = {}; + const matches = contractSource.replace(/\r/g, "").matchAll(functionRegex); + for (const [, comments, functionName] of matches) { + functionAnnotations[functionName] = {}; + const lines = comments.split("\n"); + for (const line of lines) { + const [, prop, value] = line.match(annotationsRegex) || []; + if (prop) functionAnnotations[functionName][prop] = value ?? true; + } + } + return functionAnnotations; +} + +/** + * Parser function for flow unit tests. + * + * Flow unit tests can be used for tx calls are required where + * the tx-sender should be equal to the contract-caller. + * + * Takes the whole contract source and returns an object containing + * the function annotations and function bodies for each function. + * @param contractSource + * @returns + */ +export function extractTestAnnotationsAndCalls(contractSource: string) { + const functionAnnotations: any = {}; + const functionBodies: any = {}; + contractSource = contractSource.replace(/\r/g, ""); + const matches1 = contractSource.matchAll(functionRegex); + + let indexStart: number = -1; + let headerLength: number = 0; + let indexEnd: number = -1; + let lastFunctionName: string = ""; + let contractCalls: { + callAnnotations: FunctionAnnotations; + callInfo: CallInfo; + }[]; + for (const [functionHeader, comments, functionName] of matches1) { + if (functionName.substring(0, 5) !== "test-") continue; + functionAnnotations[functionName] = {}; + const lines = comments.split("\n"); + for (const line of lines) { + const [, prop, value] = line.match(annotationsRegex) || []; + if (prop) functionAnnotations[functionName][prop] = value ?? true; + } + if (indexStart < 0) { + indexStart = contractSource.indexOf(functionHeader); + headerLength = functionHeader.length; + lastFunctionName = functionName; + } else { + indexEnd = contractSource.indexOf(functionHeader); + const lastFunctionBody = contractSource.substring( + indexStart + headerLength, + indexEnd + ); + + // add contracts calls in functions body for last function + contractCalls = extractContractCalls(lastFunctionBody); + + functionBodies[lastFunctionName] = contractCalls; + indexStart = indexEnd; + headerLength = functionHeader.length; + lastFunctionName = functionName; + } + } + const lastFunctionBody = contractSource.substring(indexStart + headerLength); + contractCalls = extractContractCalls(lastFunctionBody); + functionBodies[lastFunctionName] = contractCalls; + + return [functionAnnotations, functionBodies]; +} + +const callRegex = + /\n*^([ \t]{0,};;[ \t]{0,}@[\s\S]+?)\n[ \t]{0,}(\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\))/gm; + +/** + * Takes a string and returns an array of objects containing + * the call annotations and call info within the function body. + * + * The function body should look like this + * (begin + * ... lines of code.. + * (ok true)) + * + * Only two lines of code are accepted: + * 1. (unwrap! (contract-call? .contract-name function-name args)) + * 2. (try! (function-name)) + * @param lastFunctionBody + * @returns + */ +export function extractContractCalls(lastFunctionBody: string) { + const calls = lastFunctionBody.matchAll(callRegex); + const contractCalls: ContractCall[] = []; + for (const [, comments, call] of calls) { + const callAnnotations: FunctionAnnotations = {}; + const lines = comments.split("\n"); + for (const line of lines) { + const [, prop, value] = line.trim().match(annotationsRegex) || []; + if (prop) callAnnotations[prop] = value ?? true; + } + // try to extract call info from (unwrap! (contract-call? ...)) + let callInfo = extractUnwrapInfo(call); + if (!callInfo) { + // try to extract call info from (try! (my-function)) + callInfo = extractCallInfo(call); + } + if (callInfo) { + contractCalls.push({ callAnnotations, callInfo }); + } else { + throw new Error(`Could not extract call info from ${call}`); + } + } + return contractCalls; +} + +// take a string containing function arguments and +// split them correctly into an array of argument strings +function splitArgs(argString: string): string[] { + const splitArgs: string[] = []; + let argStart = 0; + let brackets = 0; // curly brackets + let rbrackets = 0; // round brackets + + for (let i = 0; i < argString.length; i++) { + const char = argString[i]; + + if (char === "{") brackets++; + if (char === "}") brackets--; + if (char === "(") rbrackets++; + if (char === ")") rbrackets--; + + const atLastChar = i === argString.length - 1; + if ((char === " " && brackets === 0 && rbrackets === 0) || atLastChar) { + const newArg = argString.slice(argStart, i + (atLastChar ? 1 : 0)); + if (newArg.trim()) { + splitArgs.push(newArg.trim()); + } + argStart = i + 1; + } + } + + return splitArgs; +} + +function parseTuple(tupleString: string): string { + const tupleItems = tupleString + .slice(1, -1) + .split(",") + .map((item) => { + const [key, value] = item.split(":").map((s) => s.trim()); + const uintMatch = value.match(/u(\d+)/); + if (uintMatch) { + return `"${key}": types.uint(${uintMatch[1]})`; + } else { + return `${key}: "${value}"`; + } + }) + .join(", "); + + return `types.tuple({${tupleItems}})`; +} + +function extractUnwrapInfo(statement: string): CallInfo | null { + const match = statement.match( + /\(unwrap! \(contract-call\? \.(.+?) (.+?)(( .+?)*)\)/ + ); + if (!match) return null; + + const contractName = match[1]; + const functionName = match[2]; + const argStrings = splitArgs(match[3]); + + const args = argStrings.map((arg) => parseArg(arg)); + + return { + contractName, + functionName, + args, + }; +} + +function parseArg(arg: string): { type: string; value: string } { + if (arg.startsWith("'")) { + return { type: "principal", value: `types.principal("${arg.slice(1)}")` }; + } else if (arg.startsWith("u")) { + return { type: "uint", value: `types.uint(${arg.slice(1)})` }; + } else if (arg.startsWith("{")) { + return { type: "tuple", value: parseTuple(arg) }; + } else if (arg.startsWith("(some ")) { + return { + type: "some", + value: `types.some(${parseArg(arg.substring(6, arg.length)).value})`, + }; + } else if (arg === "none") { + return { type: "none", value: "types.none()" }; + } else { + return { type: "raw", value: `"${arg}"` }; + } +} + +function extractCallInfo(statement: string) { + const match = statement.match(/\(try! \((.+?)\)\)/); + if (!match) return null; + return { contractName: "", functionName: match[1], args: [] }; +} + +export function getContractName(contractId: string) { + return contractId.split(".")[1]; +} From d1b6c95c5a2c77f4b735d0f077f0bb6970b7626c Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 29 Nov 2023 14:54:15 +0100 Subject: [PATCH 15/17] chore: add comment about feature for v5 --- contracts/clarity-bitcoin.clar | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/clarity-bitcoin.clar b/contracts/clarity-bitcoin.clar index 85ecfee..ebf2b14 100644 --- a/contracts/clarity-bitcoin.clar +++ b/contracts/clarity-bitcoin.clar @@ -1,6 +1,8 @@ ;; @contract stateless contract to verify bitcoin transaction ;; @version 5 +;; version 5 adds support for txid generation and improves security + ;; Error codes (define-constant ERR-OUT-OF-BOUNDS u1) (define-constant ERR-TOO-MANY-TXINS u2) From 6095d91e2d5da5fea6b60af41b55006e37f3b953 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Sun, 3 Dec 2023 18:54:17 +0100 Subject: [PATCH 16/17] chore: add .DS_Store to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3d8893c..65f547c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ npm-debug.log* coverage *.info costs-reports.json -node_modules \ No newline at end of file +node_modules +.DS_Store From f1af18de6bc9a212a037b80837a0dd933f4cf8d8 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Sun, 3 Dec 2023 18:54:39 +0100 Subject: [PATCH 17/17] chore: update clarity-bitcoin-v5 copy --- contracts/clarity-bitcoin-v5.clar | 559 +++++++++++++++++++++++++++++- 1 file changed, 558 insertions(+), 1 deletion(-) diff --git a/contracts/clarity-bitcoin-v5.clar b/contracts/clarity-bitcoin-v5.clar index 9d55ab4..ebf2b14 100644 --- a/contracts/clarity-bitcoin-v5.clar +++ b/contracts/clarity-bitcoin-v5.clar @@ -1 +1,558 @@ -;; TODO: to be coped from clarity-bitcoin once v5 is ready. +;; @contract stateless contract to verify bitcoin transaction +;; @version 5 + +;; version 5 adds support for txid generation and improves security + +;; Error codes +(define-constant ERR-OUT-OF-BOUNDS u1) +(define-constant ERR-TOO-MANY-TXINS u2) +(define-constant ERR-TOO-MANY-TXOUTS u3) +(define-constant ERR-VARSLICE-TOO-LONG u4) +(define-constant ERR-BAD-HEADER u5) +(define-constant ERR-HEADER-HEIGHT-MISMATCH u6) +(define-constant ERR-INVALID-MERKLE-PROOF u7) +(define-constant ERR-PROOF-TOO-SHORT u8) +(define-constant ERR-TOO-MANY-WITNESSES u9) +(define-constant ERR-INVALID-COMMITMENT u10) +(define-constant ERR-WITNESS-TX-NOT-IN-COMMITMENT u11) +(define-constant ERR-NOT-SEGWIT-TRANSACTION u12) +(define-constant ERR-LEFTOVER-DATA u13) + +;; +;; Helper functions to parse bitcoin transactions +;; + +;; Create a list with n elments `true`. n must be smaller than 9. +(define-private (bool-list-of-len (n uint)) + (unwrap-panic (slice? (list true true true true true true true true) u0 n))) + +;; Reads the next two bytes from txbuff as a little-endian 16-bit integer, and updates the index. +;; Returns (ok { uint16: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff +(define-read-only (read-uint8 (ctx { txbuff: (buff 4096), index: uint})) + (let ((data (get txbuff ctx)) + (base (get index ctx))) + (ok {uint8: (buff-to-uint-le (unwrap-panic (as-max-len? (unwrap! (slice? data base (+ base u1)) (err ERR-OUT-OF-BOUNDS)) u1))), + ctx: { txbuff: data, index: (+ u1 base)}}))) + +;; Reads the next two bytes from txbuff as a little-endian 16-bit integer, and updates the index. +;; Returns (ok { uint16: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff +(define-read-only (read-uint16 (ctx { txbuff: (buff 4096), index: uint})) + (let ((data (get txbuff ctx)) + (base (get index ctx))) + (ok {uint16: (buff-to-uint-le (unwrap-panic (as-max-len? (unwrap! (slice? data base (+ base u2)) (err ERR-OUT-OF-BOUNDS)) u2))), + ctx: { txbuff: data, index: (+ u2 base)}}))) + +;; Reads the next four bytes from txbuff as a little-endian 32-bit integer, and updates the index. +;; Returns (ok { uint32: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff +(define-read-only (read-uint32 (ctx { txbuff: (buff 4096), index: uint})) + (let ((data (get txbuff ctx)) + (base (get index ctx))) + (ok {uint32: (buff-to-uint-le (unwrap-panic (as-max-len? (unwrap! (slice? data base (+ base u4)) (err ERR-OUT-OF-BOUNDS)) u4))), + ctx: { txbuff: data, index: (+ u4 base)}}))) + +;; Reads the next eight bytes from txbuff as a little-endian 64-bit integer, and updates the index. +;; Returns (ok { uint64: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff +(define-read-only (read-uint64 (ctx { txbuff: (buff 4096), index: uint})) + (let ((data (get txbuff ctx)) + (base (get index ctx))) + (ok {uint64: (buff-to-uint-le (unwrap-panic (as-max-len? (unwrap! (slice? data base (+ base u8)) (err ERR-OUT-OF-BOUNDS)) u8))), + ctx: { txbuff: data, index: (+ u8 base)}}))) + +;; Reads the next varint from txbuff, and updates the index. +;; Returns (ok { varint: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +(define-read-only (read-varint (ctx { txbuff: (buff 4096), index: uint})) + (let ((ptr (get index ctx)) + (tx (get txbuff ctx)) + (byte (buff-to-uint-le (unwrap! (element-at tx ptr) + (err ERR-OUT-OF-BOUNDS))))) + (if (<= byte u252) + ;; given byte is the varint + (ok { varint: byte, ctx: { txbuff: tx, index: (+ u1 ptr)}}) + (if (is-eq byte u253) + (let ( + ;; next two bytes is the varint + (parsed-u16 (try! (read-uint16 { txbuff: tx, index: (+ u1 ptr)})))) + (ok { varint: (get uint16 parsed-u16), ctx: (get ctx parsed-u16)})) + (if (is-eq byte u254) + (let ( + ;; next four bytes is the varint + (parsed-u32 (try! (read-uint32 { txbuff: tx, index: (+ u1 ptr)})))) + (ok { varint: (get uint32 parsed-u32), ctx: (get ctx parsed-u32)})) + (let ( + ;; next eight bytes is the varint + (parsed-u64 (try! (read-uint64 { txbuff: tx, index: (+ u1 ptr)})))) + (ok { varint: (get uint64 parsed-u64), ctx: (get ctx parsed-u64)}))))))) + +;; Reads a varint-prefixed byte slice from txbuff, and updates the index to point to the byte after the varint and slice. +;; Returns (ok { varslice: (buff 4096), ctx: { txbuff: (buff 4096), index: uint } }) on success, where varslice has the length of the varint prefix. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +(define-read-only (read-varslice (old-ctx { txbuff: (buff 4096), index: uint})) + (let ((parsed (try! (read-varint old-ctx))) + (ctx (get ctx parsed)) + (slice-start (get index ctx)) + (target-index (+ slice-start (get varint parsed))) + (txbuff (get txbuff ctx))) + (ok {varslice: (unwrap! (slice? txbuff slice-start target-index) (err ERR-OUT-OF-BOUNDS)), + ctx: { txbuff: txbuff, index: target-index}}))) + +(define-private (reverse-buff16 (input (buff 16))) + (unwrap-panic (slice? (unwrap-panic (to-consensus-buff? (buff-to-uint-le input))) u1 u17))) + +(define-read-only (reverse-buff32 (input (buff 32))) + (unwrap-panic (as-max-len? (concat + (reverse-buff16 (unwrap-panic (as-max-len? (unwrap-panic (slice? input u16 u32)) u16))) + (reverse-buff16 (unwrap-panic (as-max-len? (unwrap-panic (slice? input u0 u16)) u16)))) u32))) + +;; Reads a little-endian hash -- consume the next 32 bytes, and reverse them. +;; Returns (ok { hashslice: (buff 32), ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +(define-read-only (read-hashslice (old-ctx { txbuff: (buff 4096), index: uint})) + (let ((slice-start (get index old-ctx)) + (target-index (+ u32 slice-start)) + (txbuff (get txbuff old-ctx)) + (hash-le (unwrap-panic + (as-max-len? (unwrap! + (slice? txbuff slice-start target-index) (err ERR-OUT-OF-BOUNDS)) u32)))) + (ok {hashslice: (reverse-buff32 hash-le), + ctx: { txbuff: txbuff, index: target-index}}))) + +;; Inner fold method to read the next tx input from txbuff. +;; The index in ctx will be updated to point to the next tx input if all goes well (or to the start of the outputs) +;; Returns (ok { ... }) on success. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptSig that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight inputs to read. +(define-read-only (read-next-txin (ignored bool) + (result (response {ctx: { txbuff: (buff 4096), index: uint }, + remaining: uint, + txins: (list 8 {outpoint: { + hash: (buff 32), + index: uint}, + scriptSig: (buff 256), ;; just big enough to hold a 2-of-3 multisig script + sequence: uint})} + uint))) + (let ((state (unwrap! result result))) + (let ((remaining (get remaining state)) + (ctx (get ctx state)) + (parsed-hash (try! (read-hashslice ctx))) + (parsed-index (try! (read-uint32 (get ctx parsed-hash)))) + (parsed-scriptSig (try! (read-varslice (get ctx parsed-index)))) + (parsed-sequence (try! (read-uint32 (get ctx parsed-scriptSig)))) + (new-ctx (get ctx parsed-sequence))) + (ok {ctx: new-ctx, + remaining: (- remaining u1), + txins: (unwrap! + (as-max-len? + (append (get txins state) { outpoint: { + hash: (get hashslice parsed-hash), + index: (get uint32 parsed-index) }, + scriptSig: (unwrap! (as-max-len? (get varslice parsed-scriptSig) u256) (err ERR-VARSLICE-TOO-LONG)), + sequence: (get uint32 parsed-sequence)}) u8) + (err ERR-TOO-MANY-TXINS))})) + )) + +;; Read a transaction's inputs. +;; Returns (ok { txins: (list { ... }), remaining: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index in ctx to point to the start of the tx outputs. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptSig that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight inputs to read. +(define-read-only (read-txins (ctx { txbuff: (buff 4096), index: uint})) + (let ((parsed-num-txins (try! (read-varint ctx))) + (num-txins (get varint parsed-num-txins)) + (new-ctx (get ctx parsed-num-txins))) + (if (> num-txins u8) + (err ERR-TOO-MANY-TXINS) + (fold read-next-txin (bool-list-of-len num-txins) (ok { ctx: new-ctx, remaining: num-txins, txins: (list)}))))) + +;; Read the next transaction output, and update the index in ctx to point to the next output. +;; Returns (ok { ... }) on success +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight outputs to read. +(define-read-only (read-next-txout (ignored bool) + (result (response {ctx: { txbuff: (buff 4096), index: uint }, + txouts: (list 8 {value: uint, + scriptPubKey: (buff 128)})} + uint))) + (let ((state (unwrap! result result)) + (parsed-value (try! (read-uint64 (get ctx state)))) + (parsed-script (try! (read-varslice (get ctx parsed-value)))) + (new-ctx (get ctx parsed-script))) + (ok {ctx: new-ctx, + txouts: (unwrap! + (as-max-len? + (append (get txouts state) + { value: (get uint64 parsed-value), + scriptPubKey: (unwrap! (as-max-len? (get varslice parsed-script) u128) (err ERR-VARSLICE-TOO-LONG))}) u8) + (err ERR-TOO-MANY-TXOUTS))}))) + +;; Read all transaction outputs in a transaction. Update the index to point to the first byte after the outputs, if all goes well. +;; Returns (ok { txouts: (list { ... }), remaining: uint, ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index in ctx to point to the start of the tx outputs. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight outputs to read. +(define-read-only (read-txouts (ctx { txbuff: (buff 4096), index: uint})) + (let ((parsed-num-txouts (try! (read-varint ctx))) + (num-txouts (get varint parsed-num-txouts)) + (new-ctx (get ctx parsed-num-txouts))) + (if (> num-txouts u8) + (err ERR-TOO-MANY-TXOUTS) + (fold read-next-txout (bool-list-of-len num-txouts) (ok { ctx: new-ctx, txouts: (list)}))))) + +;; Read the stack item of the witness field, and update the index in ctx to point to the next item. +(define-read-only (read-next-item (ignored bool) + (result (response {ctx: { txbuff: (buff 4096), index: uint }, + items: (list 8 (buff 128))} + uint))) + (let ((state (unwrap! result result)) + (parsed-item (try! (read-varslice (get ctx state)))) + (new-ctx (get ctx parsed-item))) + (ok {ctx: new-ctx, + items: (unwrap! + (as-max-len? + (append (get items state) (unwrap! (as-max-len? (get varslice parsed-item) u128) (err ERR-VARSLICE-TOO-LONG))) u8) + (err ERR-TOO-MANY-WITNESSES))}))) + +;; Read the next witness data, and update the index in ctx to point to the next witness. +(define-read-only (read-next-witness (ignored bool) + (result (response + { ctx: {txbuff: (buff 4096), index: uint}, witnesses: (list 8 (list 8 (buff 128))) } uint))) + (let ((state (unwrap! result result)) + (parsed-num-items (try! (read-varint (get ctx state)))) + (ctx (get ctx parsed-num-items)) + (varint (get varint parsed-num-items))) + (if (> varint u0) + ;; read all stack items for current txin and add to witnesses. + (let ((parsed-items (try! (fold read-next-item (bool-list-of-len varint) (ok { ctx: ctx, items: (list)}))))) + (ok { + witnesses: (unwrap-panic (as-max-len? (append (get witnesses state) (get items parsed-items)) u8)), + ctx: (get ctx parsed-items) + })) + ;; txin has not witness data, add empty list to witnesses. + (ok { + witnesses: (unwrap-panic (as-max-len? (append (get witnesses state) (list)) u8)), + ctx: ctx + })))) + +;; Read all witness data in a transaction. Update the index to point to the end of the tx, if all goes well. +;; Returns (ok {witnesses: (list 8 (list 8 (buff 128))), ctx: { txbuff: (buff 4096), index: uint } }) on success, and updates the index in ctx to point after the end of the tx. +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey that's too long to parse. +;; Returns (err ERR-TOO-MANY-WITNESSES) if there are more than eight witness data or stack items to read. +(define-read-only (read-witnesses (ctx { txbuff: (buff 4096), index: uint }) (num-txins uint)) + (fold read-next-witness (bool-list-of-len num-txins) (ok { ctx: ctx, witnesses: (list) }))) + +;; +;; Parses a Bitcoin transaction, with up to 8 inputs and 8 outputs, with scriptSigs of up to 256 bytes each, and with scriptPubKeys up to 128 bytes. +;; It will also calculate and return the TXID if calculate-txid is set to true. +;; Returns a tuple structured as follows on success: +;; (ok { +;; version: uint, ;; tx version +;; segwit-marker: uint, +;; segwit-version: uint, +;; txid: (optional (buff 32)) +;; ins: (list 8 +;; { +;; outpoint: { ;; pointer to the utxo this input consumes +;; hash: (buff 32), +;; index: uint +;; }, +;; scriptSig: (buff 256), ;; spending condition script +;; sequence: uint +;; }), +;; outs: (list 8 +;; { +;; value: uint, ;; satoshis sent +;; scriptPubKey: (buff 128) ;; parse this to get an address +;; }), +;; witnesses: (list 8 (list 8 (buff 128))), +;; locktime: uint +;; }) +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey or scriptSig that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight inputs to read. +;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight outputs to read. +;; Returns (err ERR-NOT-SEGWIT-TRANSACTION) if tx is not a segwit transaction. +;; Returns (err ERR-LEFTOVER-DATA) if the tx buffer contains leftover data at the end. +(define-read-only (parse-wtx (tx (buff 4096)) (calculate-txid bool)) + (let ((ctx { txbuff: tx, index: u0}) + (parsed-version (try! (read-uint32 ctx))) + (parsed-segwit-marker (try! (read-uint8 (get ctx parsed-version)))) + (parsed-segwit-version (try! (read-uint8 (get ctx parsed-segwit-marker)))) + (parsed-txins (try! (read-txins (get ctx parsed-segwit-version)))) + (parsed-txouts (try! (read-txouts (get ctx parsed-txins)))) + (parsed-witnesses (try! (read-witnesses (get ctx parsed-txouts) (len (get txins parsed-txins))))) + (parsed-locktime (try! (read-uint32 (get ctx parsed-witnesses)))) + ) + (asserts! (and (is-eq (get uint8 parsed-segwit-marker) u0) (is-eq (get uint8 parsed-segwit-version) u1)) (err ERR-NOT-SEGWIT-TRANSACTION)) + (asserts! (is-eq (len tx) (get index (get ctx parsed-locktime))) (err ERR-LEFTOVER-DATA)) + (ok {version: (get uint32 parsed-version), + segwit-marker: (get uint8 parsed-segwit-marker), + segwit-version: (get uint8 parsed-segwit-version), + ins: (get txins parsed-txins), + outs: (get txouts parsed-txouts), + txid: (if calculate-txid + (some (reverse-buff32 (sha256 (sha256 + (concat + (unwrap-panic (slice? tx u0 u4)) + (concat + (unwrap-panic (slice? tx (get index (get ctx parsed-segwit-version)) (get index (get ctx parsed-txouts)))) + (unwrap-panic (slice? tx (get index (get ctx parsed-witnesses)) (len tx))))))))) + none), + witnesses: (get witnesses parsed-witnesses), + locktime: (get uint32 parsed-locktime) + }))) + +;; +;; Parses a Bitcoin transaction, with up to 8 inputs and 8 outputs, with scriptSigs of up to 256 bytes each, and with scriptPubKeys up to 128 bytes. +;; Returns a tuple structured as follows on success: +;; (ok { +;; version: uint, ;; tx version +;; ins: (list 8 +;; { +;; outpoint: { ;; pointer to the utxo this input consumes +;; hash: (buff 32), +;; index: uint +;; }, +;; scriptSig: (buff 256), ;; spending condition script +;; sequence: uint +;; }), +;; outs: (list 8 +;; { +;; value: uint, ;; satoshis sent +;; scriptPubKey: (buff 128) ;; parse this to get an address +;; }), +;; locktime: uint +;; }) +;; Returns (err ERR-OUT-OF-BOUNDS) if we read past the end of txbuff. +;; Returns (err ERR-VARSLICE-TOO-LONG) if we find a scriptPubKey or scriptSig that's too long to parse. +;; Returns (err ERR-TOO-MANY-TXOUTS) if there are more than eight inputs to read. +;; Returns (err ERR-TOO-MANY-TXINS) if there are more than eight outputs to read. +;; Returns (err ERR-LEFTOVER-DATA) if the tx buffer contains leftover data at the end. +(define-read-only (parse-tx (tx (buff 4096))) + (let ((ctx { txbuff: tx, index: u0}) + (parsed-version (try! (read-uint32 ctx))) + (parsed-txins (try! (read-txins (get ctx parsed-version)))) + (parsed-txouts (try! (read-txouts (get ctx parsed-txins)))) + (parsed-locktime (try! (read-uint32 (get ctx parsed-txouts))))) + ;; check if it is a non-segwit transaction? + ;; at least check what happens + (asserts! (is-eq (len tx) (get index (get ctx parsed-locktime))) (err ERR-LEFTOVER-DATA)) + (ok {version: (get uint32 parsed-version), + ins: (get txins parsed-txins), + outs: (get txouts parsed-txouts), + locktime: (get uint32 parsed-locktime)}))) + +;; Parse a Bitcoin block header. +;; Returns a tuple structured as folowed on success: +;; (ok { +;; version: uint, ;; block version, +;; parent: (buff 32), ;; parent block hash, +;; merkle-root: (buff 32), ;; merkle root for all this block's transactions +;; timestamp: uint, ;; UNIX epoch timestamp of this block, in seconds +;; nbits: uint, ;; compact block difficulty representation +;; nonce: uint ;; PoW solution +;; }) +(define-read-only (parse-block-header (headerbuff (buff 80))) + (let ((ctx { txbuff: headerbuff, index: u0}) + (parsed-version (try! (read-uint32 ctx))) + (parsed-parent-hash (try! (read-hashslice (get ctx parsed-version)))) + (parsed-merkle-root (try! (read-hashslice (get ctx parsed-parent-hash)))) + (parsed-timestamp (try! (read-uint32 (get ctx parsed-merkle-root)))) + (parsed-nbits (try! (read-uint32 (get ctx parsed-timestamp)))) + (parsed-nonce (try! (read-uint32 (get ctx parsed-nbits))))) + (ok {version: (get uint32 parsed-version), + parent: (get hashslice parsed-parent-hash), + merkle-root: (get hashslice parsed-merkle-root), + timestamp: (get uint32 parsed-timestamp), + nbits: (get uint32 parsed-nbits), + nonce: (get uint32 parsed-nonce)}))) + +;; MOCK section +(define-constant DEBUG-MODE true) + +(define-map mock-burnchain-header-hashes uint (buff 32)) + +(define-public (mock-add-burnchain-block-header-hash (burn-height uint) (hash (buff 32))) + (ok (map-set mock-burnchain-header-hashes burn-height hash))) + +(define-read-only (get-bc-h-hash (bh uint)) + (if DEBUG-MODE (map-get? mock-burnchain-header-hashes bh) (get-burn-block-info? header-hash bh))) + +;; END MOCK section + +;; Verify that a block header hashes to a burnchain header hash at a given height. +;; Returns true if so; false if not. +(define-read-only (verify-block-header (headerbuff (buff 80)) (expected-block-height uint)) + (match (get-bc-h-hash expected-block-height) + bhh (is-eq bhh (reverse-buff32 (sha256 (sha256 headerbuff)))) + false)) + +;; Get the txid of a transaction, but little-endian. +;; This is the reverse of what you see on block explorers. +(define-read-only (get-reversed-txid (tx (buff 4096))) + (sha256 (sha256 tx))) + +;; Get the txid of a transaction. +;; This is what you see on block explorers. +(define-read-only (get-txid (tx (buff 4096))) + (reverse-buff32 (sha256 (sha256 tx)))) + +;; Determine if the ith bit in a uint is set to 1 +(define-read-only (is-bit-set (val uint) (bit uint)) + (> (bit-and val (bit-shift-left u1 bit)) u0)) + +;; Verify the next step of a Merkle proof. +;; This hashes cur-hash against the ctr-th hash in proof-hashes, and uses that as the next cur-hash. +;; The path is a bitfield describing the walk from the txid up to the merkle root: +;; * if the ith bit is 0, then cur-hash is hashed before the next proof-hash (cur-hash is "left"). +;; * if the ith bit is 1, then the next proof-hash is hashed before cur-hash (cur-hash is "right"). +;; The proof verifies if cur-hash is equal to root-hash, and we're out of proof-hashes to check. +;; Note, ctr is expected to be < (len proof-hashes), verified can be true only if ctr + 1 == (len proof-hashes). +(define-private (inner-merkle-proof-verify (ctr uint) (state { path: uint, root-hash: (buff 32), proof-hashes: (list 14 (buff 32)), tree-depth: uint, cur-hash: (buff 32), verified: bool})) + (let ((path (get path state)) + (is-left (is-bit-set path ctr)) + (proof-hashes (get proof-hashes state)) + (cur-hash (get cur-hash state)) + (root-hash (get root-hash state)) + + (h1 (if is-left (unwrap-panic (element-at proof-hashes ctr)) cur-hash)) + (h2 (if is-left cur-hash (unwrap-panic (element-at proof-hashes ctr)))) + (next-hash (sha256 (sha256 (concat h1 h2)))) + (is-verified (and (is-eq (+ u1 ctr) (len proof-hashes)) (is-eq next-hash root-hash)))) + (merge state { cur-hash: next-hash, verified: is-verified}))) + +;; Verify a Merkle proof, given the _reversed_ txid of a transaction, the merkle root of its block, and a proof consisting of: +;; * The index in the block where the transaction can be found (starting from 0), +;; * The list of hashes that link the txid to the merkle root, +;; * The depth of the block's merkle tree (required because Bitcoin does not identify merkle tree nodes as being leaves or intermediates). +;; The _reversed_ txid is required because that's the order (little-endian) processes them in. +;; The tx-index is required because it tells us the left/right traversals we'd make if we were walking down the tree from root to transaction, +;; and is thus used to deduce the order in which to hash the intermediate hashes with one another to link the txid to the merkle root. +;; Returns (ok true) if the proof is valid. +;; Returns (ok false) if the proof is invalid. +;; Returns (err ERR-PROOF-TOO-SHORT) if the proof's hashes aren't long enough to link the txid to the merkle root. +(define-read-only (verify-merkle-proof (reversed-txid (buff 32)) (merkle-root (buff 32)) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) + (if (> (get tree-depth proof) (len (get hashes proof))) + (err ERR-PROOF-TOO-SHORT) + (ok + (get verified + (fold inner-merkle-proof-verify + (unwrap-panic (slice? (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13) u0 (get tree-depth proof))) + { path: (+ (pow u2 (get tree-depth proof)) (get tx-index proof)), root-hash: merkle-root, proof-hashes: (get hashes proof), cur-hash: reversed-txid, tree-depth: (get tree-depth proof), verified: false}))))) + +;; Helper for wtxid commitments + +;; Gets the scriptPubKey in the last output that follows the 0x6a24aa21a9ed pattern regardless of its content +;; as per BIP-0141 (https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure) +(define-read-only (get-commitment-scriptPubKey (outs (list 8 { value: uint, scriptPubKey: (buff 128) }))) + (fold inner-get-commitment-scriptPubKey outs 0x)) + +(define-read-only (inner-get-commitment-scriptPubKey (out { value: uint, scriptPubKey: (buff 128) }) (result (buff 128))) + (let ((commitment (get scriptPubKey out))) + (if (is-commitment-pattern commitment) commitment result))) + +;; Returns false, if scriptPubKey does not have the commitment prefix. +(define-read-only (is-commitment-pattern (scriptPubKey (buff 128))) + (asserts! (is-eq (unwrap! (slice? scriptPubKey u0 u6) false) 0x6a24aa21a9ed) false)) + +;; +;; Top-level verification functions +;; + +;; Determine whether or not a Bitcoin transaction without witnesses +;; was mined in a prior Bitcoin block. +;; It takes the block height, the transaction, the block header and a merkle proof, and determines that: +;; * the block header corresponds to the block that was mined at the given Bitcoin height +;; * the transaction's merkle proof links it to the block header's merkle root. + +;; To verify that the merkle root is part of the block header there are two options: +;; a) read the merkle root from the header buffer +;; b) build the header buffer from its parts including the merkle root +;; +;; The merkle proof is a list of sibling merkle tree nodes that allow us to calculate the parent node from two children nodes in each merkle tree level, +;; the depth of the block's merkle tree, and the index in the block in which the given transaction can be found (starting from 0). +;; The first element in hashes must be the given transaction's sibling transaction's ID. This and the given transaction's txid are hashed to +;; calculate the parent hash in the merkle tree, which is then hashed with the *next* hash in the proof, and so on and so forth, until the final +;; hash can be compared against the block header's merkle root field. The tx-index tells us in which order to hash each pair of siblings. +;; Note that the proof hashes -- including the sibling txid -- must be _little-endian_ hashes, because this is how Bitcoin generates them. +;; This is the reverse of what you'd see in a block explorer! +;; +;; Returns (ok true) if the proof checks out. +;; Returns (ok false) if not. +;; Returns (err ERR-PROOF-TOO-SHORT) if the proof doesn't contain enough intermediate hash nodes in the merkle tree. +(define-read-only (was-tx-mined-compact (height uint) (tx (buff 4096)) + (header (buff 80)) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) + (let ((block (unwrap! (parse-block-header header) (err ERR-BAD-HEADER)))) + (was-tx-mined-internal height tx header (get merkle-root block) proof))) + +;; Private function to verify block header and merkle proof. +;; This function must only be called with the merkle root of the provided header. +;; Use was-tx-mined-compact with header as a buffer or +;; was-tx-mined with header as a tuple. +;; Returns txid if tx was mined else err u1 if the header is invalid or err u2 if the proof is invalid. +(define-private (was-tx-mined-internal (height uint) (tx (buff 4096)) (header (buff 80)) (merkle-root (buff 32)) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint})) + (if (verify-block-header header height) + (let ((reversed-txid (get-reversed-txid tx)) + (txid (reverse-buff32 reversed-txid))) + ;; verify merkle proof + (asserts! + (or + (is-eq merkle-root txid) ;; true, if the transaction is the only transaction + (try! (verify-merkle-proof reversed-txid (reverse-buff32 merkle-root) proof))) + (err ERR-INVALID-MERKLE-PROOF)) + (ok txid)) + (err ERR-HEADER-HEIGHT-MISMATCH))) + + +;; Determine whether or not a Bitcoin transaction +;; with witnesses was mined in a prior Bitcoin block. +;; It takes +;; a) the bitcoin block height, the transaction "tx" with witness data, +;; the bitcoin block header, the tx index in the block and +;; b) the depth of merkle proof of the block and +;; c) the merkle proof of the wtxid "wproof", its root "witness-merkle-proof", +;; the witness reserved value and +;; d) the coinbase transaction "ctx" without witnesses (non-segwit) and its merkle proof "cproof". +;; +;; It determines that: +;; * the block header corresponds to the block that was mined at the given Bitcoin height +;; * the coinbase tx was mined and it contains the commitment to the wtxids +;; * the wtxid of the tx is part of the commitment. +;; +;; The tree depth for wproof and cproof are the same. +;; The coinbase tx index is always 0. +;; +;; It returns (ok wtxid), if it was mined. +(define-read-only (was-segwit-tx-mined-compact + (height uint) + (wtx (buff 4096)) + (header (buff 80)) + (tx-index uint) + (tree-depth uint) + (wproof (list 14 (buff 32))) + (witness-merkle-root (buff 32)) + (witness-reserved-value (buff 32)) + (ctx (buff 1024)) + (cproof (list 14 (buff 32)))) + (begin + ;; verify that the coinbase tx is correct + (try! (was-tx-mined-compact height ctx header { tx-index: u0, hashes: cproof, tree-depth: tree-depth })) + (let ( + (witness-out (get-commitment-scriptPubKey (get outs (try! (parse-tx ctx))))) + (final-hash (sha256 (sha256 (concat witness-merkle-root witness-reserved-value)))) + (reversed-wtxid (get-reversed-txid wtx)) + (wtxid (reverse-buff32 reversed-wtxid)) + ) + ;; verify wtxid commitment + (asserts! (is-eq witness-out (concat 0x6a24aa21a9ed final-hash)) (err ERR-INVALID-COMMITMENT)) + ;; verify witness merkle tree + (asserts! (try! (verify-merkle-proof reversed-wtxid witness-merkle-root + { tx-index: tx-index, hashes: wproof, tree-depth: tree-depth })) (err ERR-WITNESS-TX-NOT-IN-COMMITMENT)) + (ok wtxid))))