diff --git a/contracts/Clarinet.toml b/contracts/Clarinet.toml
index cc629dbfc..ce7f9ac27 100644
--- a/contracts/Clarinet.toml
+++ b/contracts/Clarinet.toml
@@ -15,6 +15,11 @@ path = 'contracts/sbtc-deposit.clar'
clarity_version = 3
epoch = 3.0
+[contracts.sbtc-deposit-update-test]
+path = 'contracts/sbtc-deposit-update-test.clar'
+clarity_version = 3
+epoch = 3.0
+
[contracts.sbtc-registry]
path = 'contracts/sbtc-registry.clar'
clarity_version = 3
diff --git a/contracts/contracts/sbtc-bootstrap-signers.clar b/contracts/contracts/sbtc-bootstrap-signers.clar
index b2672bdfa..bbe0d3032 100644
--- a/contracts/contracts/sbtc-bootstrap-signers.clar
+++ b/contracts/contracts/sbtc-bootstrap-signers.clar
@@ -1,12 +1,9 @@
;; sBTC Bootstrap Signers contract
;; constants
-
;; The required length of public keys
(define-constant key-size u33)
-;; errors
-
;; if err is u200, it's the agg key
;; if err is u210>, it's the key at index (err - 210)
(define-constant ERR_KEY_SIZE_PREFIX (unwrap-err! ERR_KEY_SIZE (err true)))
@@ -17,14 +14,6 @@
;; equal to 100% of the total number of signer keys.
(define-constant ERR_SIGNATURE_THRESHOLD (err u202))
-;; data vars
-;;
-
-;; data maps
-;;
-
-;; public functions
-
;; Rotate keys
;; Used to rotate the keys of the signers. This is called whenever
;; the signer set is updated.
@@ -35,15 +24,18 @@
)
(let
(
- (current-signer-data (contract-call? .sbtc-registry get-current-signer-data))
(new-signer-principal (pubkeys-to-principal new-keys new-signature-threshold))
)
+
+ ;; Check that more than 1 key is in the new set
+ (asserts! (> (len new-keys) u1) ERR_KEY_SIZE)
+
;; Check that the signature threshold is valid
(asserts! (and (> new-signature-threshold (/ (len new-keys) u2))
(<= new-signature-threshold (len new-keys))) ERR_SIGNATURE_THRESHOLD)
- ;; Check that the caller is the current signer principal
- (asserts! (is-eq (get current-signer-principal current-signer-data) tx-sender) ERR_INVALID_CALLER)
+ ;; Check that the tx-sender is the current signer principal
+ (asserts! (is-eq (contract-call? .sbtc-registry get-current-signer-principal) tx-sender) ERR_INVALID_CALLER)
;; Checks that length of each key is exactly 33 bytes
(try! (fold signer-key-length-check new-keys (ok u0)))
@@ -52,13 +44,22 @@
(asserts! (is-eq (len new-aggregate-pubkey) key-size) ERR_KEY_SIZE)
;; Call into .sbtc-registry to update the keys & address
- (ok (try! (contract-call? .sbtc-registry rotate-keys new-keys new-signer-principal new-aggregate-pubkey new-signature-threshold)))
+ (contract-call? .sbtc-registry rotate-keys new-keys new-signer-principal new-aggregate-pubkey new-signature-threshold)
)
)
-;; read only functions
+;; Update protocol contract
+;; Used to update one of the three protocol contracts
+(define-public (update-protocol-contract-wrapper (contract-type (buff 1)) (contract-address principal))
+ (begin
+ ;; Check that the tx-sender is the current signer principal
+ (asserts! (is-eq (contract-call? .sbtc-registry get-current-signer-principal) tx-sender) ERR_INVALID_CALLER)
+ ;; Call into .sbtc-registry to update the protocol contract
+ (contract-call? .sbtc-registry update-protocol-contract contract-type contract-address)
+ )
+)
-;; private functions
+;; read only functions
;; Signer Key Length Check
;; Checks that the length of each key is exactly 33 bytes
@@ -125,8 +126,6 @@
)
)
-
-
(define-read-only (bytes-len (bytes (buff 33)))
(unwrap-panic (element-at BUFF_TO_BYTE (len bytes)))
)
diff --git a/contracts/contracts/sbtc-deposit-update-test.clar b/contracts/contracts/sbtc-deposit-update-test.clar
new file mode 100644
index 000000000..8048fd4b9
--- /dev/null
+++ b/contracts/contracts/sbtc-deposit-update-test.clar
@@ -0,0 +1,107 @@
+;; sBTC Deposit v1 contract (updated)
+
+;; constants
+
+;; The required length of a txid
+(define-constant txid-length u32)
+(define-constant dust-limit u546)
+
+;; protocol contract type
+(define-constant deposit-role 0x01)
+
+;; error codes
+;; TXID used in deposit is not the correct length
+(define-constant ERR_TXID_LEN (err u300))
+;; Deposit has already been completed
+(define-constant ERR_DEPOSIT_REPLAY (err u301))
+(define-constant ERR_LOWER_THAN_DUST (err u302))
+(define-constant ERR_DEPOSIT_INDEX_PREFIX (unwrap-err! ERR_DEPOSIT (err true)))
+(define-constant ERR_DEPOSIT (err u303))
+(define-constant ERR_INVALID_CALLER (err u304))
+(define-constant ERR_INVALID_BURN_HASH (err u305))
+
+;; public functions
+
+;; Accept a new deposit request
+;; Note that this function can only be called by the current
+;; bootstrap signer set address - it cannot be called by users directly.
+;; This function handles the validation & minting of sBTC, it then calls
+;; into the sbtc-registry contract to update the state of the protocol
+(define-public (complete-deposit-wrapper (txid (buff 32))
+ (vout-index uint)
+ (amount uint)
+ (recipient principal)
+ (burn-hash (buff 32))
+ (burn-height uint)
+ (sweep-txid (buff 32)))
+ (let
+ (
+ (current-signer-data (contract-call? .sbtc-registry get-current-signer-data))
+ (replay-fetch (contract-call? .sbtc-registry get-deposit-status txid vout-index))
+ )
+
+ ;; Check that the caller is the current signer principal
+ (asserts! (is-eq (get current-signer-principal current-signer-data) tx-sender) ERR_INVALID_CALLER)
+
+ ;; Check that amount is greater than dust limit
+ (asserts! (>= amount dust-limit) ERR_LOWER_THAN_DUST)
+
+ ;; Check that txid is the correct length
+ (asserts! (is-eq (len txid) txid-length) ERR_TXID_LEN)
+
+ ;; Check that sweep txid is the correct length
+ (asserts! (is-eq (len sweep-txid) txid-length) ERR_TXID_LEN)
+
+ ;; Assert that the deposit has not already been completed (no replay)
+ (asserts! (is-none replay-fetch) ERR_DEPOSIT_REPLAY)
+
+ ;; Verify that Bitcoin hasn't forked by comparing the burn hash provided
+ (asserts! (is-eq (some burn-hash) (get-burn-header burn-height)) ERR_INVALID_BURN_HASH)
+
+ ;; Mint the sBTC to the recipient
+ (try! (contract-call? .sbtc-token protocol-mint amount recipient deposit-role))
+
+ ;; Complete the deposit
+ (contract-call? .sbtc-registry complete-deposit txid vout-index amount recipient burn-hash burn-height sweep-txid)
+ )
+)
+
+;; Return the bitcoin header hash of the bitcoin block at the given height.
+(define-read-only (get-burn-header (height uint))
+ (get-burn-block-info? header-hash height)
+)
+
+;; Accept multiple new deposit requests
+;; Note that this function can only be called by the current
+;; bootstrap signer set address - it cannot be called by users directly.
+;;
+;; This function handles the validation & minting of sBTC by handling multiple (up to 1000) deposits at a time,
+;; it then calls into the sbtc-registry contract to update the state of the protocol.
+(define-public (complete-deposits-wrapper
+ (deposits (list 650 {txid: (buff 32), vout-index: uint, amount: uint, recipient: principal, burn-hash: (buff 32), burn-height: uint, sweep-txid: (buff 32)}))
+ )
+ (begin
+ ;; Check that the caller is the current signer principal
+ (asserts! (is-eq
+ (contract-call? .sbtc-registry get-current-signer-principal)
+ tx-sender
+ ) ERR_INVALID_CALLER)
+
+ (fold complete-individual-deposits-helper deposits (ok u0))
+ )
+)
+
+;; 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
+ (begin
+ (unwrap! (complete-deposit-wrapper (get txid deposit) (get vout-index deposit) (get amount deposit) (get recipient deposit) (get burn-hash deposit) (get burn-height deposit) (get sweep-txid deposit)) (err (+ ERR_DEPOSIT_INDEX_PREFIX (+ u10 index))))
+ (ok (+ index u1))
+ )
+ err-response
+ (err err-response)
+ )
+)
+
diff --git a/contracts/contracts/sbtc-deposit.clar b/contracts/contracts/sbtc-deposit.clar
index ec0d15b44..12969f920 100644
--- a/contracts/contracts/sbtc-deposit.clar
+++ b/contracts/contracts/sbtc-deposit.clar
@@ -6,6 +6,9 @@
(define-constant txid-length u32)
(define-constant dust-limit u546)
+;; protocol contract type
+(define-constant deposit-role 0x01)
+
;; error codes
;; TXID used in deposit is not the correct length
(define-constant ERR_TXID_LEN (err u300))
@@ -17,10 +20,6 @@
(define-constant ERR_INVALID_CALLER (err u304))
(define-constant ERR_INVALID_BURN_HASH (err u305))
-;; data vars
-
-;; data maps
-
;; public functions
;; Accept a new deposit request
@@ -60,7 +59,7 @@
(asserts! (is-eq (some burn-hash) (get-burn-header burn-height)) ERR_INVALID_BURN_HASH)
;; Mint the sBTC to the recipient
- (try! (contract-call? .sbtc-token protocol-mint amount recipient))
+ (try! (contract-call? .sbtc-token protocol-mint amount recipient deposit-role))
;; Complete the deposit
(contract-call? .sbtc-registry complete-deposit txid vout-index amount recipient burn-hash burn-height sweep-txid)
diff --git a/contracts/contracts/sbtc-registry.clar b/contracts/contracts/sbtc-registry.clar
index c6a986629..5f0e45961 100644
--- a/contracts/contracts/sbtc-registry.clar
+++ b/contracts/contracts/sbtc-registry.clar
@@ -1,23 +1,40 @@
;; sBTC Registry contract
;; Error codes
-
(define-constant ERR_UNAUTHORIZED (err u400))
(define-constant ERR_INVALID_REQUEST_ID (err u401))
(define-constant ERR_AGG_PUBKEY_REPLAY (err u402))
(define-constant ERR_MULTI_SIG_REPLAY (err u403))
+(define-constant ERR_INVALID_PROTOCOL_ID (err u404))
+(define-constant ERR_UNAUTHORIZE_FLAG (err u405))
+(define-constant ERR_UNAUTHORIZED_ROLE (err u406))
-;; Variables
+;; Protocol contract type
+(define-constant governance-role 0x00)
+(define-constant deposit-role 0x01)
+(define-constant withdrawal-role 0x02)
+
+;; Variables
(define-data-var last-withdrawal-request-id uint u0)
(define-data-var current-signature-threshold uint u0)
(define-data-var current-signer-set (list 128 (buff 33)) (list))
(define-data-var current-aggregate-pubkey (buff 33) 0x00)
(define-data-var current-signer-principal principal tx-sender)
-
;; Maps
-
+;; Active protocol contracts
+(define-map active-protocol-contracts (buff 1) principal)
+(map-set active-protocol-contracts governance-role .sbtc-bootstrap-signers)
+(map-set active-protocol-contracts deposit-role .sbtc-deposit)
+(map-set active-protocol-contracts withdrawal-role .sbtc-withdrawal)
+(if is-in-mainnet true (map-set active-protocol-contracts 0x03 tx-sender))
+;; Role for active protocol contracts
+(define-map active-protocol-roles principal (buff 1))
+(map-set active-protocol-roles .sbtc-bootstrap-signers governance-role)
+(map-set active-protocol-roles .sbtc-deposit deposit-role)
+(map-set active-protocol-roles .sbtc-withdrawal withdrawal-role)
+(if is-in-mainnet true (map-set active-protocol-roles tx-sender 0x03))
;; Internal data structure to store withdrawal
;; requests. Requests are associated with a unique
;; request ID.
@@ -73,13 +90,6 @@
;; stored to avoid replay
(define-map aggregate-pubkeys (buff 33) bool)
-;; Data structure to store the active protocol contracts
-(define-map protocol-contracts principal bool)
-(map-set protocol-contracts .sbtc-bootstrap-signers true)
-(map-set protocol-contracts .sbtc-deposit true)
-(map-set protocol-contracts .sbtc-withdrawal true)
-(if (not is-in-mainnet) (map-set protocol-contracts tx-sender true) true)
-
;; Read-only functions
;; Get a withdrawal request by its ID.
;; This function returns the fields of the withdrawal
@@ -138,6 +148,10 @@
(var-get current-signer-set)
)
+(define-read-only (get-active-protocol (contract-flag (buff 1)))
+ (map-get? active-protocol-contracts contract-flag)
+)
+
;; Public functions
@@ -161,7 +175,8 @@
(
(id (increment-last-withdrawal-request-id))
)
- (try! (is-protocol-caller))
+ (try! (is-protocol-caller withdrawal-role contract-caller))
+ ;; #[allow(unchecked_data)]
(map-insert withdrawal-requests id {
amount: amount,
max-fee: max-fee,
@@ -198,7 +213,7 @@
(sweep-txid (buff 32))
)
(begin
- (try! (is-protocol-caller))
+ (try! (is-protocol-caller withdrawal-role contract-caller))
;; Mark the withdrawal as completed
(map-insert withdrawal-status request-id true)
(map-insert completed-withdrawal-sweep request-id {
@@ -231,7 +246,7 @@
(signer-bitmap uint)
)
(begin
- (try! (is-protocol-caller))
+ (try! (is-protocol-caller withdrawal-role contract-caller))
;; Mark the withdrawal as completed
(map-insert withdrawal-status request-id false)
(print {
@@ -260,7 +275,7 @@
(sweep-txid (buff 32))
)
(begin
- (try! (is-protocol-caller))
+ (try! (is-protocol-caller deposit-role contract-caller))
(map-insert deposit-status {txid: txid, vout-index: vout-index} true)
(map-insert completed-deposits {txid: txid, vout-index: vout-index} {
amount: amount,
@@ -292,7 +307,7 @@
)
(begin
;; Check that caller is protocol contract
- (try! (is-protocol-caller))
+ (try! (is-protocol-caller governance-role contract-caller))
;; Check that the aggregate pubkey is not already in the map
(asserts! (map-insert aggregate-pubkeys new-aggregate-pubkey true) ERR_AGG_PUBKEY_REPLAY)
;; Update the current signer set
@@ -314,8 +329,29 @@
)
)
-;; Private functions
+;; Update protocol contract
+;; This function can only be called by the active bootstrap-signers contract
+(define-public (update-protocol-contract
+ (contract-type (buff 1))
+ (new-contract principal)
+ )
+ (begin
+ ;; Check that caller is protocol contract
+ (try! (is-protocol-caller governance-role contract-caller))
+ ;; Update the protocol contract
+ (map-set active-protocol-contracts contract-type new-contract)
+ ;; Update the protocol role
+ (map-set active-protocol-roles new-contract contract-type)
+ (print {
+ topic: "update-protocol-contract",
+ contract-type: contract-type,
+ new-contract: new-contract,
+ })
+ (ok true)
+ )
+)
+;; Private functions
;; Increment the last withdrawal request ID and
;; return the new value.
(define-private (increment-last-withdrawal-request-id)
@@ -329,11 +365,17 @@
)
;; Checks whether the contract-caller is a protocol contract
-(define-read-only (is-protocol-caller)
- (validate-protocol-caller contract-caller)
+(define-read-only (is-protocol-caller (contract-flag (buff 1)) (contract principal))
+ (validate-protocol-caller contract-flag contract)
)
;; Validate that a given principal is a protocol contract
-(define-read-only (validate-protocol-caller (caller principal))
- (ok (asserts! (is-some (map-get? protocol-contracts caller)) ERR_UNAUTHORIZED))
+(define-read-only (validate-protocol-caller (contract-flag (buff 1)) (contract principal))
+ (begin
+ ;; Check that contract-caller is an protocol contract
+ (asserts! (is-eq (some contract) (map-get? active-protocol-contracts contract-flag)) ERR_UNAUTHORIZED)
+ ;; Check that flag matches the contract-caller
+ (asserts! (is-eq (some contract-flag) (map-get? active-protocol-roles contract)) ERR_UNAUTHORIZED)
+ (ok true)
+ )
)
diff --git a/contracts/contracts/sbtc-token.clar b/contracts/contracts/sbtc-token.clar
index faed1832a..95322966d 100644
--- a/contracts/contracts/sbtc-token.clar
+++ b/contracts/contracts/sbtc-token.clar
@@ -6,72 +6,72 @@
(define-data-var token-name (string-ascii 32) "sBTC")
(define-data-var token-symbol (string-ascii 10) "sBTC")
-(define-data-var token-uri (optional (string-utf8 256)) none)
+(define-data-var token-uri (optional (string-utf8 256)) (some u"https://ipfs.io/ipfs/bafkreibqnozdui4ntgoh3oo437lvhg7qrsccmbzhgumwwjf2smb3eegyqu"))
(define-constant token-decimals u8)
;; --- Protocol functions
-(define-public (protocol-transfer (amount uint) (sender principal) (recipient principal))
+(define-public (protocol-transfer (amount uint) (sender principal) (recipient principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ft-transfer? sbtc-token amount sender recipient)
)
)
-(define-public (protocol-lock (amount uint) (owner principal))
+(define-public (protocol-lock (amount uint) (owner principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(try! (ft-burn? sbtc-token amount owner))
(ft-mint? sbtc-token-locked amount owner)
)
)
-(define-public (protocol-unlock (amount uint) (owner principal))
+(define-public (protocol-unlock (amount uint) (owner principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(try! (ft-burn? sbtc-token-locked amount owner))
(ft-mint? sbtc-token amount owner)
)
)
-(define-public (protocol-mint (amount uint) (recipient principal))
+(define-public (protocol-mint (amount uint) (recipient principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ft-mint? sbtc-token amount recipient)
)
)
-(define-public (protocol-burn (amount uint) (owner principal))
+(define-public (protocol-burn (amount uint) (owner principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ft-burn? sbtc-token amount owner)
)
)
-(define-public (protocol-burn-locked (amount uint) (owner principal))
+(define-public (protocol-burn-locked (amount uint) (owner principal) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ft-burn? sbtc-token-locked amount owner)
)
)
-(define-public (protocol-set-name (new-name (string-ascii 32)))
+(define-public (protocol-set-name (new-name (string-ascii 32)) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ok (var-set token-name new-name))
)
)
-(define-public (protocol-set-symbol (new-symbol (string-ascii 10)))
+(define-public (protocol-set-symbol (new-symbol (string-ascii 10)) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ok (var-set token-symbol new-symbol))
)
)
-(define-public (protocol-set-token-uri (new-uri (optional (string-utf8 256))))
+(define-public (protocol-set-token-uri (new-uri (optional (string-utf8 256))) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ok (var-set token-uri new-uri))
)
)
@@ -80,9 +80,9 @@
(ft-mint? sbtc-token (get amount item) (get recipient item))
)
-(define-public (protocol-mint-many (recipients (list 200 {amount: uint, recipient: principal})))
+(define-public (protocol-mint-many (recipients (list 200 {amount: uint, recipient: principal})) (contract-flag (buff 1)))
(begin
- (try! (contract-call? .sbtc-registry validate-protocol-caller contract-caller))
+ (try! (contract-call? .sbtc-registry validate-protocol-caller contract-flag contract-caller))
(ok (map protocol-mint-many-iter recipients))
)
)
diff --git a/contracts/contracts/sbtc-withdrawal.clar b/contracts/contracts/sbtc-withdrawal.clar
index e626cc0fa..b36d1b738 100644
--- a/contracts/contracts/sbtc-withdrawal.clar
+++ b/contracts/contracts/sbtc-withdrawal.clar
@@ -29,6 +29,8 @@
(define-constant MAX_ADDRESS_VERSION_BUFF_32 u6)
;; The minimum amount of sBTC you can withdraw
(define-constant DUST_LIMIT u546)
+;; protocol contract type
+(define-constant withdraw-role 0x02)
;; Initiate a new withdrawal request.
;;
@@ -130,7 +132,7 @@
(max-fee uint)
)
(begin
- (try! (contract-call? .sbtc-token protocol-lock (+ amount max-fee) tx-sender))
+ (try! (contract-call? .sbtc-token protocol-lock (+ amount max-fee) tx-sender withdraw-role))
(asserts! (> amount DUST_LIMIT) ERR_DUST_LIMIT)
;; Validate the recipient address
@@ -171,12 +173,12 @@
(asserts! (<= fee requested-max-fee) ERR_FEE_TOO_HIGH)
;; Burn the locked-sbtc
- (try! (contract-call? .sbtc-token protocol-burn-locked (+ requested-amount requested-max-fee) requester))
+ (try! (contract-call? .sbtc-token protocol-burn-locked (+ requested-amount requested-max-fee) requester withdraw-role))
;; Mint the difference b/w max-fee of the request & fee actually paid back to the user in sBTC
(if (is-eq (- requested-max-fee fee) u0)
true
- (try! (contract-call? .sbtc-token protocol-mint (- requested-max-fee fee) requester))
+ (try! (contract-call? .sbtc-token protocol-mint (- requested-max-fee fee) requester withdraw-role))
)
;; Call into registry to confirm accepted withdrawal
@@ -204,7 +206,7 @@
(asserts! (is-none (get status withdrawal)) ERR_ALREADY_PROCESSED)
;; Burn sbtc-locked & re-mint sbtc to original requester
- (try! (contract-call? .sbtc-token protocol-unlock (+ requested-amount requested-max-fee) requester))
+ (try! (contract-call? .sbtc-token protocol-unlock (+ requested-amount requested-max-fee) requester withdraw-role))
;; Call into registry to confirm accepted withdrawal
(try! (contract-call? .sbtc-registry complete-withdrawal-reject request-id signer-bitmap))
diff --git a/contracts/deployments/default.simnet-plan.yaml b/contracts/deployments/default.simnet-plan.yaml
index 51eeee18c..aa831029e 100644
--- a/contracts/deployments/default.simnet-plan.yaml
+++ b/contracts/deployments/default.simnet-plan.yaml
@@ -54,6 +54,11 @@ plan:
emulated-sender: ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039
path: contracts/sbtc-deposit.clar
clarity-version: 3
+ - emulated-contract-publish:
+ contract-name: sbtc-deposit-update-test
+ emulated-sender: ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039
+ path: contracts/sbtc-deposit-update-test.clar
+ clarity-version: 3
- emulated-contract-publish:
contract-name: sbtc-token-test
emulated-sender: ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039
diff --git a/contracts/docs/sbtc-bootstrap-signers.md b/contracts/docs/sbtc-bootstrap-signers.md
index f99e48c2f..53d59b9f3 100644
--- a/contracts/docs/sbtc-bootstrap-signers.md
+++ b/contracts/docs/sbtc-bootstrap-signers.md
@@ -7,6 +7,7 @@ sBTC Bootstrap Signers contract
**Public functions:**
- [`rotate-keys-wrapper`](#rotate-keys-wrapper)
+- [`update-protocol-contract-wrapper`](#update-protocol-contract-wrapper)
**Read-only functions:**
@@ -39,7 +40,7 @@ sBTC Bootstrap Signers contract
### rotate-keys-wrapper
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L31)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L20)
`(define-public (rotate-keys-wrapper ((new-keys (list 128 (buff 33))) (new-aggregate-pubkey (buff 33)) (new-signature-threshold uint)) (response bool uint))`
@@ -58,15 +59,18 @@ the signer set is updated.
)
(let
(
- (current-signer-data (contract-call? .sbtc-registry get-current-signer-data))
(new-signer-principal (pubkeys-to-principal new-keys new-signature-threshold))
)
+
+ ;; Check that more than 1 key is in the new set
+ (asserts! (> (len new-keys) u1) ERR_KEY_SIZE)
+
;; Check that the signature threshold is valid
(asserts! (and (> new-signature-threshold (/ (len new-keys) u2))
(<= new-signature-threshold (len new-keys))) ERR_SIGNATURE_THRESHOLD)
- ;; Check that the caller is the current signer principal
- (asserts! (is-eq (get current-signer-principal current-signer-data) tx-sender) ERR_INVALID_CALLER)
+ ;; Check that the tx-sender is the current signer principal
+ (asserts! (is-eq (contract-call? .sbtc-registry get-current-signer-principal) tx-sender) ERR_INVALID_CALLER)
;; Checks that length of each key is exactly 33 bytes
(try! (fold signer-key-length-check new-keys (ok u0)))
@@ -75,7 +79,7 @@ the signer set is updated.
(asserts! (is-eq (len new-aggregate-pubkey) key-size) ERR_KEY_SIZE)
;; Call into .sbtc-registry to update the keys & address
- (ok (try! (contract-call? .sbtc-registry rotate-keys new-keys new-signer-principal new-aggregate-pubkey new-signature-threshold)))
+ (contract-call? .sbtc-registry rotate-keys new-keys new-signer-principal new-aggregate-pubkey new-signature-threshold)
)
)
```
@@ -90,9 +94,41 @@ the signer set is updated.
| new-aggregate-pubkey | (buff 33) |
| new-signature-threshold | uint |
+### update-protocol-contract-wrapper
+
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L53)
+
+`(define-public (update-protocol-contract-wrapper ((contract-type (buff 1)) (contract-address principal)) (response bool uint))`
+
+Update protocol contract
+Used to update one of the three protocol contracts
+
+
+ Source code:
+
+```clarity
+(define-public (update-protocol-contract-wrapper (contract-type (buff 1)) (contract-address principal))
+ (begin
+ ;; Check that the tx-sender is the current signer principal
+ (asserts! (is-eq (contract-call? .sbtc-registry get-current-signer-principal) tx-sender) ERR_INVALID_CALLER)
+ ;; Call into .sbtc-registry to update the protocol contract
+ (contract-call? .sbtc-registry update-protocol-contract contract-type contract-address)
+ )
+)
+```
+
+
+
+**Parameters:**
+
+| Name | Type |
+| ---------------- | --------- |
+| contract-type | (buff 1) |
+| contract-address | principal |
+
### signer-key-length-check
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L65)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L66)
`(define-private (signer-key-length-check ((current-key (buff 33)) (helper-response (response uint uint))) (response uint uint))`
@@ -127,7 +163,7 @@ Checks that the length of each key is exactly 33 bytes
### pubkeys-to-spend-script
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L80)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L81)
`(define-read-only (pubkeys-to-spend-script ((pubkeys (list 128 (buff 33))) (m uint)) (buff 4355))`
@@ -160,7 +196,7 @@ Generate the p2sh redeem script for a multisig
### pubkeys-to-hash
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L92)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L93)
`(define-read-only (pubkeys-to-hash ((pubkeys (list 128 (buff 33))) (m uint)) (buff 20))`
@@ -189,7 +225,7 @@ hash160 of the p2sh redeem script
### pubkeys-to-principal
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L100)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L101)
`(define-read-only (pubkeys-to-principal ((pubkeys (list 128 (buff 33))) (m uint)) principal)`
@@ -221,7 +257,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#L111)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L112)
`(define-read-only (pubkeys-to-bytes ((pubkeys (list 128 (buff 33)))) (buff 4352))`
@@ -246,7 +282,7 @@ Concat a list of pubkeys into a buffer with length prefixes
### concat-pubkeys-fold
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L118)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L119)
`(define-read-only (concat-pubkeys-fold ((pubkey (buff 33)) (iterator (buff 4352))) (buff 4352))`
@@ -280,7 +316,7 @@ for the public keys and 128 bytes for the length prefixes.
### bytes-len
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L130)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L129)
`(define-read-only (bytes-len ((bytes (buff 33))) (buff 1))`
@@ -303,7 +339,7 @@ for the public keys and 128 bytes for the length prefixes.
### uint-to-byte
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L134)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L133)
`(define-read-only (uint-to-byte ((n uint)) (buff 1))`
@@ -332,13 +368,14 @@ for the public keys and 128 bytes for the length prefixes.
### key-size
+constants
The required length of public keys
```clarity
(define-constant key-size u33)
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L6)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L5)
### ERR_KEY_SIZE_PREFIX
@@ -349,7 +386,7 @@ if err is u210>, it's the key at index (err - 210)
(define-constant ERR_KEY_SIZE_PREFIX (unwrap-err! ERR_KEY_SIZE (err true)))
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L12)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L9)
### ERR_KEY_SIZE
@@ -357,7 +394,7 @@ if err is u210>, it's the key at index (err - 210)
(define-constant ERR_KEY_SIZE (err u200))
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L13)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L10)
### ERR_INVALID_CALLER
@@ -367,7 +404,7 @@ The function caller is not the current signer principal
(define-constant ERR_INVALID_CALLER (err u201))
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L15)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L12)
### ERR_SIGNATURE_THRESHOLD
@@ -378,7 +415,7 @@ equal to 100% of the total number of signer keys.
(define-constant ERR_SIGNATURE_THRESHOLD (err u202))
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L18)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L15)
### BUFF_TO_BYTE
@@ -403,4 +440,4 @@ equal to 100% of the total number of signer keys.
))
```
-[View in file](../contracts/sbtc-bootstrap-signers.clar#L138)
+[View in file](../contracts/sbtc-bootstrap-signers.clar#L137)
diff --git a/contracts/tests/clarigen-types.ts b/contracts/tests/clarigen-types.ts
index b05b59c95..e611ced99 100644
--- a/contracts/tests/clarigen-types.ts
+++ b/contracts/tests/clarigen-types.ts
@@ -53,6 +53,21 @@ export const contracts = {
],
Response
>,
+ updateProtocolContractWrapper: {
+ name: "update-protocol-contract-wrapper",
+ access: "public",
+ args: [
+ { name: "contract-type", type: { buffer: { length: 1 } } },
+ { name: "contract-address", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ contractType: TypedAbiArg,
+ contractAddress: TypedAbiArg,
+ ],
+ Response
+ >,
bytesLen: {
name: "bytes-len",
access: "read_only",
@@ -680,6 +695,15 @@ export const contracts = {
},
access: "constant",
} as TypedAbiVariable>,
+ depositRole: {
+ name: "deposit-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
dustLimit: {
name: "dust-limit",
type: "uint128",
@@ -717,6 +741,7 @@ export const contracts = {
isOk: false,
value: 300n,
},
+ depositRole: Uint8Array.from([1]),
dustLimit: 546n,
txidLength: 32n,
},
@@ -726,6 +751,253 @@ export const contracts = {
clarity_version: "Clarity3",
contractName: "sbtc-deposit",
},
+ sbtcDepositUpdateTest: {
+ functions: {
+ completeIndividualDepositsHelper: {
+ name: "complete-individual-deposits-helper",
+ access: "private",
+ args: [
+ {
+ name: "deposit",
+ type: {
+ tuple: [
+ { name: "amount", type: "uint128" },
+ { name: "burn-hash", type: { buffer: { length: 32 } } },
+ { name: "burn-height", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ { name: "sweep-txid", type: { buffer: { length: 32 } } },
+ { name: "txid", type: { buffer: { length: 32 } } },
+ { name: "vout-index", type: "uint128" },
+ ],
+ },
+ },
+ {
+ name: "helper-response",
+ type: { response: { ok: "uint128", error: "uint128" } },
+ },
+ ],
+ outputs: { type: { response: { ok: "uint128", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ deposit: TypedAbiArg<
+ {
+ amount: number | bigint;
+ burnHash: Uint8Array;
+ burnHeight: number | bigint;
+ recipient: string;
+ sweepTxid: Uint8Array;
+ txid: Uint8Array;
+ voutIndex: number | bigint;
+ },
+ "deposit"
+ >,
+ helperResponse: TypedAbiArg<
+ Response,
+ "helperResponse"
+ >,
+ ],
+ Response
+ >,
+ completeDepositWrapper: {
+ name: "complete-deposit-wrapper",
+ access: "public",
+ args: [
+ { name: "txid", type: { buffer: { length: 32 } } },
+ { name: "vout-index", type: "uint128" },
+ { name: "amount", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ { name: "burn-hash", type: { buffer: { length: 32 } } },
+ { name: "burn-height", type: "uint128" },
+ { name: "sweep-txid", type: { buffer: { length: 32 } } },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ txid: TypedAbiArg,
+ voutIndex: TypedAbiArg,
+ amount: TypedAbiArg,
+ recipient: TypedAbiArg,
+ burnHash: TypedAbiArg,
+ burnHeight: TypedAbiArg,
+ sweepTxid: TypedAbiArg,
+ ],
+ Response
+ >,
+ completeDepositsWrapper: {
+ name: "complete-deposits-wrapper",
+ access: "public",
+ args: [
+ {
+ name: "deposits",
+ type: {
+ list: {
+ type: {
+ tuple: [
+ { name: "amount", type: "uint128" },
+ { name: "burn-hash", type: { buffer: { length: 32 } } },
+ { name: "burn-height", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ { name: "sweep-txid", type: { buffer: { length: 32 } } },
+ { name: "txid", type: { buffer: { length: 32 } } },
+ { name: "vout-index", type: "uint128" },
+ ],
+ },
+ length: 650,
+ },
+ },
+ },
+ ],
+ outputs: { type: { response: { ok: "uint128", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ deposits: TypedAbiArg<
+ {
+ amount: number | bigint;
+ burnHash: Uint8Array;
+ burnHeight: number | bigint;
+ recipient: string;
+ sweepTxid: Uint8Array;
+ txid: Uint8Array;
+ voutIndex: number | bigint;
+ }[],
+ "deposits"
+ >,
+ ],
+ Response
+ >,
+ getBurnHeader: {
+ name: "get-burn-header",
+ access: "read_only",
+ args: [{ name: "height", type: "uint128" }],
+ outputs: { type: { optional: { buffer: { length: 32 } } } },
+ } as TypedAbiFunction<
+ [height: TypedAbiArg],
+ Uint8Array | null
+ >,
+ },
+ maps: {},
+ variables: {
+ ERR_DEPOSIT: {
+ name: "ERR_DEPOSIT",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_DEPOSIT_INDEX_PREFIX: {
+ name: "ERR_DEPOSIT_INDEX_PREFIX",
+ type: "uint128",
+ access: "constant",
+ } as TypedAbiVariable,
+ ERR_DEPOSIT_REPLAY: {
+ name: "ERR_DEPOSIT_REPLAY",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_INVALID_BURN_HASH: {
+ name: "ERR_INVALID_BURN_HASH",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_INVALID_CALLER: {
+ name: "ERR_INVALID_CALLER",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_LOWER_THAN_DUST: {
+ name: "ERR_LOWER_THAN_DUST",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_TXID_LEN: {
+ name: "ERR_TXID_LEN",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ depositRole: {
+ name: "deposit-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
+ dustLimit: {
+ name: "dust-limit",
+ type: "uint128",
+ access: "constant",
+ } as TypedAbiVariable,
+ txidLength: {
+ name: "txid-length",
+ type: "uint128",
+ access: "constant",
+ } as TypedAbiVariable,
+ },
+ constants: {
+ ERR_DEPOSIT: {
+ isOk: false,
+ value: 303n,
+ },
+ ERR_DEPOSIT_INDEX_PREFIX: 303n,
+ ERR_DEPOSIT_REPLAY: {
+ isOk: false,
+ value: 301n,
+ },
+ ERR_INVALID_BURN_HASH: {
+ isOk: false,
+ value: 305n,
+ },
+ ERR_INVALID_CALLER: {
+ isOk: false,
+ value: 304n,
+ },
+ ERR_LOWER_THAN_DUST: {
+ isOk: false,
+ value: 302n,
+ },
+ ERR_TXID_LEN: {
+ isOk: false,
+ value: 300n,
+ },
+ depositRole: Uint8Array.from([1]),
+ dustLimit: 546n,
+ txidLength: 32n,
+ },
+ non_fungible_tokens: [],
+ fungible_tokens: [],
+ epoch: "Epoch30",
+ clarity_version: "Clarity3",
+ contractName: "sbtc-deposit-update-test",
+ },
sbtcRegistry: {
functions: {
incrementLastWithdrawalRequestId: {
@@ -861,6 +1133,30 @@ export const contracts = {
],
Response
>,
+ updateProtocolContract: {
+ name: "update-protocol-contract",
+ access: "public",
+ args: [
+ { name: "contract-type", type: { buffer: { length: 1 } } },
+ { name: "new-contract", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ contractType: TypedAbiArg,
+ newContract: TypedAbiArg,
+ ],
+ Response
+ >,
+ getActiveProtocol: {
+ name: "get-active-protocol",
+ access: "read_only",
+ args: [{ name: "contract-flag", type: { buffer: { length: 1 } } }],
+ outputs: { type: { optional: "principal" } },
+ } as TypedAbiFunction<
+ [contractFlag: TypedAbiArg],
+ string | null
+ >,
getCompletedDeposit: {
name: "get-completed-deposit",
access: "read_only",
@@ -1026,20 +1322,45 @@ export const contracts = {
isProtocolCaller: {
name: "is-protocol-caller",
access: "read_only",
- args: [],
+ args: [
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
+ { name: "contract", type: "principal" },
+ ],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
- } as TypedAbiFunction<[], Response>,
+ } as TypedAbiFunction<
+ [
+ contractFlag: TypedAbiArg,
+ contract: TypedAbiArg,
+ ],
+ Response
+ >,
validateProtocolCaller: {
name: "validate-protocol-caller",
access: "read_only",
- args: [{ name: "caller", type: "principal" }],
+ args: [
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
+ { name: "contract", type: "principal" },
+ ],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
- [caller: TypedAbiArg],
+ [
+ contractFlag: TypedAbiArg,
+ contract: TypedAbiArg,
+ ],
Response
>,
},
maps: {
+ activeProtocolContracts: {
+ name: "active-protocol-contracts",
+ key: { buffer: { length: 1 } },
+ value: "principal",
+ } as TypedAbiMap,
+ activeProtocolRoles: {
+ name: "active-protocol-roles",
+ key: "principal",
+ value: { buffer: { length: 1 } },
+ } as TypedAbiMap,
aggregatePubkeys: {
name: "aggregate-pubkeys",
key: { buffer: { length: 33 } },
@@ -1109,11 +1430,6 @@ export const contracts = {
},
boolean
>,
- protocolContracts: {
- name: "protocol-contracts",
- key: "principal",
- value: "bool",
- } as TypedAbiMap,
withdrawalRequests: {
name: "withdrawal-requests",
key: "uint128",
@@ -1164,6 +1480,16 @@ export const contracts = {
},
access: "constant",
} as TypedAbiVariable>,
+ ERR_INVALID_PROTOCOL_ID: {
+ name: "ERR_INVALID_PROTOCOL_ID",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
ERR_INVALID_REQUEST_ID: {
name: "ERR_INVALID_REQUEST_ID",
type: {
@@ -1194,6 +1520,53 @@ export const contracts = {
},
access: "constant",
} as TypedAbiVariable>,
+ ERR_UNAUTHORIZED_ROLE: {
+ name: "ERR_UNAUTHORIZED_ROLE",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_UNAUTHORIZE_FLAG: {
+ name: "ERR_UNAUTHORIZE_FLAG",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ depositRole: {
+ name: "deposit-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
+ governanceRole: {
+ name: "governance-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
+ withdrawalRole: {
+ name: "withdrawal-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
currentAggregatePubkey: {
name: "current-aggregate-pubkey",
type: {
@@ -1238,6 +1611,10 @@ export const contracts = {
isOk: false,
value: 402n,
},
+ ERR_INVALID_PROTOCOL_ID: {
+ isOk: false,
+ value: 404n,
+ },
ERR_INVALID_REQUEST_ID: {
isOk: false,
value: 401n,
@@ -1250,11 +1627,22 @@ export const contracts = {
isOk: false,
value: 400n,
},
+ ERR_UNAUTHORIZED_ROLE: {
+ isOk: false,
+ value: 406n,
+ },
+ ERR_UNAUTHORIZE_FLAG: {
+ isOk: false,
+ value: 405n,
+ },
currentAggregatePubkey: Uint8Array.from([0]),
currentSignatureThreshold: 0n,
currentSignerPrincipal: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039",
currentSignerSet: [],
+ depositRole: Uint8Array.from([1]),
+ governanceRole: Uint8Array.from([0]),
lastWithdrawalRequestId: 0n,
+ withdrawalRole: Uint8Array.from([2]),
},
non_fungible_tokens: [],
fungible_tokens: [],
@@ -1339,12 +1727,14 @@ export const contracts = {
args: [
{ name: "amount", type: "uint128" },
{ name: "owner", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
[
amount: TypedAbiArg,
owner: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1354,12 +1744,14 @@ export const contracts = {
args: [
{ name: "amount", type: "uint128" },
{ name: "owner", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
[
amount: TypedAbiArg,
owner: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1369,12 +1761,14 @@ export const contracts = {
args: [
{ name: "amount", type: "uint128" },
{ name: "owner", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
[
amount: TypedAbiArg,
owner: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1384,12 +1778,14 @@ export const contracts = {
args: [
{ name: "amount", type: "uint128" },
{ name: "recipient", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
[
amount: TypedAbiArg,
recipient: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1411,6 +1807,7 @@ export const contracts = {
},
},
},
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: {
type: {
@@ -1434,16 +1831,23 @@ export const contracts = {
}[],
"recipients"
>,
+ contractFlag: TypedAbiArg,
],
Response[], bigint>
>,
protocolSetName: {
name: "protocol-set-name",
access: "public",
- args: [{ name: "new-name", type: { "string-ascii": { length: 32 } } }],
+ args: [
+ { name: "new-name", type: { "string-ascii": { length: 32 } } },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
+ ],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
- [newName: TypedAbiArg],
+ [
+ newName: TypedAbiArg,
+ contractFlag: TypedAbiArg,
+ ],
Response
>,
protocolSetSymbol: {
@@ -1451,10 +1855,14 @@ export const contracts = {
access: "public",
args: [
{ name: "new-symbol", type: { "string-ascii": { length: 10 } } },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
- [newSymbol: TypedAbiArg],
+ [
+ newSymbol: TypedAbiArg,
+ contractFlag: TypedAbiArg,
+ ],
Response
>,
protocolSetTokenUri: {
@@ -1465,10 +1873,14 @@ export const contracts = {
name: "new-uri",
type: { optional: { "string-utf8": { length: 256 } } },
},
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
- [newUri: TypedAbiArg],
+ [
+ newUri: TypedAbiArg,
+ contractFlag: TypedAbiArg,
+ ],
Response
>,
protocolTransfer: {
@@ -1478,6 +1890,7 @@ export const contracts = {
{ name: "amount", type: "uint128" },
{ name: "sender", type: "principal" },
{ name: "recipient", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
@@ -1485,6 +1898,7 @@ export const contracts = {
amount: TypedAbiArg,
sender: TypedAbiArg,
recipient: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1494,12 +1908,14 @@ export const contracts = {
args: [
{ name: "amount", type: "uint128" },
{ name: "owner", type: "principal" },
+ { name: "contract-flag", type: { buffer: { length: 1 } } },
],
outputs: { type: { response: { ok: "bool", error: "uint128" } } },
} as TypedAbiFunction<
[
amount: TypedAbiArg,
owner: TypedAbiArg,
+ contractFlag: TypedAbiArg,
],
Response
>,
@@ -1695,7 +2111,8 @@ export const contracts = {
tokenDecimals: 8n,
tokenName: "sBTC",
tokenSymbol: "sBTC",
- tokenUri: null,
+ tokenUri:
+ "https://ipfs.io/ipfs/bafkreibqnozdui4ntgoh3oo437lvhg7qrsccmbzhgumwwjf2smb3eegyqu",
},
non_fungible_tokens: [],
fungible_tokens: [{ name: "sbtc-token" }, { name: "sbtc-token-locked" }],
@@ -2065,6 +2482,15 @@ export const contracts = {
type: "uint128",
access: "constant",
} as TypedAbiVariable,
+ withdrawRole: {
+ name: "withdraw-role",
+ type: {
+ buffer: {
+ length: 1,
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable,
},
constants: {
DUST_LIMIT: 546n,
@@ -2108,6 +2534,7 @@ export const contracts = {
MAX_ADDRESS_VERSION: 6n,
mAX_ADDRESS_VERSION_BUFF_20: 4n,
mAX_ADDRESS_VERSION_BUFF_32: 6n,
+ withdrawRole: Uint8Array.from([2]),
},
non_fungible_tokens: [],
fungible_tokens: [],
@@ -2144,6 +2571,8 @@ export const identifiers = {
sbtcBootstrapSigners:
"ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-bootstrap-signers",
sbtcDeposit: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-deposit",
+ sbtcDepositUpdateTest:
+ "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-deposit-update-test",
sbtcRegistry: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-registry",
sbtcToken: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-token",
sbtcTokenTest: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-token-test",
@@ -2169,6 +2598,14 @@ export const deployments = {
testnet: null,
mainnet: null,
},
+ sbtcDepositUpdateTest: {
+ devnet:
+ "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-deposit-update-test",
+ simnet:
+ "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-deposit-update-test",
+ testnet: null,
+ mainnet: null,
+ },
sbtcRegistry: {
devnet: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-registry",
simnet: "ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039.sbtc-registry",
diff --git a/contracts/tests/helpers.ts b/contracts/tests/helpers.ts
index 843d0a5b1..9f32a0e21 100644
--- a/contracts/tests/helpers.ts
+++ b/contracts/tests/helpers.ts
@@ -28,6 +28,7 @@ export const signers = contracts.sbtcBootstrapSigners;
export const withdrawal = contracts.sbtcWithdrawal;
export const token = contracts.sbtcToken;
export const tokenTest = contracts.sbtcTokenTest;
+export const depositUpdate = contracts.sbtcDepositUpdateTest;
export const controllerId = `${accounts.deployer.address}.controller`;
diff --git a/contracts/tests/sbtc-bootstrap-signers.test.ts b/contracts/tests/sbtc-bootstrap-signers.test.ts
index 3878e7b88..e1191f3cb 100644
--- a/contracts/tests/sbtc-bootstrap-signers.test.ts
+++ b/contracts/tests/sbtc-bootstrap-signers.test.ts
@@ -6,6 +6,10 @@ import {
randomPublicKeys,
registry,
signers,
+ depositUpdate,
+ getCurrentBurnInfo,
+ deposit,
+ alice,
} from "./helpers";
import { test, expect, describe } from "vitest";
import { txOk, txErr, rov, filterEvents } from "@clarigen/test";
@@ -221,4 +225,99 @@ describe("sBTC bootstrap signers contract", () => {
});
});
});
+
+ describe("Update deposit contract", () => {
+ test("Can update deposit contract & call into new version", () => {
+ // Switch out active deposit contract
+ const receipt1 = txOk(
+ signers.updateProtocolContractWrapper({
+ contractType: new Uint8Array([1]),
+ contractAddress: depositUpdate.identifier,
+ }),
+ deployer
+ );
+ expect(receipt1.value).toEqual(true);
+ // Call into the new contract
+ const defaultAmount = 1000n;
+ const defaultMaxFee = 10n;
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
+ const receipt2 = txOk(
+ depositUpdate.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: defaultAmount + defaultMaxFee,
+ recipient: alice,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
+ }),
+ deployer
+ );
+ expect(receipt2.value).toEqual(true);
+ });
+ test("Can not call into previous deposit contract", () => {
+ // Switch out active deposit contract
+ const receipt1 = txOk(
+ signers.updateProtocolContractWrapper({
+ contractType: new Uint8Array(1).fill(1),
+ contractAddress: depositUpdate.identifier,
+ }),
+ deployer
+ );
+ expect(receipt1.value).toEqual(true);
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
+
+ const receipt = txErr(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: 1000n,
+ recipient: deployer,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
+ }),
+ deployer
+ );
+ expect(receipt.value).toEqual(errors.registry.ERR_UNAUTHORIZED);
+ });
+ test("Can update deposit contract a second time", () => {
+ // Switch out active deposit contract
+ const receipt1 = txOk(
+ signers.updateProtocolContractWrapper({
+ contractType: new Uint8Array(1).fill(1),
+ contractAddress: depositUpdate.identifier,
+ }),
+ deployer
+ );
+ expect(receipt1.value).toEqual(true);
+ // Call into the new contract
+ const defaultAmount = 1000n;
+ const defaultMaxFee = 10n;
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
+ const receipt2 = txOk(
+ depositUpdate.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: defaultAmount + defaultMaxFee,
+ recipient: alice,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
+ }),
+ deployer
+ );
+ expect(receipt2.value).toEqual(true);
+ // Switch out active deposit contract
+ const receipt3 = txOk(
+ signers.updateProtocolContractWrapper({
+ contractType: new Uint8Array(1).fill(1),
+ contractAddress: deposit.identifier,
+ }),
+ deployer
+ );
+ expect(receipt3.value).toEqual(true);
+ });
+ });
+
});
diff --git a/contracts/tests/sbtc-registry.test.ts b/contracts/tests/sbtc-registry.test.ts
index df2482eb3..646e256ac 100644
--- a/contracts/tests/sbtc-registry.test.ts
+++ b/contracts/tests/sbtc-registry.test.ts
@@ -8,9 +8,13 @@ import {
registry,
signers,
stxAddressToPoxAddress,
+ deposit,
+ getCurrentBurnInfo,
+ token,
+ withdrawal
} from "./helpers";
import { test, expect, describe } from "vitest";
-import { txOk, filterEvents, rov, txErr } from "@clarigen/test";
+import { txOk, filterEvents, rov, txErr, rovOk } from "@clarigen/test";
import { CoreNodeEventType, cvToValue } from "@clarigen/core";
const alicePoxAddr = stxAddressToPoxAddress(alice);
@@ -25,45 +29,70 @@ describe("sBTC registry contract", () => {
test("Storing a new withdrawal request", () => {
const recipient = alicePoxAddr;
- const receipt = txOk(
- registry.createWithdrawalRequest({
- amount: 100n,
- recipient,
- maxFee: 10n,
- sender: alice,
- height: simnet.blockHeight,
+ const defaultAmount = 1000n;
+ const defaultMaxFee = 10n;
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
+ txOk(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: defaultAmount + defaultMaxFee,
+ recipient: alice,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
}),
deployer
);
- expect(receipt.value).toEqual(1n);
-
+ expect(rovOk(token.getBalance(alice))).toEqual(
+ defaultAmount + defaultMaxFee
+ );
+ txOk(
+ withdrawal.initiateWithdrawalRequest({
+ amount: defaultAmount,
+ recipient: alicePoxAddr,
+ maxFee: defaultMaxFee,
+ }),
+ alice
+ );
const request = getWithdrawalRequest(1n);
if (!request) {
throw new Error("Request not stored");
}
expect(request.sender).toEqual(alice);
- expect(request.amount).toEqual(100n);
+ expect(request.amount).toEqual(defaultAmount);
expect(request.maxFee).toEqual(10n);
expect(request.recipient).toEqual(recipient);
- expect(request.blockHeight).toEqual(BigInt(simnet.blockHeight - 1));
-
- // ensure last-id is updated
+ expect(request.blockHeight).toEqual(BigInt(simnet.burnBlockHeight));
const lastId = getLastWithdrawalRequestId();
expect(lastId).toEqual(1n);
});
test("emitted events when a new withdrawal is stored", () => {
const recipient = bobPoxAddr;
- const receipt = txOk(
- registry.createWithdrawalRequest({
- recipient,
- amount: 100n,
- maxFee: 10n,
- sender: bob,
- height: simnet.blockHeight,
+ const defaultAmount = 1000n;
+ const defaultMaxFee = 10n;
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
+ txOk(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: defaultAmount + defaultMaxFee,
+ recipient: bob,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
}),
deployer
);
+ const receipt = txOk(
+ withdrawal.initiateWithdrawalRequest({
+ amount: defaultAmount,
+ recipient: recipient,
+ maxFee: defaultMaxFee,
+ }),
+ bob
+ );
const prints = filterEvents(
receipt.events,
CoreNodeEventType.ContractEvent
@@ -86,7 +115,7 @@ describe("sBTC registry contract", () => {
expect(printData).toStrictEqual({
sender: bob,
recipient: recipient,
- amount: 100n,
+ amount: defaultAmount,
maxFee: 10n,
blockHeight: request.blockHeight,
topic: "withdrawal-create",
@@ -95,28 +124,44 @@ describe("sBTC registry contract", () => {
});
test("get-withdrawal-request includes status", () => {
+
+ const defaultAmount = 1000n;
+ const defaultMaxFee = 10n;
+ const { burnHeight, burnHash } = getCurrentBurnInfo();
txOk(
- registry.createWithdrawalRequest({
- sender: alice,
- recipient: alicePoxAddr,
- amount: 100n,
- maxFee: 10n,
- height: simnet.blockHeight,
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: defaultAmount + defaultMaxFee,
+ recipient: alice,
+ burnHash,
+ burnHeight,
+ sweepTxid: new Uint8Array(32).fill(1),
}),
deployer
);
-
+ expect(rovOk(token.getBalance(alice))).toEqual(
+ defaultAmount + defaultMaxFee
+ );
+ txOk(
+ withdrawal.initiateWithdrawalRequest({
+ amount: defaultAmount,
+ recipient: alicePoxAddr,
+ maxFee: defaultMaxFee,
+ }),
+ alice
+ );
const request = rov(registry.getWithdrawalRequest(1n));
if (!request) {
throw new Error("Request not found");
}
expect(request.status).toEqual(null);
- expect(request).toStrictEqual({
+ expect(request).toEqual({
sender: alice,
recipient: alicePoxAddr,
- amount: 100n,
+ amount: defaultAmount,
maxFee: 10n,
- blockHeight: BigInt(simnet.blockHeight - 1),
+ blockHeight: BigInt(2n),
status: null,
});
});
diff --git a/contracts/tests/sbtc-token.test.ts b/contracts/tests/sbtc-token.test.ts
index d80e8dd82..b95874e57 100644
--- a/contracts/tests/sbtc-token.test.ts
+++ b/contracts/tests/sbtc-token.test.ts
@@ -394,6 +394,7 @@ describe("sBTC token contract", () => {
token.protocolMint({
amount: 1000n,
recipient: bob,
+ contractFlag: new Uint8Array([0])
}),
bob
);
@@ -405,6 +406,7 @@ describe("sBTC token contract", () => {
token.protocolLock({
amount: 1000n,
owner: bob,
+ contractFlag: new Uint8Array([0])
}),
bob
);
@@ -416,6 +418,7 @@ describe("sBTC token contract", () => {
token.protocolBurn({
amount: 1000n,
owner: bob,
+ contractFlag: new Uint8Array([0])
}),
bob
);
@@ -427,6 +430,7 @@ describe("sBTC token contract", () => {
token.protocolBurnLocked({
amount: 1000n,
owner: bob,
+ contractFlag: new Uint8Array([0])
}),
bob
);
@@ -463,6 +467,7 @@ describe("sBTC token contract", () => {
token.protocolMint({
amount: 1000n,
recipient: bob,
+ contractFlag: new Uint8Array([3])
}),
deployer
);
@@ -476,6 +481,7 @@ describe("sBTC token contract", () => {
token.protocolLock({
amount: 222n,
owner: bob,
+ contractFlag: new Uint8Array([3])
}),
deployer
);
@@ -490,6 +496,7 @@ describe("sBTC token contract", () => {
amount: 900n,
sender: bob,
recipient: charlie,
+ contractFlag: new Uint8Array([3])
}),
deployer
);
@@ -501,6 +508,7 @@ describe("sBTC token contract", () => {
amount: 500n,
sender: bob,
recipient: charlie,
+ contractFlag: new Uint8Array([3])
}),
deployer
);
@@ -512,6 +520,7 @@ describe("sBTC token contract", () => {
token.protocolUnlock({
amount: 500n,
owner: bob,
+ contractFlag: new Uint8Array([3])
}),
deployer
);
@@ -522,6 +531,7 @@ describe("sBTC token contract", () => {
token.protocolUnlock({
amount: 222n,
owner: bob,
+ contractFlag: new Uint8Array([3])
}),
deployer
);