Skip to content

Commit 69f2946

Browse files
committed
fix: remove num_trusted_signers, remove max signers functionality, add getters for private struct fields, improve tests
1 parent 333cd24 commit 69f2946

File tree

2 files changed

+145
-87
lines changed

2 files changed

+145
-87
lines changed

lazer/contracts/aptos/sources/pyth_lazer.move

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,63 +9,103 @@ module pyth_lazer::pyth_lazer {
99
/// Error codes
1010
const ENO_PERMISSIONS: u64 = 1;
1111
const EINVALID_SIGNER: u64 = 2;
12-
const ENO_SPACE: u64 = 3;
1312
const ENO_SUCH_PUBKEY: u64 = 4;
1413
const EINVALID_SIGNATURE: u64 = 5;
1514
const EINSUFFICIENT_FEE: u64 = 6;
1615

1716
/// Constants
18-
const MAX_NUM_TRUSTED_SIGNERS: u8 = 2;
1917
const ED25519_PUBLIC_KEY_LENGTH: u64 = 32;
2018

2119
/// Stores information about a trusted signer including their public key and expiration
22-
struct TrustedSignerInfo has store, drop {
23-
pubkey: vector<u8>, // Ed25519 public key (32 bytes)
24-
expires_at: u64, // Unix timestamp
20+
struct TrustedSignerInfo has store, drop, copy {
21+
pubkey: vector<u8>, // Ed25519 public key (32 bytes)
22+
expires_at: u64 // Unix timestamp
2523
}
2624

2725
/// Main storage for the Lazer contract
2826
struct Storage has key {
2927
top_authority: address,
3028
treasury: address,
3129
single_update_fee: u64,
32-
num_trusted_signers: u8,
33-
trusted_signers: vector<TrustedSignerInfo>,
30+
trusted_signers: vector<TrustedSignerInfo>
3431
}
3532

3633
/// Events
3734
struct TrustedSignerUpdateEvent has drop, store {
3835
pubkey: vector<u8>,
39-
expires_at: u64,
36+
expires_at: u64
4037
}
4138

42-
/// Initialize the Lazer contract with top authority and treasury
39+
/// Initialize the Lazer contract with top authority and treasury. One-time operation.
4340
public entry fun initialize(
4441
account: &signer,
4542
top_authority: address,
4643
treasury: address,
4744
) {
45+
// Initialize must be called by the contract account
46+
assert!(signer::address_of(account) == @pyth_lazer, ENO_PERMISSIONS);
4847
let storage = Storage {
4948
top_authority,
5049
treasury,
5150
single_update_fee: 1, // Nominal fee
52-
num_trusted_signers: 0,
53-
trusted_signers: vector::empty(),
51+
trusted_signers: vector::empty()
5452
};
53+
// Can only be called once. If storage already exists in @pyth_lazer,
54+
// this operation will fail (one-time initialization).
5555
move_to(account, storage);
5656
}
5757

58+
/// Verify a message signature and collect fee
59+
public entry fun verify_message(
60+
account: &signer,
61+
message: vector<u8>,
62+
signature: vector<u8>,
63+
trusted_signer: vector<u8>
64+
) acquires Storage {
65+
let storage = borrow_global<Storage>(@pyth_lazer);
66+
67+
// Verify fee payment
68+
assert!(
69+
coin::balance<AptosCoin>(signer::address_of(account))
70+
>= storage.single_update_fee,
71+
EINSUFFICIENT_FEE
72+
);
73+
coin::transfer<AptosCoin>(account, storage.treasury, storage.single_update_fee);
74+
75+
// Verify signer is trusted and not expired
76+
let i = 0;
77+
let valid = false;
78+
while (i < storage.trusted_signers.length()) {
79+
let signer_info = vector::borrow(&storage.trusted_signers, (i as u64));
80+
if (signer_info.pubkey == trusted_signer
81+
&& signer_info.expires_at > timestamp::now_seconds()) {
82+
valid = true;
83+
break
84+
};
85+
i = i + 1;
86+
};
87+
assert!(valid, EINVALID_SIGNER);
88+
89+
// Verify signature
90+
let sig = ed25519::new_signature_from_bytes(signature);
91+
let pk = ed25519::new_unvalidated_public_key_from_bytes(trusted_signer);
92+
assert!(
93+
ed25519::signature_verify_strict(&sig, &pk, message),
94+
EINVALID_SIGNATURE
95+
);
96+
}
5897
/// Upsert a trusted signer's information or remove them
5998
public entry fun update_trusted_signer(
60-
account: &signer,
61-
trusted_signer: vector<u8>,
62-
expires_at: u64,
99+
account: &signer, trusted_signer: vector<u8>, expires_at: u64
63100
) acquires Storage {
64101
let storage = borrow_global_mut<Storage>(@pyth_lazer);
65102
assert!(signer::address_of(account) == storage.top_authority, ENO_PERMISSIONS);
66-
assert!(vector::length(&trusted_signer) == ED25519_PUBLIC_KEY_LENGTH, EINVALID_SIGNER);
103+
assert!(
104+
vector::length(&trusted_signer) == ED25519_PUBLIC_KEY_LENGTH,
105+
EINVALID_SIGNER
106+
);
67107

68-
let num_signers = storage.num_trusted_signers;
108+
let num_signers = storage.trusted_signers.length();
69109
let i = 0;
70110
let found = false;
71111

@@ -82,51 +122,33 @@ module pyth_lazer::pyth_lazer {
82122
// Remove signer
83123
assert!(found, ENO_SUCH_PUBKEY);
84124
vector::remove(&mut storage.trusted_signers, (i as u64));
85-
storage.num_trusted_signers = storage.num_trusted_signers - 1;
86125
} else if (found) {
87126
// Update existing signer
88127
let signer_info = vector::borrow_mut(&mut storage.trusted_signers, (i as u64));
89128
signer_info.expires_at = expires_at;
90129
} else {
91130
// Add new signer
92-
assert!(storage.num_trusted_signers < MAX_NUM_TRUSTED_SIGNERS, ENO_SPACE);
93-
vector::push_back(&mut storage.trusted_signers, TrustedSignerInfo {
94-
pubkey: trusted_signer,
95-
expires_at,
96-
});
97-
storage.num_trusted_signers = storage.num_trusted_signers + 1;
131+
vector::push_back(
132+
&mut storage.trusted_signers,
133+
TrustedSignerInfo { pubkey: trusted_signer, expires_at }
134+
);
98135
};
99136
}
100137

101-
/// Verify a message signature and collect fee
102-
public entry fun verify_message(
103-
account: &signer,
104-
message: vector<u8>,
105-
signature: vector<u8>,
106-
public_key: vector<u8>,
107-
) acquires Storage {
138+
/// Returns the list of trusted signers
139+
public fun get_trusted_signers(): vector<TrustedSignerInfo> acquires Storage {
108140
let storage = borrow_global<Storage>(@pyth_lazer);
141+
storage.trusted_signers
142+
}
143+
/// Signer pubkey getter
144+
public fun get_signer_pubkey(info: &TrustedSignerInfo): vector<u8> {
145+
info.pubkey
146+
}
109147

110-
// Verify fee payment
111-
assert!(coin::balance<AptosCoin>(signer::address_of(account)) >= storage.single_update_fee, EINSUFFICIENT_FEE);
112-
coin::transfer<AptosCoin>(account, storage.treasury, storage.single_update_fee);
148+
/// Signer expiry getter
149+
public fun get_signer_expires_at(info: &TrustedSignerInfo): u64 {
150+
info.expires_at
151+
}
113152

114-
// Verify signer is trusted and not expired
115-
let i = 0;
116-
let valid = false;
117-
while (i < storage.num_trusted_signers) {
118-
let signer_info = vector::borrow(&storage.trusted_signers, (i as u64));
119-
if (signer_info.pubkey == public_key && signer_info.expires_at > timestamp::now_seconds()) {
120-
valid = true;
121-
break
122-
};
123-
i = i + 1;
124-
};
125-
assert!(valid, EINVALID_SIGNER);
126153

127-
// Verify signature
128-
let sig = ed25519::new_signature_from_bytes(signature);
129-
let pk = ed25519::new_unvalidated_public_key_from_bytes(public_key);
130-
assert!(ed25519::signature_verify_strict(&sig, &pk, message), EINVALID_SIGNATURE);
131-
}
132154
}

lazer/contracts/aptos/tests/pyth_lazer_tests.move

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
#[test_only]
22
module pyth_lazer::pyth_lazer_tests {
33
use std::signer;
4+
use std::vector;
45
use aptos_framework::account;
56
use aptos_framework::coin;
67
use aptos_framework::timestamp;
78
use aptos_framework::aptos_coin::AptosCoin;
89
use aptos_std::ed25519;
9-
use pyth_lazer::pyth_lazer;
10+
use pyth_lazer::pyth_lazer::{Self, EINVALID_SIGNER, EINSUFFICIENT_FEE};
1011

1112
// Test accounts
12-
const TOP_AUTHORITY: address = @0x3374049c3b46a907ff2fc6b62af51975fb9dc572b7e73eb1b255ed5edcd7cee0;
13+
const TOP_AUTHORITY: address =
14+
@0x3374049c3b46a907ff2fc6b62af51975fb9dc572b7e73eb1b255ed5edcd7cee0;
1315
const TREASURY: address = @0x456;
1416
const USER: address = @0x789;
1517

@@ -20,13 +22,14 @@ module pyth_lazer::pyth_lazer_tests {
2022

2123
#[test_only]
2224
fun setup_aptos_coin(framework: &signer): coin::MintCapability<AptosCoin> {
23-
let (burn_cap, freeze_cap, mint_cap) = coin::initialize<AptosCoin>(
24-
framework,
25-
std::string::utf8(b"Aptos Coin"),
26-
std::string::utf8(b"APT"),
27-
8,
28-
false,
29-
);
25+
let (burn_cap, freeze_cap, mint_cap) =
26+
coin::initialize<AptosCoin>(
27+
framework,
28+
std::string::utf8(b"Aptos Coin"),
29+
std::string::utf8(b"APT"),
30+
8,
31+
false
32+
);
3033
coin::destroy_burn_cap(burn_cap);
3134
coin::destroy_freeze_cap(freeze_cap);
3235
mint_cap
@@ -69,7 +72,7 @@ module pyth_lazer::pyth_lazer_tests {
6972
}
7073

7174
#[test]
72-
fun test_verify_message_success() {
75+
fun test_verify_message_succeeds() {
7376
let (top_authority, _treasury, user) = setup();
7477

7578
// Add a valid signer
@@ -79,47 +82,70 @@ module pyth_lazer::pyth_lazer_tests {
7982
// Create a valid ed25519 signature
8083
let signature = ed25519::new_signature_from_bytes(TEST_SIGNATURE);
8184
let pubkey = ed25519::new_unvalidated_public_key_from_bytes(TEST_PUBKEY);
82-
assert!(ed25519::signature_verify_strict(&signature, &pubkey, TEST_MESSAGE), 0);
85+
assert!(
86+
ed25519::signature_verify_strict(&signature, &pubkey, TEST_MESSAGE),
87+
0
88+
);
8389

8490
// This should succeed as we have a valid signer and sufficient fee
85-
pyth_lazer::verify_message(&user, TEST_MESSAGE, TEST_SIGNATURE, TEST_PUBKEY);
86-
}
87-
88-
#[test]
89-
fun test_update_add_signer() {
90-
let (top_authority, _treasury, _) = setup();
91-
92-
// Add signer
93-
let expires_at = timestamp::now_seconds() + 1000;
94-
pyth_lazer::update_trusted_signer(&top_authority, TEST_PUBKEY, expires_at);
95-
96-
// Update signer
97-
let new_expires_at = timestamp::now_seconds() + 2000;
98-
pyth_lazer::update_trusted_signer(&top_authority, TEST_PUBKEY, new_expires_at);
99-
100-
// Remove signer
101-
pyth_lazer::update_trusted_signer(&top_authority, TEST_PUBKEY, 0);
91+
pyth_lazer::verify_message(
92+
&user,
93+
TEST_MESSAGE,
94+
TEST_SIGNATURE,
95+
TEST_PUBKEY
96+
);
10297
}
10398

10499
#[test]
105-
#[expected_failure(abort_code = pyth_lazer::ENO_SPACE)]
106-
fun test_max_signers() {
100+
fun test_add_update_remove_signers_succeeds() {
107101
let (top_authority, _treasury, _) = setup();
108102

103+
// Add multiple signers
109104
let expires_at = timestamp::now_seconds() + 1000;
110105
let pubkey1 = x"1111111111111111111111111111111111111111111111111111111111111111";
111106
let pubkey2 = x"2222222222222222222222222222222222222222222222222222222222222222";
112107
let pubkey3 = x"3333333333333333333333333333333333333333333333333333333333333333";
113108

114109
pyth_lazer::update_trusted_signer(&top_authority, pubkey1, expires_at);
115110
pyth_lazer::update_trusted_signer(&top_authority, pubkey2, expires_at);
116-
// This should fail as we already have 2 signers
117111
pyth_lazer::update_trusted_signer(&top_authority, pubkey3, expires_at);
112+
113+
// Verify signers were added
114+
let trusted_signers = pyth_lazer::get_trusted_signers();
115+
assert!(vector::length(&trusted_signers) == 3, 0);
116+
117+
// Verify first signer
118+
let signer_info = vector::borrow(&trusted_signers, 0);
119+
let signer_pubkey = pyth_lazer::get_signer_pubkey(signer_info);
120+
let signer_expires_at = pyth_lazer::get_signer_expires_at(signer_info);
121+
assert!(signer_pubkey == pubkey1, 0);
122+
assert!(signer_expires_at == expires_at, 0);
123+
124+
// Update second signer
125+
let new_expires_at = timestamp::now_seconds() + 2000;
126+
pyth_lazer::update_trusted_signer(&top_authority, pubkey2, new_expires_at);
127+
128+
// Verify second signer was updated
129+
let trusted_signers = pyth_lazer::get_trusted_signers();
130+
let signer_info = vector::borrow(&trusted_signers, 1);
131+
let signer_pubkey = pyth_lazer::get_signer_pubkey(signer_info);
132+
let signer_expires_at = pyth_lazer::get_signer_expires_at(signer_info);
133+
assert!(signer_pubkey == pubkey2, 0);
134+
assert!(signer_expires_at == new_expires_at, 0);
135+
136+
// Remove all signers
137+
pyth_lazer::update_trusted_signer(&top_authority, pubkey1, 0);
138+
pyth_lazer::update_trusted_signer(&top_authority, pubkey2, 0);
139+
pyth_lazer::update_trusted_signer(&top_authority, pubkey3, 0);
140+
141+
// Verify all signers were removed
142+
let trusted_signers = pyth_lazer::get_trusted_signers();
143+
assert!(vector::length(&trusted_signers) == 0, 0);
118144
}
119145

120146
#[test]
121-
#[expected_failure(abort_code = pyth_lazer::EINVALID_SIGNER)]
122-
fun test_expired_signer() {
147+
#[expected_failure(abort_code = EINVALID_SIGNER)]
148+
fun test_expired_signer_throws_error() {
123149
let (top_authority, _treasury, user) = setup();
124150

125151
// Add signer that expires in 1000 seconds
@@ -130,19 +156,29 @@ module pyth_lazer::pyth_lazer_tests {
130156
timestamp::fast_forward_seconds(2000);
131157

132158
// This should fail as the signer is expired
133-
pyth_lazer::verify_message(&user, TEST_MESSAGE, TEST_SIGNATURE, TEST_PUBKEY);
159+
pyth_lazer::verify_message(
160+
&user,
161+
TEST_MESSAGE,
162+
TEST_SIGNATURE,
163+
TEST_PUBKEY
164+
);
134165
}
135166

136167
#[test]
137-
#[expected_failure(abort_code = pyth_lazer::EINSUFFICIENT_FEE)]
138-
fun test_insufficient_fee() {
168+
#[expected_failure(abort_code = EINSUFFICIENT_FEE)]
169+
fun test_insufficient_fee_throws_error() {
139170
let (_top_authority, _treasury, user) = setup();
140171

141172
// Drain user's balance
142173
let user_balance = coin::balance<AptosCoin>(signer::address_of(&user));
143174
coin::transfer<AptosCoin>(&user, TREASURY, user_balance);
144175

145176
// This should fail due to insufficient fee
146-
pyth_lazer::verify_message(&user, TEST_MESSAGE, TEST_SIGNATURE, TEST_PUBKEY);
177+
pyth_lazer::verify_message(
178+
&user,
179+
TEST_MESSAGE,
180+
TEST_SIGNATURE,
181+
TEST_PUBKEY
182+
);
147183
}
148184
}

0 commit comments

Comments
 (0)