Skip to content

Commit 131a804

Browse files
committed
test(crypto/solana): add Message and ComputeBudgetProgram tests
- MessageTest: encode/decode roundtrip, header construction from accounts, account sorting (signers before non-signers, writable before readonly), multiple instructions - ComputeBudgetProgramTest: SetComputeUnitPrice and SetComputeUnitLimit encode/decode roundtrips, encoding format validation
1 parent 9721154 commit 131a804

2 files changed

Lines changed: 281 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package com.getcode.solana
2+
3+
import com.getcode.solana.keys.AccountMeta
4+
import com.getcode.solana.keys.Hash
5+
import com.getcode.solana.keys.PublicKey
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
import kotlin.test.assertNotNull
9+
import kotlin.test.assertNull
10+
11+
class MessageTest {
12+
13+
private fun testKey(seed: Int): PublicKey =
14+
PublicKey(ByteArray(32) { seed.toByte() }.toList())
15+
16+
private fun testHash(): Hash =
17+
Hash(ByteArray(32) { 0xAB.toByte() }.toList())
18+
19+
// --- newInstance from accounts ---
20+
21+
@Test
22+
fun constructsHeaderFromAccounts() {
23+
val payer = AccountMeta.payer(testKey(1))
24+
val writable = AccountMeta.writable(testKey(2))
25+
val readonly = AccountMeta.readonly(testKey(3))
26+
val program = AccountMeta(testKey(4), isSigner = false, isWritable = false, isPayer = false, isProgram = true)
27+
28+
val message = Message.newInstance(
29+
accounts = listOf(payer, writable, readonly, program),
30+
recentBlockhash = testHash(),
31+
instructions = emptyList()
32+
)
33+
34+
// payer is the only signer
35+
assertEquals(1, message.header.requiredSignatures)
36+
}
37+
38+
@Test
39+
fun payerIsFirstAccount() {
40+
val payer = AccountMeta.payer(testKey(1))
41+
val writable = AccountMeta.writable(testKey(2))
42+
43+
val message = Message.newInstance(
44+
accounts = listOf(writable, payer),
45+
recentBlockhash = testHash(),
46+
instructions = emptyList()
47+
)
48+
49+
assertEquals(testKey(1), message.accounts.first().publicKey)
50+
}
51+
52+
// --- encode / decode roundtrip ---
53+
54+
@Test
55+
fun encodeDecodeRoundtripNoInstructions() {
56+
val payer = AccountMeta.payer(testKey(1))
57+
58+
val original = Message.newInstance(
59+
accounts = listOf(payer),
60+
recentBlockhash = testHash(),
61+
instructions = emptyList()
62+
)
63+
64+
val encoded = original.encode()
65+
val decoded = Message.newInstance(encoded.toList())
66+
67+
assertNotNull(decoded)
68+
assertEquals(original.header, decoded.header)
69+
assertEquals(original.recentBlockhash, decoded.recentBlockhash)
70+
assertEquals(original.accounts.size, decoded.accounts.size)
71+
assertEquals(original.instructions.size, decoded.instructions.size)
72+
}
73+
74+
@Test
75+
fun encodeDecodeRoundtripWithInstruction() {
76+
val payer = AccountMeta.payer(testKey(1))
77+
val dest = AccountMeta.writable(testKey(2))
78+
val program = AccountMeta(testKey(3), isSigner = false, isWritable = false, isPayer = false, isProgram = true)
79+
80+
val instruction = Instruction(
81+
program = testKey(3),
82+
accounts = listOf(payer, dest),
83+
data = listOf(0x01, 0x02, 0x03)
84+
)
85+
86+
val original = Message.newInstance(
87+
accounts = listOf(payer, dest, program),
88+
recentBlockhash = testHash(),
89+
instructions = listOf(instruction)
90+
)
91+
92+
val encoded = original.encode()
93+
val decoded = Message.newInstance(encoded.toList())
94+
95+
assertNotNull(decoded)
96+
assertEquals(original.header, decoded.header)
97+
assertEquals(1, decoded.instructions.size)
98+
assertEquals(listOf<Byte>(0x01, 0x02, 0x03), decoded.instructions[0].data)
99+
}
100+
101+
@Test
102+
fun encodeDecodePreservesBlockhash() {
103+
val hash = testHash()
104+
val payer = AccountMeta.payer(testKey(1))
105+
106+
val original = Message.newInstance(
107+
accounts = listOf(payer),
108+
recentBlockhash = hash,
109+
instructions = emptyList()
110+
)
111+
112+
val decoded = Message.newInstance(original.encode().toList())
113+
assertNotNull(decoded)
114+
assertEquals(hash, decoded.recentBlockhash)
115+
}
116+
117+
// --- account sorting ---
118+
119+
@Test
120+
fun signersBeforeNonSigners() {
121+
val payer = AccountMeta.payer(testKey(1))
122+
val signer = AccountMeta.writable(testKey(2), signer = true)
123+
val nonsigner = AccountMeta.writable(testKey(3))
124+
125+
val message = Message.newInstance(
126+
accounts = listOf(nonsigner, signer, payer),
127+
recentBlockhash = testHash(),
128+
instructions = emptyList()
129+
)
130+
131+
// First two should be signers
132+
assert(message.accounts[0].isSigner)
133+
assert(message.accounts[1].isSigner)
134+
assert(!message.accounts[2].isSigner)
135+
}
136+
137+
@Test
138+
fun writableBeforeReadonly() {
139+
val payer = AccountMeta.payer(testKey(1))
140+
val readonly = AccountMeta.readonly(testKey(2))
141+
val writable = AccountMeta.writable(testKey(3))
142+
143+
val message = Message.newInstance(
144+
accounts = listOf(readonly, writable, payer),
145+
recentBlockhash = testHash(),
146+
instructions = emptyList()
147+
)
148+
149+
// Payer first (writable signer), then writable non-signer, then readonly
150+
assert(message.accounts[0].isWritable)
151+
}
152+
153+
@Test
154+
fun multipleInstructionsRoundtrip() {
155+
val payer = AccountMeta.payer(testKey(1))
156+
val acc = AccountMeta.writable(testKey(2))
157+
val program = AccountMeta(testKey(3), isSigner = false, isWritable = false, isPayer = false, isProgram = true)
158+
159+
val ix1 = Instruction(
160+
program = testKey(3),
161+
accounts = listOf(payer),
162+
data = listOf(0x01)
163+
)
164+
val ix2 = Instruction(
165+
program = testKey(3),
166+
accounts = listOf(acc),
167+
data = listOf(0x02, 0x03)
168+
)
169+
170+
val original = Message.newInstance(
171+
accounts = listOf(payer, acc, program),
172+
recentBlockhash = testHash(),
173+
instructions = listOf(ix1, ix2)
174+
)
175+
176+
val decoded = Message.newInstance(original.encode().toList())
177+
assertNotNull(decoded)
178+
assertEquals(2, decoded.instructions.size)
179+
assertEquals(listOf<Byte>(0x01), decoded.instructions[0].data)
180+
assertEquals(listOf<Byte>(0x02, 0x03), decoded.instructions[1].data)
181+
}
182+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.getcode.solana.instructions.programs
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
class ComputeBudgetProgramTest {
7+
8+
// --- SetComputeUnitPrice ---
9+
10+
@Test
11+
fun unitPriceEncodeDecodeRoundtrip() {
12+
val original = ComputeBudgetProgram_SetComputeUnitPrice(microLamports = 50000L, bump = 0)
13+
val instruction = original.instruction()
14+
val decoded = ComputeBudgetProgram_SetComputeUnitPrice.newInstance(instruction)
15+
assertEquals(50000L, decoded.microLamports)
16+
}
17+
18+
@Test
19+
fun unitPriceZero() {
20+
val original = ComputeBudgetProgram_SetComputeUnitPrice(microLamports = 0L, bump = 0)
21+
val instruction = original.instruction()
22+
val decoded = ComputeBudgetProgram_SetComputeUnitPrice.newInstance(instruction)
23+
assertEquals(0L, decoded.microLamports)
24+
}
25+
26+
@Test
27+
fun unitPriceLargeValue() {
28+
val original = ComputeBudgetProgram_SetComputeUnitPrice(microLamports = 1_000_000_000L, bump = 0)
29+
val instruction = original.instruction()
30+
val decoded = ComputeBudgetProgram_SetComputeUnitPrice.newInstance(instruction)
31+
assertEquals(1_000_000_000L, decoded.microLamports)
32+
}
33+
34+
@Test
35+
fun unitPriceEncodeStartsWithCommandByte() {
36+
val price = ComputeBudgetProgram_SetComputeUnitPrice(microLamports = 100, bump = 0)
37+
val encoded = price.encode()
38+
assertEquals(ComputeBudgetProgram.Command.setComputeUnitPrice.ordinal.toByte(), encoded[0])
39+
}
40+
41+
@Test
42+
fun unitPriceEncodeHasCorrectLength() {
43+
val price = ComputeBudgetProgram_SetComputeUnitPrice(microLamports = 100, bump = 0)
44+
val encoded = price.encode()
45+
// 1 byte command + 8 bytes Long
46+
assertEquals(9, encoded.size)
47+
}
48+
49+
// --- SetComputeUnitLimit ---
50+
51+
@Test
52+
fun unitLimitEncodeDecodeRoundtrip() {
53+
val original = ComputeBudgetProgram_SetComputeUnitLimit(limit = 200_000, bump = 0)
54+
val instruction = original.instruction()
55+
val decoded = ComputeBudgetProgram_SetComputeUnitLimit.newInstance(instruction)
56+
assertEquals(200_000, decoded.limit)
57+
}
58+
59+
@Test
60+
fun unitLimitZero() {
61+
val original = ComputeBudgetProgram_SetComputeUnitLimit(limit = 0, bump = 0)
62+
val instruction = original.instruction()
63+
val decoded = ComputeBudgetProgram_SetComputeUnitLimit.newInstance(instruction)
64+
assertEquals(0, decoded.limit)
65+
}
66+
67+
@Test
68+
fun unitLimitMaxValue() {
69+
val original = ComputeBudgetProgram_SetComputeUnitLimit(limit = 1_400_000, bump = 0)
70+
val instruction = original.instruction()
71+
val decoded = ComputeBudgetProgram_SetComputeUnitLimit.newInstance(instruction)
72+
assertEquals(1_400_000, decoded.limit)
73+
}
74+
75+
@Test
76+
fun unitLimitEncodeStartsWithCommandByte() {
77+
val limit = ComputeBudgetProgram_SetComputeUnitLimit(limit = 100, bump = 0)
78+
val encoded = limit.encode()
79+
assertEquals(ComputeBudgetProgram.Command.setComputeUnitLimit.ordinal.toByte(), encoded[0])
80+
}
81+
82+
@Test
83+
fun unitLimitEncodeHasCorrectLength() {
84+
val limit = ComputeBudgetProgram_SetComputeUnitLimit(limit = 100, bump = 0)
85+
val encoded = limit.encode()
86+
// 1 byte command + 4 bytes Int
87+
assertEquals(5, encoded.size)
88+
}
89+
90+
// --- Command enum ---
91+
92+
@Test
93+
fun commandValues() {
94+
assertEquals(0.toByte(), ComputeBudgetProgram.Command.requestUnits.value)
95+
assertEquals(1.toByte(), ComputeBudgetProgram.Command.requestHeapFrame.value)
96+
assertEquals(2.toByte(), ComputeBudgetProgram.Command.setComputeUnitLimit.value)
97+
assertEquals(3.toByte(), ComputeBudgetProgram.Command.setComputeUnitPrice.value)
98+
}
99+
}

0 commit comments

Comments
 (0)