Skip to content

Commit

Permalink
test: progress on wormhole's coverage - 97%
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludo Galabru committed Oct 18, 2023
1 parent 31204d4 commit 65ae13e
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 87 deletions.
98 changes: 47 additions & 51 deletions contracts/wormhole/wormhole-core-v1.clar
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@
(define-constant ERR_VAA_CHECKS_THRESHOLD_SIGNATURE (err u1102))
;; Multiple signatures were issued by the same guardian
(define-constant ERR_VAA_CHECKS_REDUNDANT_SIGNATURE (err u1103))
;; Guardian set specified is expired
(define-constant ERR_VAA_CHECKS_GUARDIAN_SET_EXPIRED (err u1104))
;; Guardian signature not comprised in guardian set specified
(define-constant ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY (err u1105))
;; Guardian Set Update initiated by an unauthorized module
Expand All @@ -68,6 +66,8 @@
(define-constant ERR_GSU_CHECK_CHAIN (err u1303))
;; Guardian Set Update new index invalid
(define-constant ERR_GSU_CHECK_INDEX (err u1304))
;; Quorum of addresses required
(define-constant QUORUM u13)

;;;; Data vars

Expand Down Expand Up @@ -160,26 +160,24 @@
;; @desc Parse and check the validity of a Verified Action Approval (VAA)
;; @param vaa-bytes:
(define-read-only (parse-and-verify-vaa (vaa-bytes (buff 8192)))
(let ((message (try! (parse-vaa vaa-bytes)))
(active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id (get vaa message)) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY))
(signatures-from-active-guardians (fold batch-check-active-public-keys (get recovered-public-keys message)
{
active-guardians: active-guardians,
value: (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u33))) u20))
})))
;; Ensure that the guardian-set-id is the active one
(asserts! (is-eq (get guardian-set-id (get vaa message)) (var-get active-guardian-set-id))
ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)
;; Ensure that version is supported (v1 only)
(asserts! (is-eq (get version (get vaa message)) u1)
ERR_VAA_CHECKS_VERSION_UNSUPPORTED)
;; Ensure that the count of valid signatures is >= 13
(asserts! (>= (len (get value signatures-from-active-guardians)) u13)
ERR_VAA_CHECKS_THRESHOLD_SIGNATURE)
;; TODO: Ensure that the guardian set is not expired
;; ERR_VAA_CHECKS_GUARDIAN_SET_EXPIRED
;; Good to go!
(ok (get vaa message))))
(let ((message (try! (parse-vaa vaa-bytes))))
;; Ensure that the guardian-set-id is the active one
(asserts! (is-eq (get guardian-set-id (get vaa message)) (var-get active-guardian-set-id))
ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)
(let ((active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id (get vaa message)) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY))
(signatures-from-active-guardians (fold batch-check-active-public-keys (get recovered-public-keys message)
{
active-guardians: active-guardians,
result: (list)
})))
;; Ensure that version is supported (v1 only)
(asserts! (is-eq (get version (get vaa message)) u1)
ERR_VAA_CHECKS_VERSION_UNSUPPORTED)
;; Ensure that the count of valid signatures is >= 13
(asserts! (>= (len (get result signatures-from-active-guardians)) QUORUM)
ERR_VAA_CHECKS_THRESHOLD_SIGNATURE)
;; Good to go!
(ok (get vaa message)))))

;; @desc Update the active set of guardians
;; @param guardian-set-vaa: VAA embedding the Guardian Set Update informations
Expand All @@ -193,28 +191,22 @@
(cursor-guardians-data (try! (parse-and-verify-guardians-set (get payload vaa))))
(set-id (get new-index (get value cursor-guardians-data)))
(eth-addresses (get guardians-eth-addresses (get value cursor-guardians-data)))
(acc (unwrap-panic (as-max-len? (list {
compressed-public-key: (unwrap-panic (as-max-len? 0x u33)),
uncompressed-public-key: (unwrap-panic (as-max-len? 0x u64))
}) u20)))
(consolidated-public-keys (fold check-and-consolidate-public-keys
uncompressed-public-keys
{ success: true, cursor: u0, eth-addresses: eth-addresses, result: acc }))
{ cursor: u0, eth-addresses: eth-addresses, result: (list) }))
)
;; Ensure that enough uncompressed-public-keys were provided
(asserts! (is-eq (len uncompressed-public-keys) (len eth-addresses))
ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS)
;; Check guardians uncompressed-public-keys
(asserts! (get success consolidated-public-keys)
ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS)

