/
btc-bridge-endpoint-dev-preview-1.clar
252 lines (252 loc) · 12.6 KB
/
btc-bridge-endpoint-dev-preview-1.clar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
(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 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-out-fee uint u0)
(define-data-var peg-out-gas-fee uint u0)
(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-out-fee (fee uint))
(begin
(try! (is-contract-owner))
(ok (var-set peg-out-fee fee))
)
)
(define-public (set-peg-out-gas-fee (fee uint))
(begin
(try! (is-contract-owner))
(ok (var-set peg-out-gas-fee fee))
)
)
(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-out-fee)
(var-get peg-out-fee)
)
(define-read-only (get-peg-out-gas-fee)
(var-get peg-out-gas-fee)
)
(define-read-only (get-request-revoke-grace-period)
(contract-call? .btc-bridge-registry-dev-preview-1 get-request-revoke-grace-period)
)
(define-read-only (get-request-claim-grace-period)
(contract-call? .btc-bridge-registry-dev-preview-1 get-request-claim-grace-period)
)
(define-read-only (is-peg-in-address-approved (address (buff 128)))
(contract-call? .btc-bridge-registry-dev-preview-1 is-peg-in-address-approved address))
(define-read-only (get-request-or-fail (request-id uint))
(contract-call? .btc-bridge-registry-dev-preview-1 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 (tx (buff 4096)) (output uint))
(contract-call? .btc-bridge-registry-dev-preview-1 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 4096)))
(if (try! (contract-call? .clarity-bitcoin-v1-02 is-segwit-tx tx))
(let
(
(parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-02 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-02 parse-tx tx) err-invalid-tx))
)
(ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) })
)
)
)
(define-read-only (get-txid (tx (buff 4096)))
(if (try! (contract-call? .clarity-bitcoin-v1-02 is-segwit-tx tx))
(ok (contract-call? .clarity-bitcoin-v1-02 get-segwit-txid tx))
(ok (contract-call? .clarity-bitcoin-v1-02 get-txid tx))
)
)
(define-read-only (verify-mined (tx (buff 4096)) (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-02 is-segwit-tx tx))
(contract-call? .clarity-bitcoin-v1-02 was-segwit-tx-mined? block tx proof)
(contract-call? .clarity-bitcoin-v1-02 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 (validate-tx (tx (buff 4096)) (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))))
(order-details (try! (decode-order-or-fail order-script)))
(fee (mul-down amount (var-get peg-in-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)
(ok { order-details: order-details, fee: fee, amount-net: amount-net })
)
)
(define-public (finalize-peg-in
(tx (buff 4096))
(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 (
(was-mined (try! (verify-mined tx block proof)))
(validation-data (try! (validate-tx tx output-idx order-idx)))
(order-details (get order-details validation-data))
(order-address (get user order-details))
(dest (get dest order-details)) ;; for now, dest is ignored
(fee (get fee validation-data))
(amount-net (get amount-net validation-data)))
(asserts! (not (var-get peg-in-paused)) err-paused)
(as-contract (try! (contract-call? .btc-bridge-registry-dev-preview-1 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)))))
(and (> amount-net u0) (as-contract (try! (contract-call? .token-abtc mint-fixed amount-net order-address))))
(print { type: "peg-in", tx-id: (try! (get-txid tx)), output: output-idx, order-address: order-address, fee: fee, amount-net: amount-net, dest: dest })
(ok true)))
(define-public (request-peg-out (amount uint) (peg-out-address (buff 128)))
(let (
(fee (mul-down amount (var-get peg-out-fee)))
(amount-net (- amount fee))
(gas-fee (var-get peg-out-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-dev-preview-1 set-request u0 request-details)))))
(asserts! (not (var-get peg-out-paused)) err-paused)
(asserts! (> amount u0) err-invalid-amount)
(try! (contract-call? .token-abtc 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)))
(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-dev-preview-1 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)
)
)
(define-public (finalize-peg-out
(request-id uint)
(tx (buff 4096))
(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-dev-preview-1 set-peg-in-sent tx output-idx true)))
(as-contract (try! (contract-call? .btc-bridge-registry-dev-preview-1 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-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-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)))
(define-public (revoke-peg-out (request-id uint))
(let (
(request-details (try! (get-request-or-fail request-id))))
(asserts! (not (var-get peg-out-paused)) err-paused)
(asserts! (> block-height (+ (get requested-at request-details) (get-request-revoke-grace-period))) err-revoke-grace-period)
(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-dev-preview-1 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-susdt 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)))
(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)))