/
wrapper-migrator.clar
269 lines (240 loc) · 8.28 KB
/
wrapper-migrator.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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
;; The wrapper migrator contract provides a way for users to upgrade a
;; BNS legacy name to BNSx.
;;
;; The high-level flow for using this migrator is:
;;
;; - Deploy a wrapper contract (see [`.name-wrapper`](`./name-wrapper.md`))
;; - Verify the wrapper contract
;; - Finalize the migration
;;
;; Because Stacks contracts don't have a way to verify the source code of
;; another contract, each wrapper contract must be verified by requesting a
;; signature off-chain. This prevents malicious users from deploying "fact" wrapper
;; contracts without the same guarantees.
;;
;; For more detail on how each wrapper is verified, see [`verify-wrapper`](#verify-wrapper)
;;
;; Authorization for valid wrapper verifiers is only allowed through extensions with the
;; "mig-signer" role. By default, the contract deployer is a valid signer.
;;
;; During migration, the legacy name is transferred to the wrapper contract. Then,
;; this contract interfaces with the [`.bnsx-registry`](`./core/name-registry.md`)
;; contract to mint a new BNSx name.
(define-constant ROLE "mig-signer")
(define-constant ERR_NO_NAME (err u6000))
(define-constant ERR_UNAUTHORIZED (err u6001))
(define-constant ERR_RECOVER (err u6002))
(define-constant ERR_INVALID_CONTRACT_NAME (err u6003))
(define-constant ERR_NAME_TRANSFER (err u6004))
(define-constant ERR_WRAPPER_USED (err u6005))
(define-constant ERR_WRAPPER_NOT_REGISTERED (err u6006))
(define-constant ERR_WRAPPER_ALREADY_REGISTERED (err u6007))
(define-map migrator-signers-map (buff 20) bool)
(define-map name-wrapper-map uint principal)
(define-map wrapper-name-map principal uint)
(define-map wrapper-id-map principal uint)
(define-map id-wrapper-map uint principal)
(define-data-var next-wrapper-id-var uint u0)
;; DAO operations
;; Authorization check - only extensions with the role "mig-signer" can add/remove
;; wrapper verifiers.
(define-public (is-dao-or-extension)
;; (ok (asserts! (or (is-eq tx-sender .bnsx-extensions) (contract-call? .bnsx-extensions has-role-or-extension contract-caller ROLE)) ERR_UNAUTHORIZED))
(ok (asserts! (contract-call? .bnsx-extensions has-role-or-extension contract-caller ROLE) ERR_UNAUTHORIZED))
)
;; #[allow(unchecked_data)]
(define-private (set-signers-iter (item { signer: (buff 20), enabled: bool }))
(let
(
(pubkey (get signer item))
)
(print pubkey)
(map-set migrator-signers-map pubkey (get enabled item))
pubkey
)
)
;; Set valid wrapper verifiers
;;
;; @param signers; a list of { signer: principal, enabled: bool } tuples.
;; Existing verifiers can be removed by setting `enabled` to false.
(define-public (set-signers (signers (list 50 { signer: (buff 20), enabled: bool })))
(begin
(try! (is-dao-or-extension))
(ok (map set-signers-iter signers))
)
)
;; Migration
;; Upgrade a name to BNSx
;;
;; This function has three main steps:
;;
;; - Verify the wrapper ([`verify-wrapper`](#verify-wrapper))
;; - Transfer the BNS legacy name to the wrapper ([`resolve-and-transfer`](#resolve-and-transfer))
;; - Register the name in the BNSx name registry ([`.bnsx-registry#register`](`./core/name-registry#register.md`))
;;
;; @param wrapper; the principal of the wrapper contract that will be used
;; @param signature; a signature attesting to the validity of the wrapper contract
;; @param recipient; a principal that will receive the BNSx name. Useful for consolidating
;; names into one wallet.
(define-public (migrate (wrapper principal) (signature (buff 65)) (recipient principal))
(let
(
;; #[filter(wrapper)]
(wrapper-ok (try! (verify-wrapper wrapper signature)))
(properties (try! (resolve-and-transfer wrapper)))
(name (get name properties))
(namespace (get namespace properties))
(id (try! (contract-call? .bnsx-registry register
{
name: name,
namespace: namespace,
}
recipient
)))
(meta (merge { id: id } properties))
)
(print {
topic: "migrate",
wrapper: wrapper,
id: id,
})
(asserts! (map-insert name-wrapper-map id wrapper) ERR_WRAPPER_USED)
(asserts! (map-insert wrapper-name-map wrapper id) ERR_WRAPPER_USED)
(ok meta)
)
)
;; Register a wrapper contract
;;
;; This is necessary to establish an integer ID for each wrapper principal.
;; This ID can then be used to validate signatures
(define-public (register-wrapper (wrapper principal))
(let
(
(id (get-next-wrapper-id))
)
(asserts! (map-insert wrapper-id-map wrapper id) ERR_WRAPPER_ALREADY_REGISTERED)
(map-insert id-wrapper-map id wrapper)
(ok id)
)
)
;; Signature validation
;; Verify a wrapper principal.
;;
;; The message being signed is the Clarity-serialized representation of the `wrapper`
;; principal.
;;
;; The pubkey is recovered from the signature. The `hash160` of this pubkey is then checked
;; to ensure that pubkey hash is stored as a valid signer.
;;
;; @throws if the signature is invalid (cannot be recovered)
;;
;; @throws if the pubkey is not a valid verifier
;;
(define-read-only (verify-wrapper (wrapper principal) (signature (buff 65)))
(let
(
(id (unwrap! (map-get? wrapper-id-map wrapper) ERR_WRAPPER_NOT_REGISTERED))
(msg (sha256 id))
(pubkey (unwrap! (secp256k1-recover? msg signature) ERR_RECOVER))
(pubkey-hash (hash160 pubkey))
)
;; (ok pubkey-hash)
(asserts! (default-to false (map-get? migrator-signers-map pubkey-hash)) ERR_UNAUTHORIZED)
(ok true)
)
)
(define-read-only (hash-id (id uint))
(sha256 id)
)
(define-read-only (debug-signature (wrapper principal) (signature (buff 65)))
(let
(
(pubkey-hash (try! (recover-pubkey-hash wrapper signature)))
)
(ok {
pubkey-hash: pubkey-hash,
valid-signer: (default-to false (map-get? migrator-signers-map pubkey-hash)),
})
)
)
(define-read-only (recover-pubkey-hash (wrapper principal) (signature (buff 65)))
(let
(
(id (unwrap! (map-get? wrapper-id-map wrapper) ERR_WRAPPER_NOT_REGISTERED))
(msg (sha256 id))
(pubkey (unwrap! (secp256k1-recover? msg signature) ERR_RECOVER))
)
(ok (hash160 pubkey))
)
)
;; Helper method to check if a given principal is a valid verifier
(define-read-only (is-valid-signer (pubkey (buff 20)))
(default-to false (map-get? migrator-signers-map pubkey))
)
;; Fetch the BNS legacy name and name properties owned by a given account.
;;
;; @throws if the account does not own a valid name
;;
;; @throws if the name owned by the account is expired
(define-read-only (get-legacy-name (account principal))
(match (contract-call? 'SP000000000000000000002Q6VF78.bns resolve-principal account)
name (let
(
(properties (unwrap-panic (contract-call? 'SP000000000000000000002Q6VF78.bns name-resolve (get namespace name) (get name name))))
)
(ok (merge name properties))
)
e ERR_NO_NAME
)
)
;; Transfer an account's BNS legacy name to a wrapper contract.
;; #[allow(unchecked_data)]
(define-private (resolve-and-transfer (wrapper principal))
(let
(
(name (try! (get-legacy-name tx-sender)))
)
(match (contract-call? 'SP000000000000000000002Q6VF78.bns name-transfer (get namespace name) (get name name) wrapper (some (get zonefile-hash name)))
success (begin
(print (merge name {
topic: "v1-name-transfer",
wrapper: wrapper,
}))
(ok name)
)
err-code (begin
(print {
topic: "name-transfer-error",
bns-error: err-code,
sender: tx-sender,
name: name,
})
ERR_NAME_TRANSFER
)
)
)
)
(define-private (get-next-wrapper-id)
(let
(
(id (var-get next-wrapper-id-var))
)
(var-set next-wrapper-id-var (+ id u1))
id
)
)
;; Getters
;; Helper method to fetch the BNS legacy name that was previously transferred to
;; a given wrapper contract.
(define-read-only (get-wrapper-name (wrapper principal)) (map-get? wrapper-name-map wrapper))
;; Helper method to fetch the wrapper contract that was used during migration of a
;; given name
;;
;; @param name; the name ID of a BNSx name
(define-read-only (get-name-wrapper (name uint)) (map-get? name-wrapper-map name))
(define-read-only (get-id-from-wrapper (wrapper principal))
(map-get? wrapper-id-map wrapper)
)
(define-read-only (get-wrapper-from-id (id uint))
(map-get? id-wrapper-map id)
)