(map-set guardian-sets { set-id: set-id }
(unwrap-panic (as-max-len?
(unwrap-panic (slice? (get result consolidated-public-keys) u1 (len (get result consolidated-public-keys))))
u19)))
;; Update storage
(map-set guardian-sets { set-id: set-id } (get result consolidated-public-keys))
(var-set active-guardian-set-id set-id)
(var-set guardian-set-initialized true)
(print { object: "guardian-set", action: "updated", data: { guardians-eth-addresses: eth-addresses, guardians-public-keys: uncompressed-public-keys }})
;; Emit Event
(print {
object: "guardian-set",
action: "updated",
data: { guardians-eth-addresses: eth-addresses, guardians-public-keys: uncompressed-public-keys }})
(ok {
vaa: vaa,
result: {
Expand All @@ -236,7 +228,11 @@
;; @desc Foldable function admitting an uncompressed 64 byts public key as an input, producing a record { uncompressed-public-key, compressed-public-key }
(define-private (check-and-consolidate-public-keys
(uncompressed-public-key (buff 64))
(acc { success: bool, cursor: uint, eth-addresses: (list 19 (buff 20)), result: (list 20 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64)})}))
(acc {
cursor: uint,
eth-addresses: (list 19 (buff 20)),
result: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64)})
}))
(let ((eth-address (unwrap-panic (element-at? (get eth-addresses acc) (get cursor acc))))
(compressed-public-key (compress-public-key uncompressed-public-key))
(entry (if (is-eth-address-matching-public-key uncompressed-public-key eth-address)
Expand All @@ -245,17 +241,15 @@
{
cursor: (+ u1 (get cursor acc)),
eth-addresses: (get eth-addresses acc),
success: true,
result: (unwrap-panic (as-max-len? (append (get result acc) entry) u20)),
result: (unwrap-panic (as-max-len? (append (get result acc) entry) u19)),
}))

;; @desc Foldable function admitting an uncompressed 64 byts public key as an input, producing a record { uncompressed-public-key, compressed-public-key }
(define-private (batch-recover-public-keys
(entry { guardian-id: uint, signature: (buff 65) })
(acc { message-hash: (buff 32), value: (list 19 { recovered-compressed-public-key: (buff 33), guardian-id: uint }) }))
(let ((recovered-compressed-public-key (secp256k1-recover? (get message-hash acc) (get signature entry)))
(updated-public-keys
(match recovered-compressed-public-key
(updated-public-keys (match recovered-compressed-public-key
public-key (append (get value acc) { recovered-compressed-public-key: public-key, guardian-id: (get guardian-id entry) } )
error (get value acc))))
{
Expand All @@ -266,13 +260,16 @@
;; @desc Foldable function evaluating signatures from a list of { guardian-id: u8, signature: (buff 65) }, returning a list of recovered public-keys
(define-private (batch-check-active-public-keys
(entry { recovered-compressed-public-key: (buff 33), guardian-id: uint })
(acc { active-guardians: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64) }), value: (list 20 (buff 33))}))
(acc {
active-guardians: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64) }),
result: (list 19 (buff 33))
}))
(let ((compressed-public-key (get compressed-public-key (unwrap-panic (element-at? (get active-guardians acc) (get guardian-id entry))))))
(if (and
(is-eq (get recovered-compressed-public-key entry) compressed-public-key)
(is-none (index-of? (get value acc) (get recovered-compressed-public-key entry))))
(is-none (index-of? (get result acc) (get recovered-compressed-public-key entry))))
{
value: (unwrap-panic (as-max-len? (append (get value acc) (get recovered-compressed-public-key entry)) u20)),
result: (unwrap-panic (as-max-len? (append (get result acc) (get recovered-compressed-public-key entry)) u19)),
active-guardians: (get active-guardians acc)
}
acc)))
Expand Down Expand Up @@ -301,13 +298,13 @@
(define-private (is-eth-address-matching-public-key (uncompressed-public-key (buff 64)) (eth-address (buff 20)))
(is-eq (unwrap-panic (slice? (keccak256 uncompressed-public-key) u12 u32)) eth-address))

