diff --git a/contracts/Clarinet.toml b/contracts/Clarinet.toml index 1a04b768e..cc629dbfc 100644 --- a/contracts/Clarinet.toml +++ b/contracts/Clarinet.toml @@ -35,10 +35,4 @@ path = 'contracts/sbtc-withdrawal.clar' clarity_version = 3 epoch = 3.0 [repl.analysis] -passes = ['check_checker'] - -[repl.analysis.check_checker] -strict = false -trusted_sender = false -trusted_caller = true -callee_filter = true +passes = [] diff --git a/contracts/contracts/sbtc-bootstrap-signers.clar b/contracts/contracts/sbtc-bootstrap-signers.clar index 78acb5b15..43e7d1001 100644 --- a/contracts/contracts/sbtc-bootstrap-signers.clar +++ b/contracts/contracts/sbtc-bootstrap-signers.clar @@ -62,7 +62,6 @@ ;; Signer Key Length Check ;; Checks that the length of each key is exactly 33 bytes -;; #[allow(unchecked_data)] (define-private (signer-key-length-check (current-key (buff 33)) (helper-response (response uint uint))) (match helper-response index diff --git a/contracts/contracts/sbtc-deposit.clar b/contracts/contracts/sbtc-deposit.clar index b9d8f689d..9f9c52c38 100644 --- a/contracts/contracts/sbtc-deposit.clar +++ b/contracts/contracts/sbtc-deposit.clar @@ -93,7 +93,6 @@ ) ;; private functions -;; #[allow(unchecked_data)] (define-private (complete-individual-deposits-helper (deposit {txid: (buff 32), vout-index: uint, amount: uint, recipient: principal, burn-hash: (buff 32), burn-height: uint, sweep-txid: (buff 32)}) (helper-response (response uint uint))) (match helper-response index diff --git a/contracts/contracts/sbtc-registry.clar b/contracts/contracts/sbtc-registry.clar index c31832f5b..c6a986629 100644 --- a/contracts/contracts/sbtc-registry.clar +++ b/contracts/contracts/sbtc-registry.clar @@ -162,7 +162,6 @@ (id (increment-last-withdrawal-request-id)) ) (try! (is-protocol-caller)) - ;; #[allow(unchecked_data)] (map-insert withdrawal-requests id { amount: amount, max-fee: max-fee, @@ -188,7 +187,6 @@ ;; ;; This function will emit a print event with the topic ;; "withdrawal-accept". -;; #[allow(unchecked_data)] (define-public (complete-withdrawal-accept (request-id uint) (bitcoin-txid (buff 32)) @@ -228,7 +226,6 @@ ;; ;; This function will emit a print event with the topic ;; "withdrawal-reject". -;; #[allow(unchecked_data)] (define-public (complete-withdrawal-reject (request-id uint) (signer-bitmap uint) @@ -253,7 +250,6 @@ ;; ;; This function does not handle validation or moving the funds. ;; Instead, it is purely for the purpose of storing the completed deposit. -;; #[allow(unchecked_data)] (define-public (complete-deposit (txid (buff 32)) (vout-index uint) @@ -288,7 +284,6 @@ ;; Rotate the signer set, multi-sig principal, & aggregate pubkey ;; This function can only be called by the bootstrap-signers contract. -;; #[allow(unchecked_data)] (define-public (rotate-keys (new-keys (list 128 (buff 33))) (new-address principal) diff --git a/contracts/contracts/sbtc-token.clar b/contracts/contracts/sbtc-token.clar index 9f9c44424..faed1832a 100644 --- a/contracts/contracts/sbtc-token.clar +++ b/contracts/contracts/sbtc-token.clar @@ -1,7 +1,5 @@ (define-constant ERR_NOT_OWNER (err u4)) ;; `tx-sender` or `contract-caller` tried to move a token it does not own. -(define-constant ERR_NOT_AUTH (err u5)) ;; `tx-sender` or `contract-caller` is not the protocol caller -(define-constant ERR_TRANSFER_INDEX_PREFIX (unwrap-err! ERR_TRANSFER (err true))) -(define-constant ERR_TRANSFER (err u6)) +(define-constant ERR_TRANSFER_INDEX_PREFIX u1000) (define-fungible-token sbtc-token) (define-fungible-token sbtc-token-locked) @@ -13,7 +11,6 @@ ;; --- Protocol functions -;; #[allow(unchecked_data)] (define-public (protocol-transfer (amount uint) (sender principal) (recipient principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -21,7 +18,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-lock (amount uint) (owner principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -30,7 +26,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-unlock (amount uint) (owner principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -39,7 +34,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-mint (amount uint) (recipient principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -47,7 +41,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-burn (amount uint) (owner principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -55,7 +48,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-burn-locked (amount uint) (owner principal)) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -63,7 +55,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-set-name (new-name (string-ascii 32))) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -71,7 +62,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-set-symbol (new-symbol (string-ascii 10))) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -79,7 +69,6 @@ ) ) -;; #[allow(unchecked_data)] (define-public (protocol-set-token-uri (new-uri (optional (string-utf8 256)))) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -87,12 +76,10 @@ ) ) -;; #[allow(unchecked_data)] (define-private (protocol-mint-many-iter (item {amount: uint, recipient: principal})) (ft-mint? sbtc-token (get amount item) (get recipient item)) ) -;; #[allow(unchecked_data)] (define-public (protocol-mint-many (recipients (list 200 {amount: uint, recipient: principal}))) (begin (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller)) @@ -107,17 +94,17 @@ sender: principal, to: principal, memo: (optional (buff 34)) }))) - (fold complete-individual-transfer recipients (ok u0)) + (fold transfer-many-iter recipients (ok u0)) ) -(define-private (complete-individual-transfer +(define-private (transfer-many-iter (individual-transfer { amount: uint, sender: principal, to: principal, memo: (optional (buff 34)) }) - (helper-response (response uint uint))) - (match helper-response + (result (response uint uint))) + (match result index (begin (unwrap! @@ -129,14 +116,13 @@ (err (+ ERR_TRANSFER_INDEX_PREFIX index))) (ok (+ index u1)) ) - err-response - (err err-response) + err-index + (err err-index) ) ) ;; sip-010-trait -;; #[allow(unchecked_data)] (define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) (begin (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_OWNER) diff --git a/contracts/contracts/sbtc-withdrawal.clar b/contracts/contracts/sbtc-withdrawal.clar index 969343da6..e626cc0fa 100644 --- a/contracts/contracts/sbtc-withdrawal.clar +++ b/contracts/contracts/sbtc-withdrawal.clar @@ -235,7 +235,6 @@ ) ) -;; #[allow(unchecked_data)] (define-private (complete-individual-withdrawal-helper (withdrawal {request-id: uint, status: bool, diff --git a/contracts/docs/sbtc-bootstrap-signers.md b/contracts/docs/sbtc-bootstrap-signers.md index 00a68470e..0501a235f 100644 --- a/contracts/docs/sbtc-bootstrap-signers.md +++ b/contracts/docs/sbtc-bootstrap-signers.md @@ -92,12 +92,12 @@ the signer set is updated. ### signer-key-length-check -[View in file](../contracts/sbtc-bootstrap-signers.clar#L66) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L65) `(define-private (signer-key-length-check ((current-key (buff 33)) (helper-response (response uint uint))) (response uint uint))` Signer Key Length Check -Checks that the length of each key is exactly 33 bytes #[allow(unchecked_data)] +Checks that the length of each key is exactly 33 bytes
Source code: @@ -127,7 +127,7 @@ Checks that the length of each key is exactly 33 bytes #[allow(unchecked_data)] ### pubkeys-to-spend-script -[View in file](../contracts/sbtc-bootstrap-signers.clar#L81) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L80) `(define-read-only (pubkeys-to-spend-script ((pubkeys (list 128 (buff 33))) (m uint)) (buff 513))` @@ -160,7 +160,7 @@ Generate the p2sh redeem script for a multisig ### pubkeys-to-hash -[View in file](../contracts/sbtc-bootstrap-signers.clar#L93) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L92) `(define-read-only (pubkeys-to-hash ((pubkeys (list 128 (buff 33))) (m uint)) (buff 20))` @@ -189,7 +189,7 @@ hash160 of the p2sh redeem script ### pubkeys-to-principal -[View in file](../contracts/sbtc-bootstrap-signers.clar#L101) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L100) `(define-read-only (pubkeys-to-principal ((pubkeys (list 128 (buff 33))) (m uint)) principal)` @@ -221,7 +221,7 @@ Given a set of pubkeys and an m-of-n, generate a principal ### pubkeys-to-bytes -[View in file](../contracts/sbtc-bootstrap-signers.clar#L112) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L111) `(define-read-only (pubkeys-to-bytes ((pubkeys (list 128 (buff 33)))) (buff 510))` @@ -246,7 +246,7 @@ Concat a list of pubkeys into a buffer with length prefixes ### concat-pubkeys-fold -[View in file](../contracts/sbtc-bootstrap-signers.clar#L119) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L118) `(define-read-only (concat-pubkeys-fold ((pubkey (buff 33)) (iterator (buff 510))) (buff 510))` @@ -280,7 +280,7 @@ for the public keys and 15 bytes for the length prefixes. ### bytes-len -[View in file](../contracts/sbtc-bootstrap-signers.clar#L131) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L130) `(define-read-only (bytes-len ((bytes (buff 33))) (buff 1))` @@ -303,7 +303,7 @@ for the public keys and 15 bytes for the length prefixes. ### uint-to-byte -[View in file](../contracts/sbtc-bootstrap-signers.clar#L135) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L134) `(define-read-only (uint-to-byte ((n uint)) (buff 1))` @@ -403,4 +403,4 @@ equal to 100% of the total number of signer keys. )) ``` -[View in file](../contracts/sbtc-bootstrap-signers.clar#L139) +[View in file](../contracts/sbtc-bootstrap-signers.clar#L138) diff --git a/contracts/tests/clarigen-types.ts b/contracts/tests/clarigen-types.ts index 4131e5536..a6d333b6a 100644 --- a/contracts/tests/clarigen-types.ts +++ b/contracts/tests/clarigen-types.ts @@ -1264,8 +1264,35 @@ export const contracts = { }, sbtcToken: { functions: { - completeIndividualTransfer: { - name: "complete-individual-transfer", + protocolMintManyIter: { + name: "protocol-mint-many-iter", + access: "private", + args: [ + { + name: "item", + type: { + tuple: [ + { name: "amount", type: "uint128" }, + { name: "recipient", type: "principal" }, + ], + }, + }, + ], + outputs: { type: { response: { ok: "bool", error: "uint128" } } }, + } as TypedAbiFunction< + [ + item: TypedAbiArg< + { + amount: number | bigint; + recipient: string; + }, + "item" + >, + ], + Response + >, + transferManyIter: { + name: "transfer-many-iter", access: "private", args: [ { @@ -1283,7 +1310,7 @@ export const contracts = { }, }, { - name: "helper-response", + name: "result", type: { response: { ok: "uint128", error: "uint128" } }, }, ], @@ -1299,40 +1326,13 @@ export const contracts = { }, "individualTransfer" >, - helperResponse: TypedAbiArg< + result: TypedAbiArg< Response, - "helperResponse" + "result" >, ], Response >, - protocolMintManyIter: { - name: "protocol-mint-many-iter", - access: "private", - args: [ - { - name: "item", - type: { - tuple: [ - { name: "amount", type: "uint128" }, - { name: "recipient", type: "principal" }, - ], - }, - }, - ], - outputs: { type: { response: { ok: "bool", error: "uint128" } } }, - } as TypedAbiFunction< - [ - item: TypedAbiArg< - { - amount: number | bigint; - recipient: string; - }, - "item" - >, - ], - Response - >, protocolBurn: { name: "protocol-burn", access: "public", @@ -1636,16 +1636,6 @@ export const contracts = { }, maps: {}, variables: { - ERR_NOT_AUTH: { - name: "ERR_NOT_AUTH", - type: { - response: { - ok: "none", - error: "uint128", - }, - }, - access: "constant", - } as TypedAbiVariable>, ERR_NOT_OWNER: { name: "ERR_NOT_OWNER", type: { @@ -1656,16 +1646,6 @@ export const contracts = { }, access: "constant", } as TypedAbiVariable>, - ERR_TRANSFER: { - name: "ERR_TRANSFER", - type: { - response: { - ok: "none", - error: "uint128", - }, - }, - access: "constant", - } as TypedAbiVariable>, ERR_TRANSFER_INDEX_PREFIX: { name: "ERR_TRANSFER_INDEX_PREFIX", type: "uint128", @@ -1707,19 +1687,11 @@ export const contracts = { } as TypedAbiVariable, }, constants: { - ERR_NOT_AUTH: { - isOk: false, - value: 5n, - }, ERR_NOT_OWNER: { isOk: false, value: 4n, }, - ERR_TRANSFER: { - isOk: false, - value: 6n, - }, - ERR_TRANSFER_INDEX_PREFIX: 6n, + ERR_TRANSFER_INDEX_PREFIX: 1_000n, tokenDecimals: 8n, tokenName: "sBTC", tokenSymbol: "sBTC", diff --git a/contracts/tests/sbtc-deposit.test.ts b/contracts/tests/sbtc-deposit.test.ts index 42088c154..56c336c1b 100644 --- a/contracts/tests/sbtc-deposit.test.ts +++ b/contracts/tests/sbtc-deposit.test.ts @@ -98,7 +98,7 @@ describe("sBTC deposit contract", () => { deployer ); expect(receipt0.value).toEqual(true); - simnet.mineEmptyBlock() + simnet.mineEmptyBlock(); const receipt1 = txErr( deposit.completeDepositWrapper({ txid: new Uint8Array(32).fill(1), @@ -321,7 +321,7 @@ describe("optimization tests", () => { const { burnHeight, burnHash } = getCurrentBurnInfo(); const totalAmount = 1000000n; - const runs = 650; + const runs = 400; const txids = randomPublicKeys(runs).map((pk) => pk.slice(0, 32)); txOk( deposit.completeDepositsWrapper({ diff --git a/contracts/tests/sbtc-token.test.ts b/contracts/tests/sbtc-token.test.ts index 2779c5bfb..d80e8dd82 100644 --- a/contracts/tests/sbtc-token.test.ts +++ b/contracts/tests/sbtc-token.test.ts @@ -17,6 +17,7 @@ import { deposit, errors, getCurrentBurnInfo, + signers, token, tokenTest, } from "./helpers"; @@ -263,8 +264,8 @@ describe("sBTC token contract", () => { }), bob ); - // Many-transfer errors start at 6, error happens at index 0 - expect(receipt1.value).toEqual(errors.token.ERR_TRANSFER); + // Many-transfer errors start at 1000, error happens at index 0 + expect(receipt1.value).toEqual(1000n); }); test("Mint & transfer multiple sbtc token, fail on second", () => { @@ -320,8 +321,8 @@ describe("sBTC token contract", () => { }), alice ); - // Many-transfer errors start at 6, error happens at index 1 - expect(receipt1.value).toEqual(7n); + // Many-transfer errors start at 1000, error happens at index 1 + expect(receipt1.value).toEqual(1001n); }); test("Mint & transfer multiple sbtc token, contract principal", () => { @@ -399,17 +400,159 @@ describe("sBTC token contract", () => { expect(receipt.value).toEqual(errors.registry.ERR_UNAUTHORIZED); }); + test("Fail a non-protocol principal calling protocol-locked", () => { + const receipt = txErr( + token.protocolLock({ + amount: 1000n, + owner: bob, + }), + bob + ); + expect(receipt.value).toEqual(errors.registry.ERR_UNAUTHORIZED); + }); + + test("Fail a non-protocol principal calling protocol-burn", () => { + const receipt = txErr( + token.protocolBurn({ + amount: 1000n, + owner: bob, + }), + bob + ); + expect(receipt.value).toEqual(errors.registry.ERR_UNAUTHORIZED); + }); + + test("Fail a non-protocol principal calling protocol-burn-locked", () => { + const receipt = txErr( + token.protocolBurnLocked({ + amount: 1000n, + owner: bob, + }), + bob + ); + expect(receipt.value).toEqual(errors.registry.ERR_UNAUTHORIZED); + }); + test("Fail transferring sbtc when not owner", () => { const receipt1 = txErr( token.transfer({ amount: 999n, sender: alice, recipient: bob, - memo: new Uint8Array(1).fill(0), + memo: null, }), bob ); expect(receipt1.value).toEqual(errors.token.ERR_NOT_OWNER); }); }); + + describe("protocol actions", () => { + test("Mint, lock and transfer by protocol", () => { + const receipt0 = txOk( + signers.rotateKeysWrapper({ + newKeys: [new Uint8Array(33).fill(0), new Uint8Array(33).fill(0)], + newAggregatePubkey: new Uint8Array(33).fill(0), + newSignatureThreshold: 2n, + }), + deployer + ); + expect(receipt0.value).toEqual(true); + + const receipt1 = txOk( + token.protocolMint({ + amount: 1000n, + recipient: bob, + }), + deployer + ); + expect(receipt1.value).toEqual(true); + + const supply1 = rov(token.getTotalSupply()); + expect(supply1.value).toEqual(1000n); + checkBalances(bob, [1000n, 1000n, 0n]); + + const receipt2 = txOk( + token.protocolLock({ + amount: 222n, + owner: bob, + }), + deployer + ); + expect(receipt2.value).toEqual(true); + const supply2 = rov(token.getTotalSupply()); + expect(supply2.value).toEqual(1000n); + checkBalances(bob, [1000n, 778n, 222n]); + + // try to transfer more than available + const receipt3 = txErr( + token.protocolTransfer({ + amount: 900n, + sender: bob, + recipient: charlie, + }), + deployer + ); + expect(receipt3.value).toEqual(1n); // err not enough tokens + + // transfer less than available + const receipt4 = txOk( + token.protocolTransfer({ + amount: 500n, + sender: bob, + recipient: charlie, + }), + deployer + ); + expect(receipt4.value).toEqual(true); + checkBalances(bob, [500n, 278n, 222n]); + + // unlock more than locked + const receipt5 = txErr( + token.protocolUnlock({ + amount: 500n, + owner: bob, + }), + deployer + ); + expect(receipt5.value).toEqual(1n); + + // unlock all locked tokens + const receipt6 = txOk( + token.protocolUnlock({ + amount: 222n, + owner: bob, + }), + deployer + ); + expect(receipt6.value).toEqual(true); + checkBalances(bob, [500n, 500n, 0n]); + }); + }); }); + +function checkBalances(who: string, amounts: bigint[]) { + const balance = rov( + token.getBalance({ + who, + }), + who + ); + expect(balance.value).toEqual(amounts[0]); + + const available = rov( + token.getBalanceAvailable({ + who, + }), + who + ); + expect(available.value).toEqual(amounts[1]); + + const locked = rov( + token.getBalanceLocked({ + who, + }), + who + ); + expect(locked.value).toEqual(amounts[2]); +}