Source Code

;; 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))

;; 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)
;; 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)
;; Internal data structure to store withdrawal
;; requests. Requests are associated with a unique
;; request ID.
(define-map withdrawal-requests uint {
	;; Amount of sBTC being withdrawaled (in sats)
	amount: uint,
	max-fee: uint,
	sender: principal,
	;; BTC recipient address in the same format of
	;; pox contracts
	recipient: {
		version: (buff 1),
		hashbytes: (buff 32),
	},
	;; Burn block height where the withdrawal request was
	;; created
	block-height: uint,
})

;; Data structure to map request-id to status
;; If status is `none`, the request is pending.
;; Otherwise, the boolean value indicates whether
;; the withdrawal was accepted.
(define-map withdrawal-status uint bool)

;; Data structure to map successful withdrawal requests
;; to their respective sweep transaction. Stores the
;; txid, burn hash, and burn height.
(define-map completed-withdrawal-sweep uint {
	sweep-txid: (buff 32),
	sweep-burn-hash: (buff 32),
	sweep-burn-height: uint,
})

;; Internal data structure to store completed
;; deposit requests & avoid replay attacks.
(define-map deposit-status {txid: (buff 32), vout-index: uint} bool)

;; Data structure to map successful deposit requests
;; to their respective sweep transaction. Stores the
;; txid, burn hash, and burn height.
(define-map completed-deposits {txid: (buff 32), vout-index: uint}
	{
		amount: uint,
		recipient: principal,
		sweep-txid: (buff 32),
		sweep-burn-hash: (buff 32),
		sweep-burn-height: uint,
	}
)

;; Data structure to store aggregate pubkey,
;; stored to avoid replay
(define-map aggregate-pubkeys (buff 33) bool)

;; Read-only functions
;; Get a withdrawal request by its ID.
;; This function returns the fields of the withdrawal
;; request, along with its status.
(define-read-only (get-withdrawal-request (id uint))
	(match (map-get? withdrawal-requests id)
		request (some (merge request {
			status: (map-get? withdrawal-status id)
		}))
		none
	)
)

;; Get a completed withdrawal sweep data by its request ID.
;; This function returns the fields of the withdrawal-sweeps map.
(define-read-only (get-completed-withdrawal-sweep-data (id uint))
	(map-get? completed-withdrawal-sweep id)
)

;; Get a completed deposit by its transaction ID & vout index.
;; This function returns the fields of the completed-deposits map.
(define-read-only (get-completed-deposit (txid (buff 32)) (vout-index uint))
	(map-get? completed-deposits {txid: txid, vout-index: vout-index})
)

;; Get a completed deposit sweep data by its transaction ID & vout index.
;; This function returns the fields of the completed-deposits map.
(define-read-only (get-deposit-status (txid (buff 32)) (vout-index uint))
	(map-get? deposit-status {txid: txid, vout-index: vout-index})
)

;; Get the current signer set.
;; This function returns the current signer set as a list of principals.
(define-read-only (get-current-signer-data)
	{
		current-signer-set: (var-get current-signer-set),
		current-aggregate-pubkey: (var-get current-aggregate-pubkey),
		current-signer-principal: (var-get current-signer-principal),
		current-signature-threshold: (var-get current-signature-threshold),
	}
)

;; Get the current aggregate pubkey.
;; This function returns the current aggregate pubkey.
(define-read-only (get-current-aggregate-pubkey)
	(var-get current-aggregate-pubkey)
)

;; Get the current signer principal.
;; This function returns the current signer principal.
(define-read-only (get-current-signer-principal)
	(var-get current-signer-principal)
)

(define-read-only (get-current-signer-set)
	(var-get current-signer-set)
)

(define-read-only (get-active-protocol (contract-flag (buff 1)))
	(map-get? active-protocol-contracts contract-flag)
)


;; Public functions

;; Store a new withdrawal request.
;; Note that this function can only be called by other sBTC
;; contracts - it cannot be called by users directly.
;;
;; This function does not handle validation or moving the funds.
;; Instead, it is purely for the purpose of storing the request.
;;
;; The function will emit a print event with the topic "withdrawal-create"
;; and the data of the request.
(define-public (create-withdrawal-request
		(amount uint)
		(max-fee uint)
		(sender principal)
		(recipient { version: (buff 1), hashbytes: (buff 32) })
		(height uint)
	)
	(let
		(
			(id (increment-last-withdrawal-request-id))
		)
		(try! (is-protocol-caller withdrawal-role contract-caller))
		;; #[allow(unchecked_data)]
		(map-insert withdrawal-requests id {
			amount: amount,
			max-fee: max-fee,
			sender: sender,
			recipient: recipient,
			block-height: height,
		})
		(print {
			topic: "withdrawal-create",
			amount: amount,
			request-id: id,
			sender: sender,
			recipient: recipient,
			block-height: height,
			max-fee: max-fee,
		})
		(ok id)
	)
)

