Skip to content

Commit 6c2bef3

Browse files
author
Edward (Mike's assistant)
committed
fix(tests): align test fixtures with Quasar's compact wire format
The Quasar `#[account]` and `#[instruction]` derive macros use a single "compact" wire layout: a header containing all fixed-size fields and length prefixes grouped together, followed by a tail with all dynamic byte payloads grouped together. The previous tests assumed an interleaved field-by-field layout with u32 length prefixes, which never matched the program code that was already migrated to the compact format. Two consequences flowed from that mismatch: 1. Length prefixes for `String<MAX>` default to a single byte (the second `String` generic argument is the prefix type and its default is `u8`). The tests were using u32 prefixes, so the program rejected the instruction data or wrote wildly wrong account contents. 2. The compact format groups all length prefixes ahead of the data bytes; the tests expected each prefix to immediately precede its own bytes. This commit rewrites the failing test fixtures so they encode and decode the same compact format the programs already produce. The program code itself is not changed — programs are canonical, tests follow. Also removes a stray `rent` sysvar account from the `tokens/pda-mint-authority` `create_mint` test: the program's `CreateMint` accounts struct no longer takes a rent sysvar, so the extra entry was shifting `token_program` and `system_program` to the wrong slots. Projects fixed: basics/account-data basics/close-account basics/favorites basics/realloc basics/rent basics/repository-layout tokens/pda-mint-authority
1 parent 8eff6b6 commit 6c2bef3

7 files changed

Lines changed: 111 additions & 100 deletions

File tree

basics/account-data/quasar/src/tests.rs

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,28 @@ fn empty(address: Pubkey) -> Account {
2020
}
2121
}
2222

23-
/// Build create_address_info instruction data manually.
23+
/// Build the create_address_info instruction data using Quasar's compact
24+
/// wire format: a header containing all fixed fields and length prefixes,
25+
/// followed by a tail with all dynamic byte payloads grouped together.
2426
///
25-
/// Wire format (from reading the #[instruction] codegen):
26-
/// [disc: 1 byte]
27-
/// [ZC struct: house_number u8]
28-
/// [name: u32 LE length prefix + bytes] (String → DynKind::Str with U32 prefix)
29-
/// [street: u32 LE length prefix + bytes]
30-
/// [city: u32 LE length prefix + bytes]
27+
/// Layout:
28+
/// header: [disc: u8 = 0][house_number: u8][name_len: u8][street_len: u8][city_len: u8]
29+
/// tail: [name bytes][street bytes][city bytes]
30+
///
31+
/// `String<50>` defaults to a u8 length prefix because MAX (50) fits in a byte.
3132
fn build_create_instruction_data(name: &str, house_number: u8, street: &str, city: &str) -> Vec<u8> {
32-
let mut data = vec![0u8]; // discriminator = 0
33+
let mut data = Vec::with_capacity(5 + name.len() + street.len() + city.len());
3334

34-
// Fixed ZC struct: house_number
35+
// Header
36+
data.push(0u8); // instruction discriminator
3537
data.push(house_number);
38+
data.push(name.len() as u8);
39+
data.push(street.len() as u8);
40+
data.push(city.len() as u8);
3641

37-
// Dynamic String args with u32 length prefix
38-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
42+
// Tail
3943
data.extend_from_slice(name.as_bytes());
40-
41-
data.extend_from_slice(&(street.len() as u32).to_le_bytes());
4244
data.extend_from_slice(street.as_bytes());
43-
44-
data.extend_from_slice(&(city.len() as u32).to_le_bytes());
4545
data.extend_from_slice(city.as_bytes());
4646

4747
data
@@ -81,34 +81,25 @@ fn test_create_address_info() {
8181
// Verify the account data.
8282
let account = result.account(&address_info).unwrap();
8383

84-
// Onchain layout (from #[account] dynamic codegen):
85-
// [disc: 1 byte = 1]
86-
// [ZC header: house_number u8]
87-
// [name: u8 prefix + bytes] (String<u8, 50> uses u8 prefix)
88-
// [street: u8 prefix + bytes]
89-
// [city: u8 prefix + bytes]
84+
// Onchain layout for a Quasar `#[account]` with dynamic fields uses the
85+
// compact "header then tail" format. Length prefixes are grouped in the
86+
// header, the actual bytes follow in the tail.
87+
// header: [disc: 1][house_number: u8][name_len: u8][street_len: u8][city_len: u8]
88+
// tail: [name bytes][street bytes][city bytes]
89+
// String<50> defaults to a u8 length prefix because MAX (50) fits in a byte.
9090
assert_eq!(account.data[0], 1, "discriminator");
9191
assert_eq!(account.data[1], 42, "house_number");
92-
93-
let mut offset = 2;
94-
95-
// name: u8 prefix + "Alice"
96-
let name_len = account.data[offset] as usize;
97-
offset += 1;
92+
let name_len = account.data[2] as usize;
93+
let street_len = account.data[3] as usize;
94+
let city_len = account.data[4] as usize;
9895
assert_eq!(name_len, 5);
99-
assert_eq!(&account.data[offset..offset + name_len], b"Alice");
100-
offset += name_len;
101-
102-
// street: u8 prefix + "Main Street"
103-
let street_len = account.data[offset] as usize;
104-
offset += 1;
10596
assert_eq!(street_len, 11);
106-
assert_eq!(&account.data[offset..offset + street_len], b"Main Street");
107-
offset += street_len;
108-
109-
// city: u8 prefix + "New York"
110-
let city_len = account.data[offset] as usize;
111-
offset += 1;
11297
assert_eq!(city_len, 8);
113-
assert_eq!(&account.data[offset..offset + city_len], b"New York");
98+
99+
let header_end = 5;
100+
assert_eq!(&account.data[header_end..header_end + name_len], b"Alice");
101+
let street_start = header_end + name_len;
102+
assert_eq!(&account.data[street_start..street_start + street_len], b"Main Street");
103+
let city_start = street_start + street_len;
104+
assert_eq!(&account.data[city_start..city_start + city_len], b"New York");
114105
}

basics/close-account/quasar/src/tests.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ fn empty(address: Pubkey) -> Account {
2020
}
2121
}
2222

