From f4fc46fe328bd165b7e32ac5f9591add2c2c511e Mon Sep 17 00:00:00 2001 From: fiftyeightandeight Date: Tue, 23 Jan 2024 16:35:44 +0800 Subject: [PATCH] initial commit --- LICENSE | 98 ++++ contracts/brc20-bridge-endpoint-v1-07.clar | 318 +++++++++++ contracts/brc20-bridge-registry-v1-02.clar | 129 +++++ contracts/btc-bridge-endpoint-v1-10.clar | 619 +++++++++++++++++++++ contracts/btc-bridge-registry-v1-03.clar | 87 +++ 5 files changed, 1251 insertions(+) create mode 100644 LICENSE create mode 100644 contracts/brc20-bridge-endpoint-v1-07.clar create mode 100644 contracts/brc20-bridge-registry-v1-02.clar create mode 100644 contracts/btc-bridge-endpoint-v1-10.clar create mode 100644 contracts/btc-bridge-registry-v1-03.clar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..afc47ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,98 @@ +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: ALEXGO Pte. Ltd. + +Licensed Work: bitcoin-bridge + The Licensed Work is (c) 2024 ALEXGO Pte. Ltd. + +Additional Use Grant: None + +Change Date: 2028-01-01 + +Change License: MIT License + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark "Business Source License", +as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where "compatible" means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text "None". + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. diff --git a/contracts/brc20-bridge-endpoint-v1-07.clar b/contracts/brc20-bridge-endpoint-v1-07.clar new file mode 100644 index 0000000..dc89274 --- /dev/null +++ b/contracts/brc20-bridge-endpoint-v1-07.clar @@ -0,0 +1,318 @@ +(use-trait sip010-trait .trait-sip-010.sip-010-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-paused (err u1001)) +(define-constant err-peg-in-address-not-found (err u1002)) +(define-constant err-invalid-amount (err u1003)) +(define-constant err-token-mismatch (err u1004)) +(define-constant err-invalid-tx (err u1005)) +(define-constant err-already-sent (err u1006)) +(define-constant err-address-mismatch (err u1007)) +(define-constant err-request-already-revoked (err u1008)) +(define-constant err-request-already-finalized (err u1009)) +(define-constant err-revoke-grace-period (err u1010)) +(define-constant err-request-already-claimed (err u1011)) +(define-constant err-invalid-input (err u1012)) +(define-constant err-tx-mined-before-request (err u1013)) + +(define-constant MAX_UINT u340282366920938463463374607431768211455) +(define-constant ONE_8 u100000000) + +(define-data-var contract-owner principal tx-sender) +(define-data-var fee-address principal tx-sender) + +;; governance functions + +(define-public (set-contract-owner (new-contract-owner principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set contract-owner new-contract-owner)))) + +(define-public (set-fee-address (new-fee-address principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set fee-address new-fee-address)))) + +;; read-only functions + +(define-read-only (get-request-revoke-grace-period) + (contract-call? .brc20-bridge-registry-v1-02 get-request-revoke-grace-period)) + +(define-read-only (get-request-claim-grace-period) + (contract-call? .brc20-bridge-registry-v1-02 get-request-claim-grace-period)) + +(define-read-only (is-peg-in-address-approved (address (buff 128))) + (contract-call? .brc20-bridge-registry-v1-02 is-peg-in-address-approved address)) + +(define-read-only (get-token-to-tick-or-fail (token principal)) + (contract-call? .brc20-bridge-registry-v1-02 get-token-to-tick-or-fail token)) + +(define-read-only (get-token-details-or-fail (tick (string-utf8 4))) + (contract-call? .brc20-bridge-registry-v1-02 get-token-details-or-fail tick)) + +(define-read-only (get-token-details-or-fail-by-address (token principal)) + (contract-call? .brc20-bridge-registry-v1-02 get-token-details-or-fail-by-address token)) + +(define-read-only (is-approved-token (tick (string-utf8 4))) + (contract-call? .brc20-bridge-registry-v1-02 is-approved-token tick)) + +(define-read-only (get-request-or-fail (request-id uint)) + (contract-call? .brc20-bridge-registry-v1-02 get-request-or-fail request-id)) + +(define-read-only (create-order-or-fail (order { user: principal, dest: uint })) + (ok (unwrap! (to-consensus-buff? order) err-invalid-input))) + +(define-read-only (decode-order-or-fail (order-script (buff 128))) + (ok (unwrap! (from-consensus-buff? { user: principal, dest: uint } (unwrap-panic (slice? order-script u2 (len order-script)))) err-invalid-input))) + +(define-read-only (get-peg-in-sent-or-default (bitcoin-tx (buff 8192)) (output uint) (offset uint)) + (contract-call? .brc20-bridge-registry-v1-02 get-peg-in-sent-or-default bitcoin-tx output offset)) + +(define-read-only (get-fee-address) + (var-get fee-address)) + +(define-read-only (extract-tx-ins-outs (tx (buff 8192))) + (if (try! (contract-call? .clarity-bitcoin-v1-05 is-segwit-tx tx)) + (let + ( + (parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-05 parse-wtx tx) err-invalid-tx)) + ) + (ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) }) + ) + (let + ( + (parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-05 parse-tx tx) err-invalid-tx)) + ) + (ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) }) + ) + ) +) + +;; validate-tx +;; +;; given the inputs, +;; (1) confirm the tx is indexed (by Bitcoin Oracle), +;; (2) the tx has not yet been processed, +;; (3) the peg-in address in the tx is one of the approved / correct addresses to receive BRC20 tokens +(define-read-only (validate-tx (tx (buff 8192)) (output-idx uint) (offset-idx uint) (order-idx uint) (token principal)) + (let ( + (tx-idxed (try! (contract-call? .oracle-v1-06 get-bitcoin-tx-indexed-or-fail tx output-idx offset-idx))) + (parsed-tx (try! (extract-tx-ins-outs tx))) + (order-script (get scriptPubKey (unwrap-panic (element-at? (get outs parsed-tx) order-idx)))) + (order-details (try! (decode-order-or-fail order-script))) + (token-details (try! (get-token-details-or-fail (get tick tx-idxed)))) + (amt-in-fixed (decimals-to-fixed (get amt tx-idxed) (contract-call? .oracle-v1-06 get-tick-decimals-or-default (get tick tx-idxed)))) + (fee (mul-down amt-in-fixed (get peg-in-fee token-details))) + (amt-net (- amt-in-fixed fee))) + (asserts! (is-eq token (get token token-details)) err-token-mismatch) + (asserts! (not (get-peg-in-sent-or-default tx output-idx offset-idx)) err-already-sent) + (asserts! (is-peg-in-address-approved (get to tx-idxed)) err-peg-in-address-not-found) + + (ok { order-details: order-details, fee: fee, amt-net: amt-net, tx-idxed: tx-idxed, token-details: token-details }) + ) +) + +;; public functions + +;; pegs in `tick` of `amt` (net of fee) from Bitcoin to Stacks. +;; the relevant tx must (1) have been indexed and (2) include OP_RETURN with Stacks address (see `create-order-or-fail`) +;; +;; tx => bitcoin tx hash of peg-in transfer +;; order-idx => index of OP_RETURN +;; token-trait => trait of pegged-in token + +(define-public (finalize-peg-in-on-index + (tx { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 4), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint }) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (signature-packs (list 10 { signer: principal, tx-hash: (buff 32), signature: (buff 65) })) + (order-idx uint) (token-trait )) + (begin + (try! (index-tx tx block proof signature-packs)) + (finalize-peg-in (get bitcoin-tx tx) (get output tx) (get offset tx) order-idx token-trait))) + +(define-public (finalize-peg-in (tx (buff 8192)) (output-idx uint) (offset-idx uint) (order-idx uint) (token-trait )) + (let ( + (token (contract-of token-trait)) + (validation-data (try! (validate-tx tx output-idx offset-idx order-idx token))) + (tx-idxed (get tx-idxed validation-data)) + (order-details (get order-details validation-data)) + (order-address (get user order-details)) + (dest (get dest order-details)) + (token-details (get token-details validation-data)) + (fee (get fee validation-data)) + (amt-net (get amt-net validation-data))) + (asserts! (not (get peg-in-paused token-details)) err-paused) + + (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-peg-in-sent { tx: tx, output: output-idx, offset: offset-idx } true))) + (and (> fee u0) (as-contract (try! (contract-call? token-trait mint-fixed fee (var-get fee-address))))) + + ;; map cannot hold traits, so the below have to be hard-coded. + ;; mint to order-address if either dest == 0 or order-address is not registered with b20, or token is not registered with b20 + (if (or (is-eq dest u0) (is-err (contract-call? .stxdx-registry get-user-id-or-fail order-address)) (is-none (contract-call? .stxdx-registry get-asset-id token))) + (begin + (and (> amt-net u0) (as-contract (try! (contract-call? token-trait mint-fixed amt-net order-address)))) + (print (merge tx-idxed { type: "peg-in", order-address: order-address, fee: fee, amt-net: amt-net, dest: u0, bitcoin-tx: tx, output-idx: output-idx, offset-idx: offset-idx, order-idx: order-idx })) + ) + (begin + (and (> amt-net u0) (as-contract (try! (contract-call? token-trait mint-fixed amt-net tx-sender)))) + (and (> amt-net u0) (as-contract (try! (contract-call? .stxdx-wallet-zero transfer-in + amt-net + (try! (contract-call? .stxdx-registry get-user-id-or-fail order-address)) ;; user-id + (unwrap-panic (contract-call? .stxdx-registry get-asset-id token)) ;; asset-id + token-trait)))) + (print (merge tx-idxed { type: "peg-in", order-address: order-address, fee: fee, amt-net: amt-net, dest: u1, bitcoin-tx: tx, output-idx: output-idx, offset-idx: offset-idx, order-idx: order-idx })) + ) + ) + (ok true))) + +;; request peg-out of `tick` of `amount` (net of fee) to `peg-out-address` +;; request escrows the relevant pegged-in token and gas-fee token to the contract until the request is either finalized or revoked. +;; +;; token-trait => the trait of pegged-in token +(define-public (request-peg-out (tick (string-utf8 4)) (amount uint) (peg-out-address (buff 128)) (token-trait )) + (let ( + (token (contract-of token-trait)) + (token-details (try! (get-token-details-or-fail tick))) + (fee (mul-down amount (get peg-out-fee token-details))) + (amount-net (- amount fee)) + (gas-fee (get peg-out-gas-fee token-details)) + (request-details { requested-by: tx-sender, peg-out-address: peg-out-address, tick: tick, amount-net: amount-net, fee: fee, gas-fee: gas-fee, claimed: u0, claimed-by: tx-sender, fulfilled-by: 0x, revoked: false, finalized: false, requested-at: block-height, requested-at-burn-height: burn-block-height }) + (request-id (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-request u0 request-details))))) + (asserts! (not (get peg-out-paused token-details)) err-paused) + (asserts! (is-eq token (get token token-details)) err-token-mismatch) + (asserts! (> amount u0) err-invalid-amount) + + (try! (contract-call? token-trait transfer-fixed amount tx-sender (as-contract tx-sender) none)) + (and (> gas-fee u0) (try! (contract-call? .token-susdt transfer-fixed gas-fee tx-sender (as-contract tx-sender) none))) + + (print (merge request-details { type: "request-peg-out", request-id: request-id })) + (ok true))) + +;; claim peg-out request, so that the claimer can safely process the peg-out (within the grace period) +;; +(define-public (claim-peg-out (request-id uint) (fulfilled-by (buff 128))) + (let ( + (claimer tx-sender) + (request-details (try! (get-request-or-fail request-id))) + (token-details (try! (get-token-details-or-fail (get tick request-details))))) + (asserts! (not (get peg-out-paused token-details)) err-paused) + (asserts! (< (get claimed request-details) block-height) err-request-already-claimed) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-request request-id (merge request-details { claimed: (+ block-height (get-request-claim-grace-period)), claimed-by: claimer, fulfilled-by: fulfilled-by })))) + + (print (merge request-details { type: "claim-peg-out", request-id: request-id, claimed: (+ block-height (get-request-claim-grace-period)), claimed-by: claimer, fulfilled-by: fulfilled-by })) + (ok true) + ) +) + +;; finalize peg-out request +;; finalize `request-id` with `tx` +;; pays the fee to `fee-address` and burn the relevant pegged-in tokens. +;; +;; peg-out finalization can be done by either a peg-in address or a non-peg-in (i.e. 3rd party) address +;; if the latter, then the overall peg-in balance does not change. +;; the claimer sends non-pegged-in BRC20 tokens to the peg-out requester and receives the pegged-in BRC20 tokens (along with gas-fee) +;; if the former, then the overall peg-in balance decreases. +;; the relevant BRC20 tokens are burnt (with fees paid to `fee-address`) +(define-public (finalize-peg-out-on-index (request-id uint) + (tx { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 4), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint }) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (signature-packs (list 10 { signer: principal, tx-hash: (buff 32), signature: (buff 65) })) + (token-trait )) + (begin + (try! (index-tx tx block proof signature-packs)) + (finalize-peg-out request-id (get bitcoin-tx tx) (get output tx) (get offset tx) token-trait))) + +(define-public (finalize-peg-out (request-id uint) (tx (buff 8192)) (output-idx uint) (offset-idx uint) (token-trait )) + (let ( + (token (contract-of token-trait)) + (request-details (try! (get-request-or-fail request-id))) + (token-details (try! (get-token-details-or-fail (get tick request-details)))) + (tx-idxed (try! (contract-call? .oracle-v1-06 get-bitcoin-tx-indexed-or-fail tx output-idx offset-idx))) + (tx-mined-height (try! (contract-call? .oracle-v1-06 get-bitcoin-tx-mined-or-fail tx))) + (amount-in-fixed (decimals-to-fixed (get amt tx-idxed) (contract-call? .oracle-v1-06 get-tick-decimals-or-default (get tick tx-idxed)))) + (fulfilled-by (get from tx-idxed)) + (is-fulfilled-by-peg-in (is-peg-in-address-approved fulfilled-by)) + ) + (asserts! (not (get peg-out-paused token-details)) err-paused) + (asserts! (is-eq token (get token token-details)) err-token-mismatch) + (asserts! (is-eq (get tick request-details) (get tick tx-idxed)) err-token-mismatch) + (asserts! (is-eq (get amount-net request-details) amount-in-fixed) err-invalid-amount) + (asserts! (is-eq (get peg-out-address request-details) (get to tx-idxed)) err-address-mismatch) + (asserts! (is-eq (get fulfilled-by request-details) fulfilled-by) err-address-mismatch) + (asserts! (< (get requested-at-burn-height request-details) tx-mined-height) err-tx-mined-before-request) + (asserts! (not (get-peg-in-sent-or-default tx output-idx offset-idx)) err-already-sent) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-peg-in-sent { tx: tx, output: output-idx, offset: offset-idx } true))) + (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-request request-id (merge request-details { finalized: true })))) + + (and (> (get fee request-details) u0) (as-contract (try! (contract-call? token-trait transfer-fixed (get fee request-details) tx-sender (var-get fee-address) none)))) + (and (> (get gas-fee request-details) u0) (as-contract (try! (contract-call? .token-susdt transfer-fixed (get gas-fee request-details) tx-sender (if is-fulfilled-by-peg-in (var-get fee-address) (get claimed-by request-details)) none)))) + + (if is-fulfilled-by-peg-in + (as-contract (try! (contract-call? token-trait burn-fixed (get amount-net request-details) tx-sender))) + (as-contract (try! (contract-call? token-trait transfer-fixed (get amount-net request-details) tx-sender (get claimed-by request-details) none))) + ) + + (print { type: "finalize-peg-out", request-id: request-id, tx: tx }) + (ok true))) + +;; revoke peg-out request +;; only after `request-revoke-grace-period` passed +;; returns fee and pegged-in tokens to the requester. +(define-public (revoke-peg-out (request-id uint) (token-trait )) + (let ( + (token (contract-of token-trait)) + (request-details (try! (get-request-or-fail request-id))) + (token-details (try! (get-token-details-or-fail (get tick request-details))))) + (asserts! (is-eq token (get token token-details)) err-token-mismatch) + (asserts! (> block-height (+ (get requested-at request-details) (get-request-revoke-grace-period))) err-revoke-grace-period) + (asserts! (< (get claimed request-details) block-height) err-request-already-claimed) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .brc20-bridge-registry-v1-02 set-request request-id (merge request-details { revoked: true })))) + + (and (> (get fee request-details) u0) (as-contract (try! (contract-call? token-trait transfer-fixed (get fee request-details) tx-sender (get requested-by request-details) none)))) + (and (> (get gas-fee request-details) u0) (as-contract (try! (contract-call? .token-susdt transfer-fixed (get gas-fee request-details) tx-sender (get requested-by request-details) none)))) + (as-contract (try! (contract-call? token-trait transfer-fixed (get amount-net request-details) tx-sender (get requested-by request-details) none))) + + (print { type: "revoke-peg-out", request-id: request-id }) + (ok true))) + +;; internal functions + +(define-private (index-tx + (tx { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 4), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint }) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (signature-packs (list 10 { signer: principal, tx-hash: (buff 32), signature: (buff 65) }))) + (begin + (and + (not (is-ok (contract-call? .oracle-v1-06 get-bitcoin-tx-indexed-or-fail (get bitcoin-tx tx) (get output tx) (get offset tx)))) + (as-contract (try! (contract-call? .oracle-v1-06 index-tx-many (list { tx: tx, block: block, proof: proof, signature-packs: signature-packs }))))) + (print { type: "indexed-tx", tx: tx, block: block, proof: proof, signature-packs: signature-packs }) + (ok true))) + +(define-private (is-contract-owner) + (ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised))) + +(define-private (min (a uint) (b uint)) + (if (< a b) a b)) + +(define-private (mul-down (a uint) (b uint)) + (/ (* a b) ONE_8)) + +(define-private (div-down (a uint) (b uint)) + (if (is-eq a u0) + u0 + (/ (* a ONE_8) b))) + +(define-private (decimals-to-fixed (amount uint) (decimals uint)) + (/ (* amount ONE_8) (pow u10 decimals))) diff --git a/contracts/brc20-bridge-registry-v1-02.clar b/contracts/brc20-bridge-registry-v1-02.clar new file mode 100644 index 0000000..cd71e46 --- /dev/null +++ b/contracts/brc20-bridge-registry-v1-02.clar @@ -0,0 +1,129 @@ +(use-trait sip010-trait .trait-sip-010.sip-010-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-token-not-found (err u1001)) +(define-constant err-request-not-found (err u1002)) + +(define-data-var contract-owner principal tx-sender) + +(define-map peg-in-sent { tx: (buff 8192), output: uint, offset: uint } bool) + +;; governance functions + +(define-public (set-contract-owner-legacy (new-owner principal)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-contract-owner new-owner)))) + +(define-public (approve-operator (operator principal) (approved bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 approve-operator operator approved)))) + +(define-public (set-request-revoke-grace-period (grace-period uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-request-revoke-grace-period grace-period)))) + +(define-public (set-request-claim-grace-period (grace-period uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-request-claim-grace-period grace-period)))) + +(define-public (pause-peg-in (tick (string-utf8 4)) (paused bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 pause-peg-in tick paused)))) + +(define-public (pause-peg-out (tick (string-utf8 4)) (paused bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 pause-peg-out tick paused)))) + +(define-public (set-peg-in-fee (tick (string-utf8 4)) (new-peg-in-fee uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-peg-in-fee tick new-peg-in-fee)))) + +(define-public (set-peg-out-fee (tick (string-utf8 4)) (new-peg-out-fee uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-peg-out-fee tick new-peg-out-fee)))) + +(define-public (set-peg-out-gas-fee (tick (string-utf8 4)) (new-peg-out-gas-fee uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-peg-out-gas-fee tick new-peg-out-gas-fee)))) + +(define-public (approve-token (tick (string-utf8 4)) (token principal) (approved bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 approve-token tick token approved)))) + +(define-public (approve-peg-in-address (address (buff 128)) (approved bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 approve-peg-in-address address approved)))) + +(define-public (set-contract-owner (new-contract-owner principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set contract-owner new-contract-owner)))) + +;; read-only functions + +(define-read-only (get-request-revoke-grace-period) + (contract-call? .brc20-bridge-registry-v1-01 get-request-revoke-grace-period)) + +(define-read-only (get-request-claim-grace-period) + (contract-call? .brc20-bridge-registry-v1-01 get-request-claim-grace-period)) + +(define-read-only (is-peg-in-address-approved (address (buff 128))) + (contract-call? .brc20-bridge-registry-v1-01 is-peg-in-address-approved address)) + +(define-read-only (get-token-to-tick-or-fail (token principal)) + (contract-call? .brc20-bridge-registry-v1-01 get-token-to-tick-or-fail token)) + +(define-read-only (get-token-details-or-fail (tick (string-utf8 4))) + (contract-call? .brc20-bridge-registry-v1-01 get-token-details-or-fail tick)) + +(define-read-only (get-token-details-or-fail-by-address (token principal)) + (contract-call? .brc20-bridge-registry-v1-01 get-token-details-or-fail-by-address token)) + +(define-read-only (is-approved-token (tick (string-utf8 4))) + (contract-call? .brc20-bridge-registry-v1-01 is-approved-token tick)) + +(define-read-only (get-request-or-fail (request-id uint)) + (contract-call? .brc20-bridge-registry-v1-01 get-request-or-fail request-id)) + +(define-read-only (get-approved-operator-or-default (operator principal)) + (contract-call? .brc20-bridge-registry-v1-01 get-approved-operator-or-default operator)) + +(define-read-only (get-peg-in-sent-or-default (bitcoin-tx (buff 8192)) (output uint) (offset uint)) + (match (as-max-len? bitcoin-tx u4096) + some-value + (or + (contract-call? .brc20-bridge-registry-v1-01 get-peg-in-sent-or-default some-value output offset) + (default-to false (map-get? peg-in-sent { tx: bitcoin-tx, output: output, offset: offset }))) + (default-to false (map-get? peg-in-sent { tx: bitcoin-tx, output: output, offset: offset })))) + +(define-read-only (is-approved-operator) + (contract-call? .brc20-bridge-registry-v1-01 is-approved-operator)) + +;; priviledged functions + +(define-public (set-peg-in-sent (peg-in-tx { tx: (buff 8192), output: uint, offset: uint }) (sent bool)) + (begin + (try! (is-approved-operator)) + (ok (map-set peg-in-sent peg-in-tx sent)))) + +(define-public (set-request (request-id uint) (details { requested-by: principal, peg-out-address: (buff 128), tick: (string-utf8 4), amount-net: uint, fee: uint, gas-fee: uint, claimed: uint, claimed-by: principal, fulfilled-by: (buff 128), revoked: bool, finalized: bool, requested-at: uint, requested-at-burn-height: uint})) + (begin + (try! (is-approved-operator)) + (as-contract (contract-call? .brc20-bridge-registry-v1-01 set-request request-id details)))) + +;; internal functions + +(define-private (is-contract-owner) + (ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised))) + diff --git a/contracts/btc-bridge-endpoint-v1-10.clar b/contracts/btc-bridge-endpoint-v1-10.clar new file mode 100644 index 0000000..d14cd5b --- /dev/null +++ b/contracts/btc-bridge-endpoint-v1-10.clar @@ -0,0 +1,619 @@ +(use-trait sip010-trait .trait-sip-010.sip-010-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-paused (err u1001)) +(define-constant err-peg-in-address-not-found (err u1002)) +(define-constant err-invalid-amount (err u1003)) +(define-constant err-invalid-tx (err u1004)) +(define-constant err-already-sent (err u1005)) +(define-constant err-address-mismatch (err u1006)) +(define-constant err-request-already-revoked (err u1007)) +(define-constant err-request-already-finalized (err u1008)) +(define-constant err-revoke-grace-period (err u1009)) +(define-constant err-request-already-claimed (err u1010)) +(define-constant err-bitcoin-tx-not-mined (err u1011)) +(define-constant err-invalid-input (err u1012)) +(define-constant err-tx-mined-before-request (err u1013)) +(define-constant err-dest-mismatch (err u1014)) +(define-constant err-token-mismatch (err u1015)) +(define-constant err-slippage (err u1016)) +(define-constant err-not-in-whitelist (err u1017)) + +(define-constant MAX_UINT u340282366920938463463374607431768211455) +(define-constant ONE_8 u100000000) + +(define-data-var contract-owner principal tx-sender) +(define-data-var fee-address principal tx-sender) + +(define-data-var peg-in-paused bool true) +(define-data-var peg-out-paused bool true) +(define-data-var peg-in-fee uint u0) +(define-data-var peg-in-min-fee uint u0) +(define-data-var peg-out-fee uint u0) +(define-data-var peg-out-min-fee uint u0) + +(define-map use-whitelist uint bool) +(define-map whitelisted {launch-id: uint, owner: (buff 128)} bool) + +;; governance functions + +(define-public (set-use-whitelist (launch-id uint) (new-whitelisted bool)) + (begin + (try! (is-contract-owner)) + (ok (map-set use-whitelist launch-id new-whitelisted)))) + +(define-public (set-whitelisted (launch-id uint) (whitelisted-users (list 200 {owner: (buff 128), whitelisted: bool}))) + (begin + (try! (is-contract-owner)) + (fold set-whitelisted-iter whitelisted-users launch-id) + (ok true))) + +(define-public (set-contract-owner (new-contract-owner principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set contract-owner new-contract-owner)))) + +(define-public (set-fee-address (new-fee-address principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set fee-address new-fee-address)))) + +(define-public (pause-peg-in (paused bool)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-in-paused paused)))) + +(define-public (pause-peg-out (paused bool)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-out-paused paused)))) + +(define-public (set-peg-in-fee (fee uint)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-in-fee fee)))) + +(define-public (set-peg-in-min-fee (fee uint)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-in-min-fee fee)))) + +(define-public (set-peg-out-fee (fee uint)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-out-fee fee)))) + +(define-public (set-peg-out-min-fee (fee uint)) + (begin + (try! (is-contract-owner)) + (ok (var-set peg-out-min-fee fee)))) + +;; read-only functions + +(define-read-only (is-peg-in-paused) + (var-get peg-in-paused)) + +(define-read-only (is-peg-out-paused) + (var-get peg-out-paused)) + +(define-read-only (get-peg-in-fee) + (var-get peg-in-fee)) + +(define-read-only (get-peg-in-min-fee) + (var-get peg-in-min-fee)) + +(define-read-only (get-peg-out-fee) + (var-get peg-out-fee)) + +(define-read-only (get-peg-out-min-fee) + (var-get peg-out-min-fee)) + +(define-read-only (get-request-revoke-grace-period) + (contract-call? .btc-bridge-registry-v1-03 get-request-revoke-grace-period)) + +(define-read-only (get-request-claim-grace-period) + (contract-call? .btc-bridge-registry-v1-03 get-request-claim-grace-period)) + +(define-read-only (is-peg-in-address-approved (address (buff 128))) + (contract-call? .btc-bridge-registry-v1-03 is-peg-in-address-approved address)) + +(define-read-only (get-request-or-fail (request-id uint)) + (contract-call? .btc-bridge-registry-v1-03 get-request-or-fail request-id)) + +(define-read-only (get-peg-in-sent-or-default (tx (buff 16384)) (output uint)) + (contract-call? .btc-bridge-registry-v1-03 get-peg-in-sent-or-default tx output)) + +(define-read-only (get-fee-address) + (var-get fee-address)) + +(define-read-only (extract-tx-ins-outs (tx (buff 16384))) + (if (try! (contract-call? .clarity-bitcoin-v1-06 is-segwit-tx tx)) + (let ( + (parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-06 parse-wtx tx) err-invalid-tx))) + (ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) })) + (let ( + (parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-06 parse-tx tx) err-invalid-tx))) + (ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) })) + )) + +(define-read-only (get-txid (tx (buff 16384))) + (if (try! (contract-call? .clarity-bitcoin-v1-06 is-segwit-tx tx)) + (ok (contract-call? .clarity-bitcoin-v1-06 get-segwit-txid tx)) + (ok (contract-call? .clarity-bitcoin-v1-06 get-txid tx)) + )) + +(define-read-only (get-use-whitelist-or-default (launch-id uint)) + (default-to false (map-get? use-whitelist launch-id))) + +(define-read-only (get-whitelisted-or-default (launch-id uint) (owner (buff 128))) + (if (get-use-whitelist-or-default launch-id) + (default-to false (map-get? whitelisted {launch-id: launch-id, owner: owner})) + true)) + +;; verify-mined +;; +;; it takes Bitcoin tx and confirms if the tx is mined on Bitcoin L1 +(define-read-only (verify-mined (tx (buff 16384)) (block { header: (buff 80), height: uint }) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint })) + (if (is-eq chain-id u1) + (let ( + (response (if (try! (contract-call? .clarity-bitcoin-v1-06 is-segwit-tx tx)) + (contract-call? .clarity-bitcoin-v1-06 was-segwit-tx-mined? block tx proof) + (contract-call? .clarity-bitcoin-v1-06 was-tx-mined? block tx proof)) + )) + (if (or (is-err response) (not (unwrap-panic response))) + err-bitcoin-tx-not-mined + (ok true) + )) + (ok true))) ;; if not mainnet, assume verified + +(define-read-only (create-order-0-or-fail (order principal)) + (ok (unwrap! (to-consensus-buff? order) err-invalid-input))) + +(define-read-only (decode-order-0-or-fail (order-script (buff 128))) + (let ( + (op-code (unwrap-panic (slice? order-script u1 u2)))) + (ok (unwrap! (from-consensus-buff? principal (unwrap-panic (slice? order-script (if (< op-code 0x4c) u2 u3) (len order-script)))) err-invalid-input)))) + +(define-read-only (validate-tx-0 (tx (buff 16384)) (output-idx uint) (order-idx uint)) + (let ( + (validation-data (try! (validate-tx-common tx output-idx order-idx)))) + (ok { order-details: (try! (decode-order-0-or-fail (get order-script validation-data))), fee: (get fee validation-data), amount-net: (get amount-net validation-data) }))) + +(define-read-only (create-order-1-or-fail (order { user: principal, pool-id: uint, min-dy: uint })) + (ok (unwrap! (to-consensus-buff? { u: (get user order), p: (int-to-ascii (get pool-id order)), y: (int-to-ascii (get min-dy order)) }) err-invalid-input))) + +(define-read-only (decode-order-1-or-fail (order-script (buff 128))) + (let ( + (op-code (unwrap-panic (slice? order-script u1 u2))) + (offset (if (< op-code 0x4c) u2 u3)) + (raw-order (unwrap! (from-consensus-buff? { u: principal, p: (string-ascii 40), y: (string-ascii 40) } (unwrap-panic (slice? order-script offset (len order-script)))) err-invalid-input)) + (pool-id (unwrap! (string-to-uint? (get p raw-order)) err-invalid-input)) + (min-dy (unwrap! (string-to-uint? (get y raw-order)) err-invalid-input))) + (ok { user: (get u raw-order), pool-id: pool-id, min-dy: min-dy }))) + +(define-read-only (validate-tx-1 (tx (buff 16384)) (output-idx uint) (order-idx uint) (token principal)) + (validate-tx-1-extra (try! (validate-tx-1-base tx output-idx order-idx)) token)) + +(define-read-only (create-order-2-or-fail (order { user: principal, pool1-id: uint, pool2-id: uint, min-dz: uint })) + (ok (unwrap! (to-consensus-buff? { u: (get user order), p1: (int-to-ascii (get pool1-id order)), p2: (int-to-ascii (get pool2-id order)), z: (int-to-ascii (get min-dz order)) }) err-invalid-input))) + +(define-read-only (decode-order-2-or-fail (order-script (buff 128))) + (let ( + (op-code (unwrap-panic (slice? order-script u1 u2))) + (offset (if (< op-code 0x4c) u2 u3)) + (raw-order (unwrap! (from-consensus-buff? { u: principal, p1: (string-ascii 40), p2: (string-ascii 40), z: (string-ascii 40) } (unwrap-panic (slice? order-script offset (len order-script)))) err-invalid-input)) + (pool1-id (unwrap! (string-to-uint? (get p1 raw-order)) err-invalid-input)) + (pool2-id (unwrap! (string-to-uint? (get p2 raw-order)) err-invalid-input)) + (min-dz (unwrap! (string-to-uint? (get z raw-order)) err-invalid-input))) + (ok { user: (get u raw-order), pool1-id: pool1-id, pool2-id: pool2-id, min-dz: min-dz }))) + +(define-read-only (validate-tx-2 (tx (buff 16384)) (output-idx uint) (order-idx uint) (token1 principal) (token2 principal)) + (validate-tx-2-extra (try! (validate-tx-2-base tx output-idx order-idx)) token1 token2)) + +(define-read-only (create-order-3-or-fail (order { user: principal, pool1-id: uint, pool2-id: uint, pool3-id: uint, min-dw: uint })) + (ok (unwrap! (to-consensus-buff? { u: (get user order), p1: (int-to-ascii (get pool1-id order)), p2: (int-to-ascii (get pool2-id order)), p3: (int-to-ascii (get pool3-id order)), w: (int-to-ascii (get min-dw order)) }) err-invalid-input))) + +(define-read-only (decode-order-3-or-fail (order-script (buff 128))) + (let ( + (op-code (unwrap-panic (slice? order-script u1 u2))) + (offset (if (< op-code 0x4c) u2 u3)) + (raw-order (unwrap! (from-consensus-buff? { u: principal, p1: (string-ascii 40), p2: (string-ascii 40), p3: (string-ascii 40), w: (string-ascii 40) } (unwrap-panic (slice? order-script offset (len order-script)))) err-invalid-input)) + (pool1-id (unwrap! (string-to-uint? (get p1 raw-order)) err-invalid-input)) + (pool2-id (unwrap! (string-to-uint? (get p2 raw-order)) err-invalid-input)) + (pool3-id (unwrap! (string-to-uint? (get p3 raw-order)) err-invalid-input)) + (min-dw (unwrap! (string-to-uint? (get w raw-order)) err-invalid-input))) + (ok { user: (get u raw-order), pool1-id: pool1-id, pool2-id: pool2-id, pool3-id: pool3-id, min-dw: min-dw }))) + +(define-read-only (validate-tx-3 (tx (buff 16384)) (output-idx uint) (order-idx uint) (token1 principal) (token2 principal) (token3 principal)) + (validate-tx-3-extra (try! (validate-tx-3-base tx output-idx order-idx)) token1 token2 token3)) + +(define-read-only (create-order-launchpad-or-fail (order { user: principal, launch-id: uint })) + (ok (unwrap! (to-consensus-buff? { u: (get user order), l: (int-to-ascii (get launch-id order)) }) err-invalid-input))) + +(define-read-only (decode-order-launchpad-or-fail (order-script (buff 128))) + (let ( + (op-code (unwrap-panic (slice? order-script u1 u2))) + (offset (if (< op-code 0x4c) u2 u3)) + (raw-order (unwrap! (from-consensus-buff? { u: principal, l: (string-ascii 40) } (unwrap-panic (slice? order-script offset (len order-script)))) err-invalid-input)) + (launch-id (unwrap! (string-to-uint? (get l raw-order)) err-invalid-input))) + (ok { user: (get u raw-order), launch-id: launch-id }))) + +(define-read-only (validate-tx-launchpad (tx (buff 16384)) (output-idx uint) (order-idx uint) (owner-idx uint)) + (validate-tx-launchpad-extra (try! (validate-tx-launchpad-base tx output-idx order-idx owner-idx)))) + +;; public functions + +;; pegs in BTC (net of fee) from Bitcoin to Stacks. +;; the relevant tx include OP_RETURN with Stacks address (see `create-order-or-fail`) +;; +;; tx => bitcoin tx hash of peg-in transfer +;; order-idx => index of OP_RETURN + +(define-public (finalize-peg-in-0 + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint)) + (let ( + (common-check (try! (finalize-peg-in-common tx block proof output-idx order-idx))) + (validation-data (try! (validate-tx-0 tx output-idx order-idx))) + (order-details (get order-details validation-data)) + (amount-net (get amount-net validation-data)) + (fee (get fee validation-data))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (and (> fee u0) (as-contract (try! (contract-call? .token-abtc mint-fixed fee (var-get fee-address))))) + (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net order-details))) + (print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-details: order-details, fee: fee, amount-net: amount-net }) + (ok true))) + +(define-public (finalize-peg-in-1 + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint) + (token-trait )) + (let ( + (common-check (try! (finalize-peg-in-common tx block proof output-idx order-idx))) + (validation-data (try! (validate-tx-1-base tx output-idx order-idx))) + (order-details (get order-details validation-data)) + (amount-net (get amount-net validation-data)) + (fee (get fee validation-data)) + (minted (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net tx-sender))))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (and (> fee u0) (as-contract (try! (contract-call? .token-abtc mint-fixed fee (var-get fee-address))))) + (print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-details: order-details, fee: fee, amount-net: amount-net }) + (match (validate-tx-1-extra validation-data (contract-of token-trait)) + extra-details + (let ( + (swapped (as-contract (try! (contract-call? .amm-swap-pool-v1-1 swap-helper .token-abtc token-trait (get factor extra-details) amount-net (some (get min-dy order-details))))))) + (as-contract (try! (contract-call? token-trait transfer-fixed swapped tx-sender (get user order-details) none))) + (ok true) + ) + err-value + (begin + (as-contract (try! (contract-call? .token-abtc transfer-fixed amount-net tx-sender (get user order-details) none))) + (ok false) + ) + ))) + +(define-public (finalize-peg-in-2 + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint) + (token1-trait ) (token2-trait )) + (let ( + (common-check (try! (finalize-peg-in-common tx block proof output-idx order-idx))) + (validation-data (try! (validate-tx-2-base tx output-idx order-idx))) + (order-details (get order-details validation-data)) + (amount-net (get amount-net validation-data)) + (fee (get fee validation-data)) + (minted (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net tx-sender))))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (and (> fee u0) (as-contract (try! (contract-call? .token-abtc mint-fixed fee (var-get fee-address))))) + (print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-details: order-details, fee: fee, amount-net: amount-net }) + (match (validate-tx-2-extra validation-data (contract-of token1-trait) (contract-of token2-trait)) + extra-details + (let ( + (swapped (as-contract (try! (contract-call? .amm-swap-pool-v1-1 swap-helper-a .token-abtc token1-trait token2-trait (get factor1 extra-details) (get factor2 extra-details) amount-net (some (get min-dz order-details))))))) + (as-contract (try! (contract-call? token2-trait transfer-fixed swapped tx-sender (get user order-details) none))) + (ok true) + ) + err-value + (begin + (as-contract (try! (contract-call? .token-abtc transfer-fixed amount-net tx-sender (get user order-details) none))) + (ok false) + ) + ))) + +(define-public (finalize-peg-in-3 + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint) + (token1-trait ) (token2-trait ) (token3-trait )) + (let ( + (common-check (try! (finalize-peg-in-common tx block proof output-idx order-idx))) + (validation-data (try! (validate-tx-3-base tx output-idx order-idx))) + (order-details (get order-details validation-data)) + (amount-net (get amount-net validation-data)) + (fee (get fee validation-data)) + (minted (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net tx-sender))))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (and (> fee u0) (as-contract (try! (contract-call? .token-abtc mint-fixed fee (var-get fee-address))))) + (print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-details: order-details, fee: fee, amount-net: amount-net }) + (match (validate-tx-3-extra validation-data (contract-of token1-trait) (contract-of token2-trait) (contract-of token3-trait)) + extra-details + (let ( + (swapped (as-contract (try! (contract-call? .amm-swap-pool-v1-1 swap-helper-b .token-abtc token1-trait token2-trait token3-trait (get factor1 extra-details) (get factor2 extra-details) (get factor3 extra-details) amount-net (some (get min-dw order-details))))))) + (as-contract (try! (contract-call? token3-trait transfer-fixed swapped tx-sender (get user order-details) none))) + (ok true) + ) + err-value + (begin + (as-contract (try! (contract-call? .token-abtc transfer-fixed amount-net tx-sender (get user order-details) none))) + (ok false) + ) + ))) + +(define-public (finalize-peg-in-launchpad + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint) (owner-idx uint)) + (let ( + (common-check (try! (finalize-peg-in-common tx block proof output-idx order-idx))) + (validation-data (try! (validate-tx-launchpad-base tx output-idx order-idx owner-idx))) + (order-details (get order-details validation-data)) + (amount-net (get amount-net validation-data)) + (fee (get fee validation-data))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (and (> fee u0) (as-contract (try! (contract-call? .token-abtc mint-fixed fee (var-get fee-address))))) + (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net tx-sender))) + (print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-details: order-details, fee: fee, amount-net: amount-net }) + (if (is-ok (validate-tx-launchpad-extra validation-data)) + (let ( + (bounds (as-contract (try! (contract-call? .alex-launchpad-v1-7 register-on-behalf (get user order-details) (get launch-id order-details) amount-net .token-abtc))))) + (map-set whitelisted { launch-id: (get launch-id order-details), owner: (get owner-script validation-data) } false) + (ok bounds) + ) + (begin + (as-contract (try! (contract-call? .token-abtc transfer-fixed amount-net tx-sender (get user order-details) none))) + (ok { start: u0, end: u0 }) + ) + ))) + +;; request peg-out of aBTC (net of fee) to `peg-out-address` +;; request escrows the relevant aBTC and gas-fee token to the contract until the request is either finalized or revoked. +;; +(define-public (request-peg-out-0 (peg-out-address (buff 128)) (amount uint)) + (let ( + (gas-fee (var-get peg-out-min-fee)) + (fee (- (max (mul-down amount (var-get peg-out-fee)) gas-fee) gas-fee)) + (check-amount (asserts! (> amount (+ fee gas-fee)) err-invalid-amount)) + (amount-net (- amount fee gas-fee)) + (request-details { requested-by: tx-sender, peg-out-address: peg-out-address, amount-net: amount-net, fee: fee, gas-fee: gas-fee, claimed: u0, claimed-by: tx-sender, fulfilled-by: 0x, revoked: false, finalized: false, requested-at: block-height, requested-at-burn-height: burn-block-height }) + (request-id (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-request u0 request-details))))) + (asserts! (not (var-get peg-out-paused)) err-paused) + + (try! (contract-call? .token-abtc transfer-fixed amount tx-sender (as-contract tx-sender) none)) + (print (merge request-details { type: "request-peg-out", request-id: request-id })) + (ok request-id))) + +(define-public (request-peg-out-1 (peg-out-address (buff 128)) (token-trait ) (factor uint) (dx uint) (min-dy (optional uint))) + (let ( + (swapped (try! (contract-call? .amm-swap-pool-v1-1 swap-helper token-trait .token-abtc factor dx min-dy)))) + (request-peg-out-0 peg-out-address swapped))) + +(define-public (request-peg-out-2 (peg-out-address (buff 128)) (token1-trait ) (token2-trait ) (factor1 uint) (factor2 uint) (dx uint) (min-dz (optional uint))) + (let ( + (swapped (try! (contract-call? .amm-swap-pool-v1-1 swap-helper-a token1-trait token2-trait .token-abtc factor1 factor2 dx min-dz)))) + (request-peg-out-0 peg-out-address swapped))) + +(define-public (request-peg-out-3 (peg-out-address (buff 128)) (token1-trait ) (token2-trait ) (token3-trait ) (factor1 uint) (factor2 uint) (factor3 uint) (dx uint) (min-dw (optional uint))) + (let ( + (swapped (try! (contract-call? .amm-swap-pool-v1-1 swap-helper-b token1-trait token2-trait token3-trait .token-abtc factor1 factor2 factor3 dx min-dw)))) + (request-peg-out-0 peg-out-address swapped))) + +(define-public (claim-peg-out (request-id uint) (fulfilled-by (buff 128))) + (let ( + (claimer tx-sender) + (request-details (try! (get-request-or-fail request-id)))) + (asserts! (not (var-get peg-out-paused)) err-paused) + (asserts! (< (get claimed request-details) block-height) err-request-already-claimed) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-request request-id (merge request-details { claimed: (+ block-height (get-request-claim-grace-period)), claimed-by: claimer, fulfilled-by: fulfilled-by })))) + (print (merge request-details { type: "claim-peg-out", request-id: request-id, claimed: (+ block-height (get-request-claim-grace-period)), claimed-by: claimer, fulfilled-by: fulfilled-by })) + (ok true) + ) +) + +;; finalize peg-out request +;; finalize `request-id` with `tx` +;; pays the fee to `fee-address` and burn the relevant pegged-in tokens. +;; +(define-public (finalize-peg-out + (request-id uint) + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (fulfilled-by-idx uint)) + (let ( + (request-details (try! (get-request-or-fail request-id))) + (was-mined (try! (verify-mined tx block proof))) + (parsed-tx (try! (extract-tx-ins-outs tx))) + (output (unwrap! (element-at (get outs parsed-tx) output-idx) err-invalid-tx)) + (fulfilled-by (get scriptPubKey (unwrap! (element-at (get outs parsed-tx) fulfilled-by-idx) err-invalid-tx))) + (amount (get value output)) + (peg-out-address (get scriptPubKey output)) + (is-fulfilled-by-peg-in (is-peg-in-address-approved fulfilled-by)) + ) + + (asserts! (not (var-get peg-out-paused)) err-paused) + (asserts! (is-eq amount (get amount-net request-details)) err-invalid-amount) + (asserts! (is-eq (get peg-out-address request-details) peg-out-address) err-address-mismatch) + (asserts! (is-eq (get fulfilled-by request-details) fulfilled-by) err-address-mismatch) + (asserts! (< (get requested-at-burn-height request-details) (get height block)) err-tx-mined-before-request) + ;; (asserts! (<= block-height (get claimed request-details)) err-request-claim-expired) ;; allow fulfilled if not claimed again + (asserts! (not (get-peg-in-sent-or-default tx output-idx)) err-already-sent) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-peg-in-sent tx output-idx true))) + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-request request-id (merge request-details { finalized: true })))) + + (and (> (get fee request-details) u0) (as-contract (try! (contract-call? .token-abtc transfer-fixed (get fee request-details) tx-sender (var-get fee-address) none)))) + (and (> (get gas-fee request-details) u0) (as-contract (try! (contract-call? .token-abtc transfer-fixed (get gas-fee request-details) tx-sender (if is-fulfilled-by-peg-in (var-get fee-address) (get claimed-by request-details)) none)))) + + (if is-fulfilled-by-peg-in + (as-contract (try! (contract-call? .token-abtc burn-fixed (get amount-net request-details) tx-sender))) + (as-contract (try! (contract-call? .token-abtc transfer-fixed (get amount-net request-details) tx-sender (get claimed-by request-details) none))) + ) + + (print { type: "finalize-peg-out", request-id: request-id, tx: tx }) + (ok true))) + +;; revoke peg-out request +;; only after `request-revoke-grace-period` passed +;; returns fee and pegged-in tokens to the requester. +(define-public (revoke-peg-out (request-id uint)) + (let ( + (request-details (try! (get-request-or-fail request-id)))) + (asserts! (> block-height (+ (get requested-at request-details) (get-request-revoke-grace-period))) err-revoke-grace-period) + (asserts! (< (get claimed request-details) block-height) err-request-already-claimed) + (asserts! (not (get revoked request-details)) err-request-already-revoked) + (asserts! (not (get finalized request-details)) err-request-already-finalized) + + (as-contract (try! (contract-call? .btc-bridge-registry-v1-03 set-request request-id (merge request-details { revoked: true })))) + + (and (> (get fee request-details) u0) (as-contract (try! (contract-call? .token-abtc transfer-fixed (get fee request-details) tx-sender (get requested-by request-details) none)))) + (and (> (get gas-fee request-details) u0) (as-contract (try! (contract-call? .token-abtc transfer-fixed (get gas-fee request-details) tx-sender (get requested-by request-details) none)))) + (as-contract (try! (contract-call? .token-abtc transfer-fixed (get amount-net request-details) tx-sender (get requested-by request-details) none))) + + (print { type: "revoke-peg-out", request-id: request-id }) + (ok true))) + +;; internal functions + +(define-private (validate-tx-1-base (tx (buff 16384)) (output-idx uint) (order-idx uint)) + (let ( + (validation-data (try! (validate-tx-common tx output-idx order-idx)))) + (ok { order-details: (try! (decode-order-1-or-fail (get order-script validation-data))), fee: (get fee validation-data), amount-net: (get amount-net validation-data) }))) + +(define-private (validate-tx-1-extra (validation-data { order-details: { user: principal, pool-id: uint, min-dy: uint }, fee: uint, amount-net: uint }) (token principal)) + (let ( + (order-details (get order-details validation-data)) + (pool-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool-id order-details)))) + (token-y (if (is-eq (get token-x pool-details) .token-abtc) (get token-y pool-details) (get token-x pool-details))) + (factor (get factor pool-details)) + (dy (try! (contract-call? .amm-swap-pool-v1-1 get-helper .token-abtc token-y factor (get amount-net validation-data))))) + (asserts! (>= dy (get min-dy order-details)) err-slippage) + (asserts! (is-eq token-y token) err-token-mismatch) + (ok { validation-data: validation-data, token-y: token-y, factor: factor }))) + +(define-private (validate-tx-2-base (tx (buff 16384)) (output-idx uint) (order-idx uint)) + (let ( + (validation-data (try! (validate-tx-common tx output-idx order-idx)))) + (ok { order-details: (try! (decode-order-2-or-fail (get order-script validation-data))), fee: (get fee validation-data), amount-net: (get amount-net validation-data) }))) + +(define-private (validate-tx-2-extra (validation-data { order-details: { user: principal, pool1-id: uint, pool2-id: uint, min-dz: uint }, fee: uint, amount-net: uint }) (token1 principal) (token2 principal)) + (let ( + (order-details (get order-details validation-data)) + (pool1-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool1-id order-details)))) + (pool2-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool2-id order-details)))) + (token1-y (if (is-eq (get token-x pool1-details) .token-abtc) (get token-y pool1-details) (get token-x pool1-details))) + (token2-y (if (is-eq (get token-x pool2-details) token1-y) (get token-y pool2-details) (get token-x pool2-details))) + (factor1 (get factor pool1-details)) + (factor2 (get factor pool2-details)) + (dz (try! (contract-call? .amm-swap-pool-v1-1 get-helper-a .token-abtc token1-y token2-y factor1 factor2 (get amount-net validation-data))))) + (asserts! (is-eq token1-y token1) err-token-mismatch) + (asserts! (is-eq token2-y token2) err-token-mismatch) + (asserts! (>= dz (get min-dz order-details)) err-slippage) + (ok { validation-data: validation-data, token1-y: token1-y, token2-y: token2-y, factor1: factor1, factor2: factor2 }))) + +(define-private (validate-tx-3-base (tx (buff 16384)) (output-idx uint) (order-idx uint)) + (let ( + (validation-data (try! (validate-tx-common tx output-idx order-idx)))) + (ok { order-details: (try! (decode-order-3-or-fail (get order-script validation-data))), fee: (get fee validation-data), amount-net: (get amount-net validation-data) }))) + +(define-private (validate-tx-3-extra (validation-data { order-details: { user: principal, pool1-id: uint, pool2-id: uint, pool3-id: uint, min-dw: uint }, fee: uint, amount-net: uint }) (token1 principal) (token2 principal) (token3 principal)) + (let ( + (order-details (get order-details validation-data)) + (pool1-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool1-id order-details)))) + (pool2-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool2-id order-details)))) + (pool3-details (try! (contract-call? .amm-swap-pool-v1-1 get-pool-details-by-id (get pool3-id order-details)))) + (token1-y (if (is-eq (get token-x pool1-details) .token-abtc) (get token-y pool1-details) (get token-x pool1-details))) + (token2-y (if (is-eq (get token-x pool2-details) token1-y) (get token-y pool2-details) (get token-x pool2-details))) + (token3-y (if (is-eq (get token-x pool3-details) token2-y) (get token-y pool3-details) (get token-x pool3-details))) + (factor1 (get factor pool1-details)) + (factor2 (get factor pool2-details)) + (factor3 (get factor pool3-details)) + (dw (try! (contract-call? .amm-swap-pool-v1-1 get-helper-b .token-abtc token1-y token2-y token3-y factor1 factor2 factor3 (get amount-net validation-data))))) + (asserts! (is-eq token1-y token1) err-token-mismatch) + (asserts! (is-eq token2-y token2) err-token-mismatch) + (asserts! (is-eq token3-y token3) err-token-mismatch) + (asserts! (>= dw (get min-dw order-details)) err-slippage) + (ok { validation-data: validation-data, token1-y: token1-y, token2-y: token2-y, token3-y: token3-y, factor1: factor1, factor2: factor2, factor3: factor3 }))) + +(define-private (validate-tx-launchpad-base (tx (buff 16384)) (output-idx uint) (order-idx uint) (owner-idx uint)) + (let ( + (validation-data (try! (validate-tx-common tx output-idx order-idx))) + (owner-script (get scriptPubKey (unwrap-panic (element-at? (get outs (get parsed-tx validation-data)) owner-idx))))) + (ok { owner-script: owner-script, order-details: (try! (decode-order-launchpad-or-fail (get order-script validation-data))), fee: (get fee validation-data), amount-net: (get amount-net validation-data) }))) + +(define-private (validate-tx-launchpad-extra (validation-data { owner-script: (buff 128), order-details: { user: principal, launch-id: uint }, fee: uint, amount-net: uint })) + (let ( + (order-details (get order-details validation-data))) + (asserts! (get-whitelisted-or-default (get launch-id order-details) (get owner-script validation-data)) err-not-in-whitelist) + (try! (contract-call? .alex-launchpad-v1-7 validate-register (get user order-details) (get launch-id order-details) (get amount-net validation-data) .token-abtc)) + (ok validation-data))) + +(define-private (validate-tx-common (tx (buff 16384)) (output-idx uint) (order-idx uint)) + (let ( + (parsed-tx (try! (extract-tx-ins-outs tx))) + (output (unwrap! (element-at (get outs parsed-tx) output-idx) err-invalid-tx)) + (amount (get value output)) + (peg-in-address (get scriptPubKey output)) + (order-script (get scriptPubKey (unwrap-panic (element-at? (get outs parsed-tx) order-idx)))) + (fee (max (mul-down amount (var-get peg-in-fee)) (var-get peg-in-min-fee))) + (amount-net (- amount fee))) + (asserts! (not (get-peg-in-sent-or-default tx output-idx)) err-already-sent) + (asserts! (is-peg-in-address-approved peg-in-address) err-peg-in-address-not-found) + (asserts! (> amount-net u0) err-invalid-amount) + + (ok { parsed-tx: parsed-tx, order-script: order-script, fee: fee, amount-net: amount-net }))) + +(define-private (finalize-peg-in-common + (tx (buff 16384)) + (block { header: (buff 80), height: uint }) + (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }) + (output-idx uint) (order-idx uint)) + (begin + (asserts! (not (var-get peg-in-paused)) err-paused) + (verify-mined tx block proof))) + +(define-private (set-whitelisted-iter (e {owner: (buff 128), whitelisted: bool}) (launch-id uint)) + (begin + (map-set whitelisted {launch-id: launch-id, owner: (get owner e)} (get whitelisted e)) + launch-id)) + +(define-private (is-contract-owner) + (ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised))) + +(define-private (max (a uint) (b uint)) + (if (< a b) b a) +) + +(define-private (min (a uint) (b uint)) + (if (< a b) a b)) + +(define-private (mul-down (a uint) (b uint)) + (/ (* a b) ONE_8)) + +(define-private (div-down (a uint) (b uint)) + (if (is-eq a u0) + u0 + (/ (* a ONE_8) b))) + diff --git a/contracts/btc-bridge-registry-v1-03.clar b/contracts/btc-bridge-registry-v1-03.clar new file mode 100644 index 0000000..12f53a1 --- /dev/null +++ b/contracts/btc-bridge-registry-v1-03.clar @@ -0,0 +1,87 @@ +(use-trait sip010-trait .trait-sip-010.sip-010-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-request-not-found (err u1002)) +(define-constant err-invalid-input (err u1003)) + +(define-data-var contract-owner principal tx-sender) + +(define-map peg-in-sent { tx: (buff 16384), output: uint } bool) + +;; governance functions + +(define-public (set-contract-owner (new-contract-owner principal)) + (begin + (try! (is-contract-owner)) + (ok (var-set contract-owner new-contract-owner)))) + +(define-public (set-contract-owner-legacy (new-owner principal)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .btc-bridge-registry-v1-02 set-contract-owner new-owner)))) + +(define-public (approve-operator (operator principal) (approved bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .btc-bridge-registry-v1-02 approve-operator operator approved)))) + +(define-public (set-request-revoke-grace-period (grace-period uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .btc-bridge-registry-v1-02 set-request-revoke-grace-period grace-period)))) + +(define-public (set-request-claim-grace-period (grace-period uint)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .btc-bridge-registry-v1-02 set-request-claim-grace-period grace-period)))) + +(define-public (approve-peg-in-address (address (buff 128)) (approved bool)) + (begin + (try! (is-contract-owner)) + (as-contract (contract-call? .btc-bridge-registry-v1-02 approve-peg-in-address address approved)))) + +;; read-only functions + +(define-read-only (get-request-revoke-grace-period) + (contract-call? .btc-bridge-registry-v1-02 get-request-revoke-grace-period)) + +(define-read-only (get-request-claim-grace-period) + (contract-call? .btc-bridge-registry-v1-02 get-request-claim-grace-period)) + +(define-read-only (is-peg-in-address-approved (address (buff 128))) + (contract-call? .btc-bridge-registry-v1-02 is-peg-in-address-approved address)) + +(define-read-only (get-request-or-fail (request-id uint)) + (contract-call? .btc-bridge-registry-v1-02 get-request-or-fail request-id)) + +(define-read-only (get-approved-operator-or-default (operator principal)) + (contract-call? .btc-bridge-registry-v1-02 get-approved-operator-or-default operator)) + +(define-read-only (is-approved-operator) + (contract-call? .btc-bridge-registry-v1-02 is-approved-operator)) + +(define-read-only (get-peg-in-sent-or-default (tx (buff 16384)) (output uint)) + (match (as-max-len? tx u4096) + some-value + (or + (contract-call? .btc-bridge-registry-v1-02 get-peg-in-sent-or-default some-value output) + (default-to false (map-get? peg-in-sent { tx: tx, output: output }))) + (default-to false (map-get? peg-in-sent { tx: tx, output: output })))) + +;; priviledged functions + +(define-public (set-peg-in-sent (tx (buff 16384)) (output uint) (sent bool)) + (begin + (try! (is-approved-operator)) + (ok (map-set peg-in-sent { tx: tx, output: output } sent)))) + +(define-public (set-request (request-id uint) (details { requested-by: principal, peg-out-address: (buff 128), amount-net: uint, fee: uint, gas-fee: uint, claimed: uint, claimed-by: principal, fulfilled-by: (buff 128), revoked: bool, finalized: bool, requested-at: uint, requested-at-burn-height: uint})) + (begin + (try! (is-approved-operator)) + (contract-call? .btc-bridge-registry-v1-02 set-request request-id details))) + +;; internal functions + +(define-private (is-contract-owner) + (ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised))) +