;; Complete withdrawal request by noting the acceptance in the
;; withdrawal-status state map.
;;
;; This function will emit a print event with the topic
;; "withdrawal-accept".
(define-public (complete-withdrawal-accept
		(request-id uint)
		(bitcoin-txid (buff 32))
		(output-index uint)
		(signer-bitmap uint)
		(fee uint)
		(burn-hash (buff 32))
		(burn-height uint)
		(sweep-txid (buff 32))
	)
	(begin
		(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 {
			sweep-txid: sweep-txid,
			sweep-burn-hash: burn-hash,
			sweep-burn-height: burn-height,
		})
		(print {
			topic: "withdrawal-accept",
			request-id: request-id,
			bitcoin-txid: bitcoin-txid,
			signer-bitmap: signer-bitmap,
			output-index: output-index,
			fee: fee,
			burn-hash: burn-hash,
			burn-height: burn-height,
			sweep-txid: sweep-txid,
		})
		(ok true)
	)
)

;; Complete withdrawal request by noting the rejection in the
;; withdrawal-status state map.
;;
;; This function will emit a print event with the topic
;; "withdrawal-reject".
(define-public (complete-withdrawal-reject
		(request-id uint)
		(signer-bitmap uint)
	)
	(begin
		(try! (is-protocol-caller withdrawal-role contract-caller))
		;; Mark the withdrawal as completed
		(map-insert withdrawal-status request-id false)
		(print {
			topic: "withdrawal-reject",
			request-id: request-id,
			signer-bitmap: signer-bitmap,
		})
		(ok true)
	)
)

;; Store a new insert request.
;; Note that this function can only be called by other sBTC
;; contracts (specifically the current version of the deposit contract)
;; - it cannot be called by users directly.
;;
;; This function does not handle validation or moving the funds.
;; Instead, it is purely for the purpose of storing the completed deposit.
(define-public (complete-deposit
		(txid (buff 32))
		(vout-index uint)
		(amount uint)
		(recipient principal)
		(burn-hash (buff 32))
		(burn-height uint)
		(sweep-txid (buff 32))
	)
	(begin
		(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,
			recipient: recipient,
			sweep-txid: sweep-txid,
			sweep-burn-hash: burn-hash,
			sweep-burn-height: burn-height,
		})
		(print {
			topic: "completed-deposit",
			bitcoin-txid: txid,
			output-index: vout-index,
			amount: amount,
			burn-hash: burn-hash,
			burn-height: burn-height,
			sweep-txid: sweep-txid,
		})
		(ok true)
	)
)

;; Rotate the signer set, multi-sig principal, & aggregate pubkey
;; This function can only be called by the bootstrap-signers contract.
(define-public (rotate-keys
		(new-keys (list 128 (buff 33)))
		(new-address principal)
		(new-aggregate-pubkey (buff 33))
		(new-signature-threshold uint)
	)
	(begin
		;; Check that caller is protocol contract
		(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
		(var-set current-signer-set new-keys)
		;; Update the current multi-sig address
		(var-set current-signer-principal new-address)
		;; Update the current signature threshold
		(var-set current-signature-threshold new-signature-threshold)
		;; Update the current aggregate pubkey
		(var-set current-aggregate-pubkey new-aggregate-pubkey)
		(print {
			topic: "key-rotation",
			new-keys: new-keys,
			new-address: new-address,
			new-aggregate-pubkey: new-aggregate-pubkey,
			new-signature-threshold: new-signature-threshold
		})
		(ok true)
	)
)

;; 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)
	(let
		(
			(next-value (+ u1 (var-get last-withdrawal-request-id)))
		)
		(var-set last-withdrawal-request-id next-value)
		next-value
	)
)

;; Checks whether the contract-caller is a protocol contract
(define-read-only (is-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)
	)
)

Functions (15)

FunctionAccessArgs
get-withdrawal-requestread-onlyid: uint
get-completed-withdrawal-sweep-dataread-onlyid: uint
get-completed-depositread-onlytxid: (buff 32
get-deposit-statusread-onlytxid: (buff 32
get-current-signer-dataread-only
get-current-aggregate-pubkeyread-only
get-current-signer-principalread-only
get-current-signer-setread-only
get-active-protocolread-onlycontract-flag: (buff 1
complete-withdrawal-acceptpublicrequest-id: uint, bitcoin-txid: (buff 32
complete-depositpublictxid: (buff 32
rotate-keyspublicnew-keys: (list 128 (buff 33
update-protocol-contractpubliccontract-type: (buff 1
increment-last-withdrawal-request-idprivate
is-protocol-callerread-onlycontract-flag: (buff 1