23-
/// Build create_user instruction data.
24-
/// Wire format: [disc=0] [name: u32 prefix + bytes]
23+
/// Build create_user instruction data using Quasar's compact wire format
24+
/// (header then tail). `String<50>` defaults to a u8 length prefix.
25+
///
26+
/// header: [disc: u8 = 0][name_len: u8]
27+
/// tail: [name bytes]
2528
fn build_create_instruction(name: &str) -> Vec<u8> {
26-
let mut data = vec![0u8]; // discriminator = 0
27-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
29+
let mut data = Vec::with_capacity(2 + name.len());
30+
data.push(0u8); // discriminator
31+
data.push(name.len() as u8);
2832
data.extend_from_slice(name.as_bytes());
2933
data
3034
}

basics/favorites/quasar/src/tests.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ fn empty(address: Pubkey) -> Account {
2020
}
2121
}
2222

23-
/// Build set_favorites instruction data.
24-
/// Wire format: [disc=0] [ZC: number(u64)] [color: u32 prefix + bytes]
23+
/// Build set_favorites instruction data using Quasar's compact wire format
24+
/// (header then tail). `String<50>` defaults to a u8 length prefix.
25+
///
26+
/// header: [disc: u8 = 0][number: u64 LE][color_len: u8]
27+
/// tail: [color bytes]
2528
fn build_set_favorites(number: u64, color: &str) -> Vec<u8> {
26-
let mut data = vec![0u8]; // discriminator = 0
29+
let mut data = Vec::with_capacity(10 + color.len());
2730

28-
// Fixed ZC args: number (u64, but as Pod it's le bytes)
31+
// Header
32+
data.push(0u8); // discriminator
2933
data.extend_from_slice(&number.to_le_bytes());
34+
data.push(color.len() as u8);
3035

31-
// Dynamic String arg: color with u32 prefix
32-
data.extend_from_slice(&(color.len() as u32).to_le_bytes());
36+
// Tail
3337
data.extend_from_slice(color.as_bytes());
3438

3539
data

basics/realloc/quasar/src/tests.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,25 @@ fn empty(address: Pubkey) -> Account {
2020
}
2121
}
2222

23-
/// Build initialize instruction data.
24-
/// Wire format: [disc=0] [message: u32 prefix + bytes]
23+
/// Build initialize instruction data using Quasar's compact wire format.
24+
/// `String<1024>` defaults to a u8 length prefix (the second `String` generic
25+
/// argument is the prefix type and its default is `u8`).
26+
///
27+
/// header: [disc: u8 = 0][message_len: u8]
28+
/// tail: [message bytes]
2529
fn build_initialize(message: &str) -> Vec<u8> {
26-
let mut data = vec![0u8]; // discriminator = 0
27-
data.extend_from_slice(&(message.len() as u32).to_le_bytes());
30+
let mut data = Vec::with_capacity(2 + message.len());
31+
data.push(0u8); // discriminator
32+
data.push(message.len() as u8);
2833
data.extend_from_slice(message.as_bytes());
2934
data
3035
}
3136

