@5ive-tech/sdk 1.1.10 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/FiveSDK.d.ts +3 -4
- package/dist/FiveSDK.js +47 -5
- package/dist/accounts/index.js +3 -2
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +5 -5
- package/dist/bin/gen-types.js +0 -0
- package/dist/compiler/BytecodeCompiler.d.ts +1 -7
- package/dist/compiler/BytecodeCompiler.js +105 -44
- package/dist/config/ProgramIdResolver.d.ts +1 -6
- package/dist/config/ProgramIdResolver.js +11 -16
- package/dist/config/VmClusterConfigResolver.d.ts +27 -0
- package/dist/config/VmClusterConfigResolver.js +111 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/modules/accounts.js +7 -1
- package/dist/modules/admin.js +25 -12
- package/dist/modules/deploy.js +62 -23
- package/dist/modules/execute.js +71 -37
- package/dist/modules/vm-state.js +6 -1
- package/dist/program/FunctionBuilder.d.ts +8 -0
- package/dist/program/FunctionBuilder.js +18 -5
- package/dist/project/config.js +0 -1
- package/dist/testing/TestRunner.js +6 -1
- package/dist/types.d.ts +19 -2
- package/dist/utils/abi.d.ts +4 -0
- package/dist/utils/abi.js +3 -0
- package/dist/utils/transaction.d.ts +22 -0
- package/dist/utils/transaction.js +94 -12
- package/dist/wasm/compiler/CompilationLogic.d.ts +0 -5
- package/dist/wasm/compiler/CompilationLogic.js +45 -163
- package/dist/wasm/compiler/FiveCompiler.d.ts +0 -5
- package/dist/wasm/compiler/FiveCompiler.js +0 -6
- package/dist/wasm/compiler/utils.d.ts +34 -1
- package/dist/wasm/compiler/utils.js +223 -5
- package/package.json +4 -3
package/dist/modules/admin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validator } from "../validation/index.js";
|
|
2
2
|
import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
|
|
3
|
+
import { confirmTransactionRobust } from "../utils/transaction.js";
|
|
3
4
|
import bs58 from "bs58";
|
|
4
5
|
const VM_STATE_SEED = Buffer.from("vm_state", "utf8");
|
|
5
6
|
const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
|
|
@@ -205,9 +206,12 @@ export async function initializeVmStateOnSolana(connection, authorityKeypair, op
|
|
|
205
206
|
preflightCommitment: "confirmed",
|
|
206
207
|
maxRetries: options.maxRetries || 3,
|
|
207
208
|
});
|
|
208
|
-
const confirmation = await connection
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
210
|
+
commitment: "finalized",
|
|
211
|
+
timeoutMs: 120000,
|
|
212
|
+
});
|
|
213
|
+
if (!confirmation.success) {
|
|
214
|
+
return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: confirmation.error || "confirmation failed" };
|
|
211
215
|
}
|
|
212
216
|
return { success: true, transactionId: signature, vmStateAccount: generated.vmStateAccount };
|
|
213
217
|
}
|
|
@@ -235,9 +239,12 @@ export async function setFeesOnSolana(connection, authorityKeypair, deployFeeLam
|
|
|
235
239
|
preflightCommitment: "confirmed",
|
|
236
240
|
maxRetries: options.maxRetries || 3,
|
|
237
241
|
});
|
|
238
|
-
const confirmation = await connection
|
|
239
|
-
|
|
240
|
-
|
|
242
|
+
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
243
|
+
commitment: "finalized",
|
|
244
|
+
timeoutMs: 120000,
|
|
245
|
+
});
|
|
246
|
+
if (!confirmation.success) {
|
|
247
|
+
return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: confirmation.error || "confirmation failed" };
|
|
241
248
|
}
|
|
242
249
|
return { success: true, transactionId: signature, vmStateAccount: generated.vmStateAccount };
|
|
243
250
|
}
|
|
@@ -266,14 +273,17 @@ export async function initFeeVaultOnSolana(connection, payerKeypair, shardIndex,
|
|
|
266
273
|
preflightCommitment: "confirmed",
|
|
267
274
|
maxRetries: options.maxRetries || 3,
|
|
268
275
|
});
|
|
269
|
-
const confirmation = await connection
|
|
270
|
-
|
|
276
|
+
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
277
|
+
commitment: "finalized",
|
|
278
|
+
timeoutMs: 120000,
|
|
279
|
+
});
|
|
280
|
+
if (!confirmation.success) {
|
|
271
281
|
return {
|
|
272
282
|
success: false,
|
|
273
283
|
transactionId: signature,
|
|
274
284
|
vmStateAccount: generated.vmStateAccount,
|
|
275
285
|
feeVaultAccount: generated.feeVaultAccount,
|
|
276
|
-
error:
|
|
286
|
+
error: confirmation.error || "confirmation failed",
|
|
277
287
|
};
|
|
278
288
|
}
|
|
279
289
|
return {
|
|
@@ -308,14 +318,17 @@ export async function withdrawScriptFeesOnSolana(connection, authorityKeypair, r
|
|
|
308
318
|
preflightCommitment: "confirmed",
|
|
309
319
|
maxRetries: options.maxRetries || 3,
|
|
310
320
|
});
|
|
311
|
-
const confirmation = await connection
|
|
312
|
-
|
|
321
|
+
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
322
|
+
commitment: "finalized",
|
|
323
|
+
timeoutMs: 120000,
|
|
324
|
+
});
|
|
325
|
+
if (!confirmation.success) {
|
|
313
326
|
return {
|
|
314
327
|
success: false,
|
|
315
328
|
transactionId: signature,
|
|
316
329
|
vmStateAccount: generated.vmStateAccount,
|
|
317
330
|
feeVaultAccount: generated.feeVaultAccount,
|
|
318
|
-
error:
|
|
331
|
+
error: confirmation.error || "confirmation failed",
|
|
319
332
|
};
|
|
320
333
|
}
|
|
321
334
|
return {
|
package/dist/modules/deploy.js
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import { PDAUtils, RentCalculator } from "../crypto/index.js";
|
|
2
2
|
import { validator, Validators } from "../validation/index.js";
|
|
3
3
|
import { calculateDeployFee } from "./fees.js";
|
|
4
|
-
import { pollForConfirmation } from "../utils/transaction.js";
|
|
4
|
+
import { confirmTransactionRobust, getAccountInfoWithRetry, pollForConfirmation, SDK_COMMITMENTS, } from "../utils/transaction.js";
|
|
5
5
|
import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
|
|
6
|
-
|
|
6
|
+
import { VmClusterConfigResolver } from "../config/VmClusterConfigResolver.js";
|
|
7
|
+
const DEFAULT_FEE_VAULT_SHARD_COUNT = (() => {
|
|
8
|
+
try {
|
|
9
|
+
return VmClusterConfigResolver.loadClusterConfig().feeVaultShardCount;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 2;
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
7
15
|
const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
|
|
8
16
|
0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
|
|
9
17
|
0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
|
|
10
18
|
]);
|
|
19
|
+
function clampShardCount(rawCount) {
|
|
20
|
+
const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
21
|
+
return Math.max(1, Math.min(DEFAULT_FEE_VAULT_SHARD_COUNT, normalized));
|
|
22
|
+
}
|
|
11
23
|
async function readVMStateFeeConfig(connection, vmStateAddress) {
|
|
12
24
|
if (!connection) {
|
|
13
25
|
return { shardCount: DEFAULT_FEE_VAULT_SHARD_COUNT };
|
|
14
26
|
}
|
|
15
27
|
try {
|
|
16
28
|
const { PublicKey } = await import("@solana/web3.js");
|
|
17
|
-
const info = await connection
|
|
29
|
+
const info = await getAccountInfoWithRetry(connection, new PublicKey(vmStateAddress), {
|
|
30
|
+
commitment: SDK_COMMITMENTS.READ,
|
|
31
|
+
retries: 2,
|
|
32
|
+
delayMs: 700,
|
|
33
|
+
});
|
|
18
34
|
if (!info) {
|
|
19
35
|
return { shardCount: DEFAULT_FEE_VAULT_SHARD_COUNT };
|
|
20
36
|
}
|
|
@@ -25,7 +41,7 @@ async function readVMStateFeeConfig(connection, vmStateAddress) {
|
|
|
25
41
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
26
42
|
const deployFeeLamports = view.getUint32(40, true);
|
|
27
43
|
const shardCountRaw = data.length > 50 ? data[50] : 0;
|
|
28
|
-
const shardCount = shardCountRaw
|
|
44
|
+
const shardCount = clampShardCount(shardCountRaw);
|
|
29
45
|
return { deployFeeLamports, shardCount };
|
|
30
46
|
}
|
|
31
47
|
catch {
|
|
@@ -43,7 +59,8 @@ function createInitFeeVaultInstructionData(shardIndex, bump) {
|
|
|
43
59
|
async function initProgramFeeVaultShards(connection, programId, vmStateAccount, shardCount, payer, options = {}) {
|
|
44
60
|
const { PublicKey, Transaction, TransactionInstruction, SystemProgram } = await import("@solana/web3.js");
|
|
45
61
|
const signatures = [];
|
|
46
|
-
|
|
62
|
+
const effectiveShardCount = clampShardCount(shardCount);
|
|
63
|
+
for (let shardIndex = 0; shardIndex < effectiveShardCount; shardIndex++) {
|
|
47
64
|
const vault = await deriveProgramFeeVault(programId, shardIndex);
|
|
48
65
|
const tx = new Transaction().add(new TransactionInstruction({
|
|
49
66
|
programId: new PublicKey(programId),
|
|
@@ -64,7 +81,14 @@ async function initProgramFeeVaultShards(connection, programId, vmStateAccount,
|
|
|
64
81
|
preflightCommitment: "confirmed",
|
|
65
82
|
maxRetries: options.maxRetries || 3,
|
|
66
83
|
});
|
|
67
|
-
await connection
|
|
84
|
+
const shardConfirm = await confirmTransactionRobust(connection, sig, {
|
|
85
|
+
commitment: "finalized",
|
|
86
|
+
timeoutMs: 120000,
|
|
87
|
+
debug: options.debug,
|
|
88
|
+
});
|
|
89
|
+
if (!shardConfirm.success) {
|
|
90
|
+
throw new Error(`Fee vault shard init failed: ${shardConfirm.error || "unconfirmed"}`);
|
|
91
|
+
}
|
|
68
92
|
signatures.push(sig);
|
|
69
93
|
if (options.debug) {
|
|
70
94
|
console.log(`[FiveSDK] Initialized fee vault shard ${shardIndex}: ${vault.address}`);
|
|
@@ -192,7 +216,7 @@ options = {}) {
|
|
|
192
216
|
}));
|
|
193
217
|
}
|
|
194
218
|
// 1. Initialize canonical VM State if missing
|
|
195
|
-
const vmStateInfo = await connection.getAccountInfo(vmStatePubkey);
|
|
219
|
+
const vmStateInfo = await connection.getAccountInfo(vmStatePubkey, "finalized");
|
|
196
220
|
if (!vmStateInfo) {
|
|
197
221
|
tx.add(new TransactionInstruction({
|
|
198
222
|
keys: [
|
|
@@ -530,7 +554,14 @@ options = {}) {
|
|
|
530
554
|
preflightCommitment: "confirmed",
|
|
531
555
|
maxRetries: options.maxRetries || 3,
|
|
532
556
|
});
|
|
533
|
-
await connection
|
|
557
|
+
const initConfirm = await confirmTransactionRobust(connection, initSignature, {
|
|
558
|
+
commitment: "finalized",
|
|
559
|
+
timeoutMs: 120000,
|
|
560
|
+
debug: options.debug,
|
|
561
|
+
});
|
|
562
|
+
if (!initConfirm.success) {
|
|
563
|
+
throw new Error(`Initialization confirmation failed: ${initConfirm.error || "unconfirmed"}`);
|
|
564
|
+
}
|
|
534
565
|
transactionIds.push(initSignature);
|
|
535
566
|
if (options.debug) {
|
|
536
567
|
console.log(`[FiveSDK] ✅ Initialization completed: ${initSignature}`);
|
|
@@ -549,16 +580,9 @@ options = {}) {
|
|
|
549
580
|
console.log(`[FiveSDK] Step ${i + 2}: Appending chunk ${i + 1}/${chunks.length} (${chunk.length} bytes)`);
|
|
550
581
|
}
|
|
551
582
|
// Calculate additional rent needed for this chunk
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (options.debug)
|
|
556
|
-
console.log(`[FiveSDK] Account info null, retrying...`);
|
|
557
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
558
|
-
currentInfo = await connection.getAccountInfo(scriptKeypair.publicKey);
|
|
559
|
-
if (!currentInfo)
|
|
560
|
-
throw new Error("Script account not found after initialization");
|
|
561
|
-
}
|
|
583
|
+
const currentInfo = await getAccountInfoWithRetry(connection, scriptKeypair.publicKey, { commitment: "finalized", retries: 2, delayMs: 1000, debug: options.debug });
|
|
584
|
+
if (!currentInfo)
|
|
585
|
+
throw new Error("Script account not found after initialization");
|
|
562
586
|
const newSize = currentInfo.data.length + chunk.length;
|
|
563
587
|
const newRentRequired = await connection.getMinimumBalanceForRentExemption(newSize);
|
|
564
588
|
const additionalRent = Math.max(0, newRentRequired - currentInfo.lamports);
|
|
@@ -627,14 +651,29 @@ options = {}) {
|
|
|
627
651
|
preflightCommitment: "confirmed",
|
|
628
652
|
maxRetries: options.maxRetries || 3,
|
|
629
653
|
});
|
|
630
|
-
await connection
|
|
654
|
+
const appendConfirm = await confirmTransactionRobust(connection, appendSignature, {
|
|
655
|
+
commitment: "finalized",
|
|
656
|
+
timeoutMs: 120000,
|
|
657
|
+
debug: options.debug,
|
|
658
|
+
});
|
|
659
|
+
if (!appendConfirm.success) {
|
|
660
|
+
throw new Error(`Append confirmation failed: ${appendConfirm.error || "unconfirmed"}`);
|
|
661
|
+
}
|
|
631
662
|
transactionIds.push(appendSignature);
|
|
632
663
|
if (options.debug) {
|
|
633
664
|
console.log(`[FiveSDK] ✅ Chunk ${i + 1} appended: ${appendSignature}`);
|
|
634
665
|
}
|
|
635
666
|
}
|
|
636
667
|
// Final verification
|
|
637
|
-
|
|
668
|
+
let finalInfo = await getAccountInfoWithRetry(connection, scriptKeypair.publicKey, {
|
|
669
|
+
commitment: "finalized",
|
|
670
|
+
retries: 2,
|
|
671
|
+
delayMs: 1000,
|
|
672
|
+
debug: options.debug,
|
|
673
|
+
});
|
|
674
|
+
if (!finalInfo) {
|
|
675
|
+
throw new Error("Script account not found during final verification");
|
|
676
|
+
}
|
|
638
677
|
const expectedSize = SCRIPT_HEADER_SIZE + bytecode.length;
|
|
639
678
|
if (options.debug) {
|
|
640
679
|
console.log(`[FiveSDK] 🔍 Final verification:`);
|
|
@@ -882,7 +921,7 @@ options = {}) {
|
|
|
882
921
|
preflightCommitment: "confirmed",
|
|
883
922
|
maxRetries: options.maxRetries || 3,
|
|
884
923
|
});
|
|
885
|
-
const appendConfirmation = await pollForConfirmation(connection, appendSignature, "
|
|
924
|
+
const appendConfirmation = await pollForConfirmation(connection, appendSignature, "finalized", 120000, options.debug);
|
|
886
925
|
if (!appendConfirmation.success) {
|
|
887
926
|
return {
|
|
888
927
|
success: false,
|
|
@@ -929,7 +968,7 @@ options = {}) {
|
|
|
929
968
|
maxRetries: options.maxRetries || 3,
|
|
930
969
|
});
|
|
931
970
|
// Use custom polling for finalize to handle validator latency
|
|
932
|
-
const finalizeConfirmation = await pollForConfirmation(connection, finalizeSignature, "
|
|
971
|
+
const finalizeConfirmation = await pollForConfirmation(connection, finalizeSignature, "finalized", 120000, // 120 second timeout
|
|
933
972
|
options.debug);
|
|
934
973
|
if (!finalizeConfirmation.success) {
|
|
935
974
|
console.error(`[FiveSDK] FinalizeScript confirmation failed: ${finalizeConfirmation.error}`);
|
|
@@ -1056,7 +1095,7 @@ async function ensureCanonicalVmStateAccount(connection, deployerKeypair, progra
|
|
|
1056
1095
|
throw new Error(`vmStateAccount must be canonical PDA ${canonical.address}; got ${options.vmStateAccount}`);
|
|
1057
1096
|
}
|
|
1058
1097
|
const vmStatePubkey = new PublicKey(canonical.address);
|
|
1059
|
-
const existing = await connection.getAccountInfo(vmStatePubkey);
|
|
1098
|
+
const existing = await connection.getAccountInfo(vmStatePubkey, "finalized");
|
|
1060
1099
|
if (existing) {
|
|
1061
1100
|
if (existing.owner.toBase58() !== programId.toBase58()) {
|
|
1062
1101
|
throw new Error(`canonical VM state ${canonical.address} exists but is owned by ${existing.owner.toBase58()}, expected ${programId.toBase58()}`);
|
package/dist/modules/execute.js
CHANGED
|
@@ -7,13 +7,26 @@ import { validator, Validators } from "../validation/index.js";
|
|
|
7
7
|
import { calculateExecuteFee } from "./fees.js";
|
|
8
8
|
import { loadWasmVM } from "../wasm/instance.js";
|
|
9
9
|
import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
|
|
10
|
-
|
|
10
|
+
import { VmClusterConfigResolver } from "../config/VmClusterConfigResolver.js";
|
|
11
|
+
import { confirmTransactionRobust, getAccountInfoWithRetry, } from "../utils/transaction.js";
|
|
12
|
+
const DEFAULT_FEE_VAULT_SHARD_COUNT = (() => {
|
|
13
|
+
try {
|
|
14
|
+
return VmClusterConfigResolver.loadClusterConfig().feeVaultShardCount;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
11
20
|
const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
|
|
12
21
|
0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
|
|
13
22
|
0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
|
|
14
23
|
]);
|
|
15
24
|
const EXECUTE_FEE_HEADER_A = 0xff;
|
|
16
25
|
const EXECUTE_FEE_HEADER_B = 0x53;
|
|
26
|
+
function clampShardCount(rawCount) {
|
|
27
|
+
const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
28
|
+
return Math.max(1, Math.min(DEFAULT_FEE_VAULT_SHARD_COUNT, normalized));
|
|
29
|
+
}
|
|
17
30
|
async function deriveProgramFeeVault(programId, shardIndex) {
|
|
18
31
|
const { PublicKey } = await import("@solana/web3.js");
|
|
19
32
|
const [pda, bump] = PublicKey.findProgramAddressSync([FEE_VAULT_NAMESPACE_SEED, Buffer.from([shardIndex])], new PublicKey(programId));
|
|
@@ -24,14 +37,17 @@ async function readVMStateShardCount(connection, vmStateAddress) {
|
|
|
24
37
|
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
25
38
|
try {
|
|
26
39
|
const { PublicKey } = await import("@solana/web3.js");
|
|
27
|
-
const info = await connection
|
|
40
|
+
const info = await getAccountInfoWithRetry(connection, new PublicKey(vmStateAddress), {
|
|
41
|
+
commitment: "finalized",
|
|
42
|
+
retries: 2,
|
|
43
|
+
delayMs: 1000,
|
|
44
|
+
});
|
|
28
45
|
if (!info)
|
|
29
46
|
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
30
47
|
const data = new Uint8Array(info.data);
|
|
31
48
|
if (data.length <= 50)
|
|
32
49
|
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
33
|
-
|
|
34
|
-
return shardCount > 0 ? shardCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
50
|
+
return clampShardCount(data[50]);
|
|
35
51
|
}
|
|
36
52
|
catch {
|
|
37
53
|
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
@@ -265,7 +281,10 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
265
281
|
{ pubkey: vmState, isSigner: false, isWritable: false },
|
|
266
282
|
];
|
|
267
283
|
const abiAccountMetadata = new Map();
|
|
268
|
-
|
|
284
|
+
const hasFullParameterList = !!funcDef &&
|
|
285
|
+
Array.isArray(funcDef.parameters) &&
|
|
286
|
+
parameters.length === funcDef.parameters.length;
|
|
287
|
+
if (funcDef && funcDef.parameters && hasFullParameterList) {
|
|
269
288
|
// First pass: detect if there's an @init constraint and find the payer
|
|
270
289
|
let hasInit = false;
|
|
271
290
|
let payerPubkey;
|
|
@@ -312,9 +331,11 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
312
331
|
// Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
|
|
313
332
|
const abiMetadata = abiAccountMetadata.get(acc);
|
|
314
333
|
const passedMetadata = options.accountMetadata?.get(acc);
|
|
315
|
-
const metadata =
|
|
334
|
+
const metadata = passedMetadata || abiMetadata;
|
|
316
335
|
const isSigner = metadata ? metadata.isSigner : false;
|
|
317
|
-
const isWritable = metadata
|
|
336
|
+
const isWritable = metadata
|
|
337
|
+
? (metadata.isSystemAccount ? false : metadata.isWritable)
|
|
338
|
+
: true;
|
|
318
339
|
return {
|
|
319
340
|
pubkey: acc,
|
|
320
341
|
isSigner,
|
|
@@ -322,7 +343,7 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
322
343
|
};
|
|
323
344
|
});
|
|
324
345
|
instructionAccounts.push(...userInstructionAccounts);
|
|
325
|
-
const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex,
|
|
346
|
+
const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, options.debug === true);
|
|
326
347
|
// Runtime requires strict tail: [payer, fee_vault, system_program].
|
|
327
348
|
const signerCandidates = instructionAccounts
|
|
328
349
|
.filter((acc) => acc.isSigner)
|
|
@@ -444,15 +465,15 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
444
465
|
maxRetries: options.maxRetries || 3,
|
|
445
466
|
});
|
|
446
467
|
lastSignature = signature;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
468
|
+
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
469
|
+
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
470
|
+
commitment: "confirmed",
|
|
471
|
+
timeoutMs: 120000,
|
|
472
|
+
debug: options.debug,
|
|
473
|
+
blockhash,
|
|
474
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
475
|
+
});
|
|
476
|
+
if (!confirmation.success) {
|
|
456
477
|
try {
|
|
457
478
|
const txDetails = await connection.getTransaction(signature, {
|
|
458
479
|
commitment: "confirmed",
|
|
@@ -479,9 +500,9 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
479
500
|
}
|
|
480
501
|
}
|
|
481
502
|
catch (getTransactionError) { }
|
|
482
|
-
throw
|
|
503
|
+
throw new Error(confirmation.error || "Execution confirmation failed");
|
|
483
504
|
}
|
|
484
|
-
if (confirmation.
|
|
505
|
+
if (confirmation.err) {
|
|
485
506
|
let logs = [];
|
|
486
507
|
let computeUnitsUsed;
|
|
487
508
|
try {
|
|
@@ -495,7 +516,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
495
516
|
}
|
|
496
517
|
}
|
|
497
518
|
catch { }
|
|
498
|
-
const errorMessage = `Execution transaction failed: ${JSON.stringify(confirmation.
|
|
519
|
+
const errorMessage = `Execution transaction failed: ${JSON.stringify(confirmation.err)}`;
|
|
499
520
|
return {
|
|
500
521
|
success: false,
|
|
501
522
|
error: errorMessage,
|
|
@@ -559,14 +580,13 @@ export async function executeScriptAccount(scriptAccount, functionIndex = 0, par
|
|
|
559
580
|
});
|
|
560
581
|
}
|
|
561
582
|
// Helpers
|
|
562
|
-
function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex,
|
|
583
|
+
function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex, debug = false) {
|
|
563
584
|
const parts = [];
|
|
564
585
|
parts.push(new Uint8Array([9]));
|
|
565
586
|
parts.push(new Uint8Array([
|
|
566
587
|
EXECUTE_FEE_HEADER_A,
|
|
567
588
|
EXECUTE_FEE_HEADER_B,
|
|
568
589
|
feeShardIndex & 0xff,
|
|
569
|
-
feeVaultBump & 0xff,
|
|
570
590
|
]));
|
|
571
591
|
// Function index as fixed u32
|
|
572
592
|
parts.push(encodeU32(functionIndex));
|
|
@@ -580,6 +600,15 @@ function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeS
|
|
|
580
600
|
result.set(part, resultOffset);
|
|
581
601
|
resultOffset += part.length;
|
|
582
602
|
}
|
|
603
|
+
if (debug) {
|
|
604
|
+
const payloadLen = encodedParams.length;
|
|
605
|
+
const previewLen = Math.min(24, result.length);
|
|
606
|
+
const previewHex = Buffer.from(result.subarray(0, previewLen)).toString("hex");
|
|
607
|
+
const legacyVarintLikely = result.length >= 9 &&
|
|
608
|
+
// Legacy varint flow had no fee header and placed varint fields immediately after discriminator.
|
|
609
|
+
!(result[1] === EXECUTE_FEE_HEADER_A && result[2] === EXECUTE_FEE_HEADER_B);
|
|
610
|
+
console.log(`[FiveSDK] Execute wire envelope: discr=${result[0]} fee_header=[${result[1]},${result[2]},${result[3]}] function_index_u32=${functionIndex} param_count_u32=${paramCount} payload_len=${payloadLen} total_len=${result.length} preview_hex=${previewHex}${legacyVarintLikely ? " legacy_varint_suspected=true" : ""}`);
|
|
611
|
+
}
|
|
583
612
|
return result;
|
|
584
613
|
}
|
|
585
614
|
function encodeU32(value) {
|
|
@@ -625,21 +654,27 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, _
|
|
|
625
654
|
const type = (param.type || param.param_type || '').toString().trim().toLowerCase();
|
|
626
655
|
return type === 'pubkey';
|
|
627
656
|
};
|
|
628
|
-
const paramDefs =
|
|
657
|
+
const paramDefs = functionDef.parameters || [];
|
|
629
658
|
const nonAccountParamDefs = paramDefs.filter((param) => !isAccountParam(param));
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
659
|
+
const isFullParamList = parameters.length === paramDefs.length;
|
|
660
|
+
const isArgOnlyList = parameters.length === nonAccountParamDefs.length;
|
|
661
|
+
if (!isFullParamList && !isArgOnlyList) {
|
|
662
|
+
console.warn(`[FiveSDK] Parameter validation warning: Function '${functionDef.name}' expects ${paramDefs.length} total params (${nonAccountParamDefs.length} non-account), but received ${parameters.length}.`);
|
|
663
|
+
}
|
|
664
|
+
// Current VM/compiler contract encodes non-account parameters only.
|
|
665
|
+
// If callers pass full ABI params (accounts + args), normalize to args-only order.
|
|
666
|
+
if (isFullParamList) {
|
|
667
|
+
console.warn(`[FiveSDK] Deprecation: full ABI parameter lists (including account params) are normalized to non-account execute args for '${functionDef.name}'. Pass args-only params to avoid this warning.`);
|
|
633
668
|
}
|
|
669
|
+
const defsForEncoding = nonAccountParamDefs;
|
|
634
670
|
const paramValues = {};
|
|
635
|
-
|
|
636
|
-
for (let index = 0; index <
|
|
637
|
-
const param =
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (sourceIndex >= parameters.length) {
|
|
671
|
+
const paramSourceIndexByName = new Map(paramDefs.map((param, index) => [param.name, index]));
|
|
672
|
+
for (let index = 0; index < defsForEncoding.length; index++) {
|
|
673
|
+
const param = defsForEncoding[index];
|
|
674
|
+
const sourceIndex = isFullParamList
|
|
675
|
+
? (paramSourceIndexByName.get(param.name) ?? -1)
|
|
676
|
+
: index;
|
|
677
|
+
if (sourceIndex < 0 || sourceIndex >= parameters.length) {
|
|
643
678
|
throw new Error(`Missing value for parameter: ${param.name}`);
|
|
644
679
|
}
|
|
645
680
|
let value = parameters[sourceIndex];
|
|
@@ -649,10 +684,9 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, _
|
|
|
649
684
|
}
|
|
650
685
|
}
|
|
651
686
|
paramValues[param.name] = value;
|
|
652
|
-
argCursor += 1;
|
|
653
687
|
}
|
|
654
|
-
const encoded = await BytecodeEncoder.encodeExecute(functionIndex,
|
|
655
|
-
return { encoded, paramCount:
|
|
688
|
+
const encoded = await BytecodeEncoder.encodeExecute(functionIndex, defsForEncoding, paramValues, true, options);
|
|
689
|
+
return { encoded, paramCount: defsForEncoding.length };
|
|
656
690
|
}
|
|
657
691
|
function estimateComputeUnits(functionIndex, parameterCount) {
|
|
658
692
|
return Math.max(5000, 1000 + parameterCount * 500 + functionIndex * 100);
|
package/dist/modules/vm-state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PDAUtils, Base58Utils } from "../crypto/index.js";
|
|
2
2
|
import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
|
|
3
|
+
import { getAccountInfoWithRetry } from "../utils/transaction.js";
|
|
3
4
|
export async function getVMState(connection, fiveVMProgramId) {
|
|
4
5
|
const programId = ProgramIdResolver.resolve(fiveVMProgramId);
|
|
5
6
|
const vmStatePDA = await PDAUtils.deriveVMStatePDA(programId);
|
|
@@ -12,7 +13,11 @@ export async function getVMState(connection, fiveVMProgramId) {
|
|
|
12
13
|
pubkey = new PublicKey(vmStatePDA.address);
|
|
13
14
|
}
|
|
14
15
|
catch { }
|
|
15
|
-
const info = await connection
|
|
16
|
+
const info = await getAccountInfoWithRetry(connection, pubkey, {
|
|
17
|
+
commitment: "finalized",
|
|
18
|
+
retries: 2,
|
|
19
|
+
delayMs: 1000,
|
|
20
|
+
});
|
|
16
21
|
if (!info)
|
|
17
22
|
throw new Error("VM State account not found");
|
|
18
23
|
accountData = new Uint8Array(info.data);
|
|
@@ -29,6 +29,7 @@ export declare class FunctionBuilder {
|
|
|
29
29
|
private accountsMap;
|
|
30
30
|
private argsMap;
|
|
31
31
|
private resolvedAccounts;
|
|
32
|
+
private vmPayerAccount?;
|
|
32
33
|
constructor(functionDef: FunctionDefinition, scriptAccount: string, abi: ScriptABI, options: FiveProgramOptions);
|
|
33
34
|
/**
|
|
34
35
|
* Specify accounts for this function call
|
|
@@ -47,6 +48,13 @@ export declare class FunctionBuilder {
|
|
|
47
48
|
* @returns this for method chaining
|
|
48
49
|
*/
|
|
49
50
|
args(args: Record<string, any>): this;
|
|
51
|
+
/**
|
|
52
|
+
* Override the VM execute-fee payer account for this instruction.
|
|
53
|
+
* This is distinct from the outer transaction fee payer.
|
|
54
|
+
*/
|
|
55
|
+
payer(payer: string | {
|
|
56
|
+
toBase58(): string;
|
|
57
|
+
}): this;
|
|
50
58
|
/**
|
|
51
59
|
* Build and return serialized instruction data
|
|
52
60
|
* This is the main method that orchestrates parameter resolution and instruction generation
|
|
@@ -62,6 +62,15 @@ export class FunctionBuilder {
|
|
|
62
62
|
}
|
|
63
63
|
return this;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Override the VM execute-fee payer account for this instruction.
|
|
67
|
+
* This is distinct from the outer transaction fee payer.
|
|
68
|
+
*/
|
|
69
|
+
payer(payer) {
|
|
70
|
+
this.vmPayerAccount =
|
|
71
|
+
typeof payer === 'string' ? payer : payer.toBase58();
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
65
74
|
/**
|
|
66
75
|
* Build and return serialized instruction data
|
|
67
76
|
* This is the main method that orchestrates parameter resolution and instruction generation
|
|
@@ -93,7 +102,7 @@ export class FunctionBuilder {
|
|
|
93
102
|
// Now validate that all required parameters are provided (including auto-injected ones)
|
|
94
103
|
this.validateParameters();
|
|
95
104
|
// Merge parameters in ABI order (accounts first, then data)
|
|
96
|
-
const { mergedParams, accountPubkeys } = this.mergeParameters();
|
|
105
|
+
const { mergedParams, argParams, accountPubkeys } = this.mergeParameters();
|
|
97
106
|
// Append system accounts to the account list (they go at the end)
|
|
98
107
|
const allAccountPubkeys = [...accountPubkeys, ...systemAccountsList];
|
|
99
108
|
// Build account metadata from ABI attributes
|
|
@@ -105,7 +114,7 @@ export class FunctionBuilder {
|
|
|
105
114
|
}
|
|
106
115
|
// Call existing SDK method to generate instruction
|
|
107
116
|
// This reuses the proven parameter encoding logic
|
|
108
|
-
const instruction = await this.generateInstructionData(mergedParams, allAccountPubkeys, accountMetadata);
|
|
117
|
+
const instruction = await this.generateInstructionData(mergedParams, argParams, allAccountPubkeys, accountMetadata);
|
|
109
118
|
if (this.options.debug) {
|
|
110
119
|
console.log(`[FunctionBuilder] Generated instruction:`, instruction);
|
|
111
120
|
}
|
|
@@ -178,6 +187,7 @@ export class FunctionBuilder {
|
|
|
178
187
|
*/
|
|
179
188
|
mergeParameters() {
|
|
180
189
|
const mergedParams = [];
|
|
190
|
+
const argParams = [];
|
|
181
191
|
const accountPubkeys = [];
|
|
182
192
|
for (const param of this.functionDef.parameters) {
|
|
183
193
|
if (param.is_account) {
|
|
@@ -196,9 +206,10 @@ export class FunctionBuilder {
|
|
|
196
206
|
throw new Error(`Missing argument '${param.name}'`);
|
|
197
207
|
}
|
|
198
208
|
mergedParams.push(value);
|
|
209
|
+
argParams.push(value);
|
|
199
210
|
}
|
|
200
211
|
}
|
|
201
|
-
return { mergedParams, accountPubkeys };
|
|
212
|
+
return { mergedParams, argParams, accountPubkeys };
|
|
202
213
|
}
|
|
203
214
|
/**
|
|
204
215
|
* Build account metadata (isSigner, isWritable) from ABI attributes
|
|
@@ -279,14 +290,14 @@ export class FunctionBuilder {
|
|
|
279
290
|
* @param accountMetadata - Account metadata (isSigner, isWritable)
|
|
280
291
|
* @returns SerializedInstruction
|
|
281
292
|
*/
|
|
282
|
-
async generateInstructionData(mergedParams, accountList, accountMetadata) {
|
|
293
|
+
async generateInstructionData(mergedParams, argParams, accountList, accountMetadata) {
|
|
283
294
|
// Account list is already passed in
|
|
284
295
|
// Dynamically import FiveSDK to avoid circular dependencies
|
|
285
296
|
const { FiveSDK } = await import('../FiveSDK.js');
|
|
286
297
|
// Call the SDK's generateExecuteInstruction method.
|
|
287
298
|
// This handles fixed-size typed execute encoding and parameter validation.
|
|
288
299
|
const executionResult = await FiveSDK.generateExecuteInstruction(this.scriptAccount, this.functionDef.index, // Use function index directly
|
|
289
|
-
|
|
300
|
+
argParams, // Current VM contract encodes non-account args only
|
|
290
301
|
accountList, // Account pubkey list
|
|
291
302
|
undefined, // No connection needed - we have ABI
|
|
292
303
|
{
|
|
@@ -295,6 +306,8 @@ export class FunctionBuilder {
|
|
|
295
306
|
fiveVMProgramId: this.options.fiveVMProgramId,
|
|
296
307
|
vmStateAccount: this.options.vmStateAccount,
|
|
297
308
|
adminAccount: this.options.feeReceiverAccount,
|
|
309
|
+
payerAccount: this.vmPayerAccount ||
|
|
310
|
+
this.options.provider?.publicKey?.toBase58?.(),
|
|
298
311
|
accountMetadata: accountMetadata, // Pass account metadata for correct isWritable flags
|
|
299
312
|
});
|
|
300
313
|
// Map SDK's instruction format (with 'accounts') to SerializedInstruction format (with 'keys')
|
package/dist/project/config.js
CHANGED
|
@@ -22,7 +22,6 @@ export function parseProjectConfig(parsedToml) {
|
|
|
22
22
|
rpcUrl: deploy.rpc_url,
|
|
23
23
|
programId: deploy.program_id,
|
|
24
24
|
keypairPath: deploy.keypair_path,
|
|
25
|
-
multiFileMode: build.multi_file_mode ?? false,
|
|
26
25
|
optimizations: {
|
|
27
26
|
enableCompression: optimizations.enable_compression ?? true,
|
|
28
27
|
enableConstraintOptimization: optimizations.enable_constraint_optimization ?? true,
|
|
@@ -238,9 +238,14 @@ export class FiveTestRunner {
|
|
|
238
238
|
byFile.set(test.path, cases);
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
const moduleNameForFile = (file) => file
|
|
242
|
+
.replace(/\.v$/i, '')
|
|
243
|
+
.split(/[\\/]/)
|
|
244
|
+
.filter(Boolean)
|
|
245
|
+
.join('::');
|
|
241
246
|
for (const [file, testCases] of byFile.entries()) {
|
|
242
247
|
suites.push({
|
|
243
|
-
name:
|
|
248
|
+
name: moduleNameForFile(file),
|
|
244
249
|
description: `Tests from ${file}`,
|
|
245
250
|
testCases
|
|
246
251
|
});
|