(define-private (parse-guardian (cue-position uint) (acc { bytes: (buff 8192), result: (list 20 (buff 20))}))
(define-private (parse-guardian (cue-position uint) (acc { bytes: (buff 8192), result: (list 19 (buff 20))}))
(let (
(cursor-address-bytes (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-20 { bytes: (get bytes acc), pos: cue-position })))
)
{
bytes: (get bytes acc),
result: (unwrap-panic (as-max-len? (append (get result acc) (get value cursor-address-bytes)) u20))
result: (unwrap-panic (as-max-len? (append (get result acc) (get value cursor-address-bytes)) u19))
}))

;; @desc Parse and verify payload's VAA
Expand All @@ -325,9 +322,8 @@
ERR_GSU_PARSING_GUARDIAN_LEN))
(guardians-bytes (unwrap! (contract-call? .hk-cursor-v1 read-buff-8192-max (get next cursor-guardians-count) (some (* (get value cursor-guardians-count) u20)))
ERR_GSU_PARSING_GUARDIANS_BYTES))
(guardians-cues (get result (fold is-guardian-cue (get value guardians-bytes) { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) })))
(eth-addresses-init (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u20))) u19)))
(eth-addresses (get result (fold parse-guardian guardians-cues { bytes: (get value guardians-bytes), result: eth-addresses-init }))))
(guardians-cues (get result (fold is-guardian-cue (get value guardians-bytes) { cursor: u0, result: (list) })))
(eth-addresses (get result (fold parse-guardian guardians-cues { bytes: (get value guardians-bytes), result: (list) }))))
;; Ensure that this message was emitted from authorized module
(asserts! (is-eq (get value cursor-module) 0x00000000000000000000000000000000000000000000000000000000436f7265)
ERR_GSU_CHECK_MODULE)
Expand All @@ -343,7 +339,7 @@
;; Good to go!
(ok {
value: {
guardians-eth-addresses: (unwrap-panic (as-max-len? (unwrap-panic (slice? eth-addresses u1 (+ u1 (get value cursor-guardians-count)))) u19)),
guardians-eth-addresses: eth-addresses,
module: (get value cursor-module),
action: (get value cursor-action),
chain: (get value cursor-chain),
Expand All @@ -357,7 +353,7 @@
})))

(define-private (is-guardian-cue (byte (buff 1)) (acc { cursor: uint, result: (list 19 uint) }))
(if (and (is-eq u0 (mod (get cursor acc) u20)) (> (get cursor acc) u0) )
(if (is-eq u0 (mod (get cursor acc) u20))
{
cursor: (+ u1 (get cursor acc)),
result: (unwrap-panic (as-max-len? (append (get result acc) (get cursor acc)) u19)),
Expand Down
23 changes: 22 additions & 1 deletion unit-tests/wormhole/helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fc } from '@fast-check/vitest';
import { tx } from "@hirosystems/clarinet-sdk";
import { Cl, ClarityValue } from "@stacks/transactions";
import { bigintToBuffer } from '../utils/helper';
import * as secp from '@noble/secp256k1';
Expand All @@ -23,7 +24,7 @@ export namespace wormhole {
ethereumAddress: Uint8Array,
}

export const generateGuardianSetKeychain = (count = 19) => {
export const generateGuardianSetKeychain = (count = 19): Guardian[] => {
let keychain = [];
for (let i = 0; i < count; i++) {
let secretKey = secp.utils.randomPrivateKey();
Expand Down Expand Up @@ -356,4 +357,24 @@ export namespace wormhole {

return Buffer.concat(components);
}

export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) {
let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule);
let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload });
let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 });
let vaa = wormhole.serializeVaa(vaaHeader, vaaBody);
let uncompressedPublicKey = [];
for (let guardian of keychain) {
uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey));
}
let result = simnet.mineBlock([
tx.callPublicFn(
contract_name,
`update-guardians-set`,
[Cl.buffer(vaa), Cl.list(uncompressedPublicKey)],
txSenderAddress
),
])[0].result;
return [result, vaaHeader, vaaBody]
}
}
Loading

0 comments on commit 65ae13e

Please sign in to comment.