32-
/// Build update instruction data.
33-
/// Wire format: [disc=1] [message: u32 prefix + bytes]
37+
/// Build update instruction data using the same compact wire format.
3438
fn build_update(message: &str) -> Vec<u8> {
35-
let mut data = vec![1u8]; // discriminator = 1
36-
data.extend_from_slice(&(message.len() as u32).to_le_bytes());
39+
let mut data = Vec::with_capacity(2 + message.len());
40+
data.push(1u8); // discriminator
41+
data.push(message.len() as u8);
3742
data.extend_from_slice(message.as_bytes());
3843
data
3944
}
@@ -65,14 +70,13 @@ fn test_initialize() {
6570
let result = svm.process_instruction(&ix, &[signer(payer), empty(message_account)]);
6671
result.assert_success();
6772

68-
// Verify: disc(1) + message (u32 prefix "Hello, World!")
73+
// Verify: disc(1) + message (u8 prefix + bytes)
6974
let account = result.account(&message_account).unwrap();
7075
assert_eq!(account.data[0], 1, "discriminator");
7176

72-
// Default String uses u32 prefix, max 1024
73-
let msg_len = u32::from_le_bytes(account.data[1..5].try_into().unwrap()) as usize;
77+
let msg_len = account.data[1] as usize;
7478
assert_eq!(msg_len, 13);
75-
assert_eq!(&account.data[5..5 + msg_len], b"Hello, World!");
79+
assert_eq!(&account.data[2..2 + msg_len], b"Hello, World!");
7680
}
7781

7882
#[test]

basics/rent/quasar/src/tests.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,22 @@ fn empty(address: Pubkey) -> Account {
2020
}
2121
}
2222

23-
/// Build create_system_account instruction data (discriminator = 0).
24-
/// Wire format: [disc=0] [name: String] [address: String]
25-
/// Both String args are dynamic (u32 length prefix + bytes).
23+
/// Build create_system_account instruction data using Quasar's compact
24+
/// wire format (header then tail). `String<50>` defaults to a u8 length
25+
/// prefix (the second `String` generic argument is the prefix type).
26+
///
27+
/// header: [disc: u8 = 0][name_len: u8][address_len: u8]
28+
/// tail: [name bytes][address bytes]
2629
fn build_create_system_account(name: &str, address: &str) -> Vec<u8> {
27-
let mut data = vec![0u8]; // discriminator = 0
30+
let mut data = Vec::with_capacity(3 + name.len() + address.len());
2831

29-
// Dynamic String: name
30-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
31-
data.extend_from_slice(name.as_bytes());
32+
// Header
33+
data.push(0u8); // discriminator
34+
data.push(name.len() as u8);
35+
data.push(address.len() as u8);
3236

33-
// Dynamic String: address
34-
data.extend_from_slice(&(address.len() as u32).to_le_bytes());
37+
// Tail
38+
data.extend_from_slice(name.as_bytes());
3539
data.extend_from_slice(address.as_bytes());
3640

3741
data

basics/repository-layout/quasar/src/tests.rs

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,59 +10,63 @@ fn signer(address: Pubkey) -> Account {
1010
quasar_svm::token::create_keyed_system_account(&address, 10_000_000_000)
1111
}
1212

13-
/// Build go_on_ride instruction data (discriminator = 0).
14-
/// Wire format: [disc=0] [ZC: height(u32), ticket_count(u32)] [name: String] [ride_name: String]
13+
/// Build go_on_ride instruction data using Quasar's compact wire format
14+
/// (header then tail). `String<50>` defaults to a u8 length prefix.
15+
///
16+
/// header: [disc: u8 = 0][height: u32 LE][ticket_count: u32 LE][name_len: u8][ride_name_len: u8]
17+
/// tail: [name bytes][ride_name bytes]
1518
fn build_go_on_ride(name: &str, height: u32, ticket_count: u32, ride_name: &str) -> Vec<u8> {
16-
let mut data = vec![0u8]; // discriminator = 0
19+
let mut data = Vec::with_capacity(11 + name.len() + ride_name.len());
1720

18-
// Fixed ZC fields: height, ticket_count
21+
// Header
22+
data.push(0u8); // discriminator
1923
data.extend_from_slice(&height.to_le_bytes());
2024
data.extend_from_slice(&ticket_count.to_le_bytes());
25+
data.push(name.len() as u8);
26+
data.push(ride_name.len() as u8);
2127

22-
// Dynamic String: name
23-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
28+
// Tail
2429
data.extend_from_slice(name.as_bytes());
25-
26-
// Dynamic String: ride_name
27-
data.extend_from_slice(&(ride_name.len() as u32).to_le_bytes());
2830
data.extend_from_slice(ride_name.as_bytes());
2931

3032
data
3133
}
3234

33-
/// Build play_game instruction data (discriminator = 1).
34-
/// Wire format: [disc=1] [ZC: ticket_count(u32)] [name: String] [game_name: String]
35+
/// Build play_game instruction data using the same compact wire format.
36+
///
37+
/// header: [disc: u8 = 1][ticket_count: u32 LE][name_len: u8][game_name_len: u8]
38+
/// tail: [name bytes][game_name bytes]
3539
fn build_play_game(name: &str, ticket_count: u32, game_name: &str) -> Vec<u8> {
36-
let mut data = vec![1u8]; // discriminator = 1
40+
let mut data = Vec::with_capacity(7 + name.len() + game_name.len());
3741

38-
// Fixed ZC: ticket_count
42+
// Header
43+
data.push(1u8); // discriminator
3944
data.extend_from_slice(&ticket_count.to_le_bytes());
45+
data.push(name.len() as u8);
46+
data.push(game_name.len() as u8);
4047

41-
// Dynamic String: name
42-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
48+
// Tail
4349
data.extend_from_slice(name.as_bytes());
44-
45-
// Dynamic String: game_name
46-
data.extend_from_slice(&(game_name.len() as u32).to_le_bytes());
4750
data.extend_from_slice(game_name.as_bytes());
4851

4952
data
5053
}
5154

52-
/// Build eat_food instruction data (discriminator = 2).
53-
/// Wire format: [disc=2] [ZC: ticket_count(u32)] [name: String] [food_stand_name: String]
55+
/// Build eat_food instruction data using the same compact wire format.
56+
///
57+
/// header: [disc: u8 = 2][ticket_count: u32 LE][name_len: u8][food_stand_name_len: u8]
58+
/// tail: [name bytes][food_stand_name bytes]
5459
fn build_eat_food(name: &str, ticket_count: u32, food_stand_name: &str) -> Vec<u8> {
55-
let mut data = vec![2u8]; // discriminator = 2
60+
let mut data = Vec::with_capacity(7 + name.len() + food_stand_name.len());
5661

57-
// Fixed ZC: ticket_count
62+
// Header
63+
data.push(2u8); // discriminator
5864
data.extend_from_slice(&ticket_count.to_le_bytes());
65+
data.push(name.len() as u8);
66+
data.push(food_stand_name.len() as u8);
5967

60-
// Dynamic String: name
61-
data.extend_from_slice(&(name.len() as u32).to_le_bytes());
68+
// Tail
6269
data.extend_from_slice(name.as_bytes());
63-
64-
// Dynamic String: food_stand_name
65-
data.extend_from_slice(&(food_stand_name.len() as u32).to_le_bytes());
6670
data.extend_from_slice(food_stand_name.as_bytes());
6771

6872
data

tokens/pda-mint-authority/quasar/src/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,16 @@ fn test_create_mint() {
7575
let (mint_pda, _) = Pubkey::find_program_address(&[b"mint"], &crate::ID);
7676
let token_program = quasar_svm::SPL_TOKEN_PROGRAM_ID;
7777
let system_program = quasar_svm::system_program::ID;
78-
let rent = quasar_svm::solana_sdk_ids::sysvar::rent::ID;
7978

8079
let data = build_create_mint_data(9);
8180

81+
// Account order matches the `CreateMint` Accounts struct:
82+
// payer, mint, token_program, system_program.
8283
let instruction = Instruction {
8384
program_id: crate::ID,
8485
accounts: vec![
8586
solana_instruction::AccountMeta::new(payer.into(), true),
8687
solana_instruction::AccountMeta::new(mint_pda.into(), false),
87-
solana_instruction::AccountMeta::new_readonly(rent.into(), false),
8888
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
8989
solana_instruction::AccountMeta::new_readonly(system_program.into(), false),
9090
],

0 commit comments

Comments
 (0)