@5ive-tech/sdk 1.1.7 → 1.1.9
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/dist/FiveSDK.d.ts +17 -0
- package/dist/FiveSDK.js +11 -0
- 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/config/ProgramIdResolver.d.ts +1 -1
- package/dist/config/ProgramIdResolver.js +1 -1
- package/dist/crypto/index.js +2 -10
- package/dist/modules/admin.d.ts +121 -0
- package/dist/modules/admin.js +327 -0
- package/dist/modules/deploy.d.ts +17 -0
- package/dist/modules/deploy.js +294 -241
- package/dist/modules/execute.d.ts +4 -0
- package/dist/modules/execute.js +104 -94
- package/dist/modules/fees.d.ts +2 -0
- package/dist/modules/fees.js +21 -19
- package/dist/modules/vm-state.d.ts +4 -0
- package/dist/modules/vm-state.js +9 -2
- package/dist/testing/AccountTestFixture.js +3 -3
- package/dist/testing/TestDiscovery.d.ts +6 -0
- package/dist/testing/TestDiscovery.js +62 -57
- package/dist/testing/TestRunner.d.ts +1 -1
- package/dist/testing/TestRunner.js +62 -41
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +2 -1
- package/dist/assets/vm/dummy.file +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.js +0 -3307
|
@@ -52,6 +52,8 @@ export declare function generateExecuteInstruction(scriptAccount: string, functi
|
|
|
52
52
|
isWritable: boolean;
|
|
53
53
|
isSystemAccount?: boolean;
|
|
54
54
|
}>;
|
|
55
|
+
feeShardIndex?: number;
|
|
56
|
+
payerAccount?: string;
|
|
55
57
|
}): Promise<SerializedExecution>;
|
|
56
58
|
export declare function executeOnSolana(scriptAccount: string, connection: any, signerKeypair: any, functionName: string | number, parameters?: any[], accounts?: string[], options?: {
|
|
57
59
|
debug?: boolean;
|
|
@@ -63,6 +65,8 @@ export declare function executeOnSolana(scriptAccount: string, connection: any,
|
|
|
63
65
|
vmStateAccount?: string;
|
|
64
66
|
fiveVMProgramId?: string;
|
|
65
67
|
abi?: any;
|
|
68
|
+
feeShardIndex?: number;
|
|
69
|
+
payerAccount?: string;
|
|
66
70
|
}): Promise<{
|
|
67
71
|
success: boolean;
|
|
68
72
|
result?: any;
|
package/dist/modules/execute.js
CHANGED
|
@@ -7,6 +7,45 @@ 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
|
+
const DEFAULT_FEE_VAULT_SHARD_COUNT = 10;
|
|
11
|
+
const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
|
|
12
|
+
0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
|
|
13
|
+
0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
|
|
14
|
+
]);
|
|
15
|
+
const EXECUTE_FEE_HEADER_A = 0xff;
|
|
16
|
+
const EXECUTE_FEE_HEADER_B = 0x53;
|
|
17
|
+
async function deriveProgramFeeVault(programId, shardIndex) {
|
|
18
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
19
|
+
const [pda, bump] = PublicKey.findProgramAddressSync([FEE_VAULT_NAMESPACE_SEED, Buffer.from([shardIndex])], new PublicKey(programId));
|
|
20
|
+
return { address: pda.toBase58(), bump };
|
|
21
|
+
}
|
|
22
|
+
async function readVMStateShardCount(connection, vmStateAddress) {
|
|
23
|
+
if (!connection)
|
|
24
|
+
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
25
|
+
try {
|
|
26
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
27
|
+
const info = await connection.getAccountInfo(new PublicKey(vmStateAddress), "confirmed");
|
|
28
|
+
if (!info)
|
|
29
|
+
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
30
|
+
const data = new Uint8Array(info.data);
|
|
31
|
+
if (data.length <= 50)
|
|
32
|
+
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
33
|
+
const shardCount = data[50];
|
|
34
|
+
return shardCount > 0 ? shardCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function selectFeeShard(shardCount) {
|
|
41
|
+
const totalShards = Math.max(1, shardCount | 0);
|
|
42
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
43
|
+
const bytes = new Uint32Array(1);
|
|
44
|
+
crypto.getRandomValues(bytes);
|
|
45
|
+
return bytes[0] % totalShards;
|
|
46
|
+
}
|
|
47
|
+
return Math.floor(Math.random() * totalShards);
|
|
48
|
+
}
|
|
10
49
|
// Helper function to initialize ParameterEncoder if needed (though BytecodeEncoder is preferred)
|
|
11
50
|
// Assume BytecodeEncoder handles it or call it if needed.
|
|
12
51
|
// BytecodeEncoder uses WASM module directly via loader.
|
|
@@ -188,9 +227,9 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
188
227
|
funcDef = Array.isArray(scriptMetadata.functions)
|
|
189
228
|
? scriptMetadata.functions.find((f) => f.index === functionIndex)
|
|
190
229
|
: scriptMetadata.functions[functionIndex];
|
|
191
|
-
const
|
|
192
|
-
actualParamCount =
|
|
193
|
-
encodedParams =
|
|
230
|
+
const encoded = await encodeParametersWithABI(parameters, funcDef, functionIndex, accounts, options);
|
|
231
|
+
actualParamCount = encoded.paramCount;
|
|
232
|
+
encodedParams = encoded.encoded;
|
|
194
233
|
}
|
|
195
234
|
catch (metadataError) {
|
|
196
235
|
if (options.debug) {
|
|
@@ -212,34 +251,18 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
212
251
|
// Resolve program ID with consistent precedence
|
|
213
252
|
const programId = ProgramIdResolver.resolve(options.fiveVMProgramId);
|
|
214
253
|
const vmStatePDA = await PDAUtils.deriveVMStatePDA(programId);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!adminAccount && connection) {
|
|
218
|
-
try {
|
|
219
|
-
let vmStateAddress = options.vmStateAccount;
|
|
220
|
-
if (!vmStateAddress) {
|
|
221
|
-
const pda = await PDAUtils.deriveVMStatePDA(programId);
|
|
222
|
-
vmStateAddress = pda.address;
|
|
223
|
-
}
|
|
224
|
-
const { PublicKey } = await import("@solana/web3.js");
|
|
225
|
-
const info = await connection.getAccountInfo(new PublicKey(vmStateAddress));
|
|
226
|
-
if (info) {
|
|
227
|
-
const data = new Uint8Array(info.data);
|
|
228
|
-
if (data.length >= 32) {
|
|
229
|
-
const authorityPubkey = new PublicKey(data.slice(0, 32));
|
|
230
|
-
adminAccount = authorityPubkey.toBase58();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
if (options.debug) {
|
|
236
|
-
console.warn(`[FiveSDK] Failed to resolve admin account from VM state:`, error);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
254
|
+
if (options.vmStateAccount && options.vmStateAccount !== vmStatePDA.address) {
|
|
255
|
+
throw new Error(`vmStateAccount must be canonical PDA ${vmStatePDA.address}; got ${options.vmStateAccount}`);
|
|
239
256
|
}
|
|
257
|
+
const vmState = vmStatePDA.address;
|
|
258
|
+
const shardCount = await readVMStateShardCount(connection, vmState);
|
|
259
|
+
const feeShardIndex = options.feeShardIndex !== undefined
|
|
260
|
+
? ((options.feeShardIndex % shardCount) + shardCount) % shardCount
|
|
261
|
+
: selectFeeShard(shardCount);
|
|
262
|
+
const feeVault = await deriveProgramFeeVault(programId, feeShardIndex);
|
|
240
263
|
const instructionAccounts = [
|
|
241
264
|
{ pubkey: scriptAccount, isSigner: false, isWritable: false },
|
|
242
|
-
{ pubkey: vmState, isSigner: false, isWritable:
|
|
265
|
+
{ pubkey: vmState, isSigner: false, isWritable: false },
|
|
243
266
|
];
|
|
244
267
|
const abiAccountMetadata = new Map();
|
|
245
268
|
if (funcDef && funcDef.parameters) {
|
|
@@ -285,12 +308,12 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
285
308
|
}
|
|
286
309
|
});
|
|
287
310
|
}
|
|
288
|
-
const userInstructionAccounts = accounts.map((acc
|
|
311
|
+
const userInstructionAccounts = accounts.map((acc) => {
|
|
289
312
|
// Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
|
|
290
313
|
const abiMetadata = abiAccountMetadata.get(acc);
|
|
291
314
|
const passedMetadata = options.accountMetadata?.get(acc);
|
|
292
315
|
const metadata = abiMetadata || passedMetadata;
|
|
293
|
-
const isSigner = metadata ? metadata.isSigner :
|
|
316
|
+
const isSigner = metadata ? metadata.isSigner : false;
|
|
294
317
|
const isWritable = metadata ? metadata.isWritable : true;
|
|
295
318
|
return {
|
|
296
319
|
pubkey: acc,
|
|
@@ -299,20 +322,24 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
299
322
|
};
|
|
300
323
|
});
|
|
301
324
|
instructionAccounts.push(...userInstructionAccounts);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
325
|
+
const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, feeVault.bump);
|
|
326
|
+
// Runtime requires strict tail: [payer, fee_vault, system_program].
|
|
327
|
+
const signerCandidates = instructionAccounts
|
|
328
|
+
.filter((acc) => acc.isSigner)
|
|
329
|
+
.map((acc) => acc.pubkey);
|
|
330
|
+
const inferredPayer = options.payerAccount ||
|
|
331
|
+
(signerCandidates.length > 0
|
|
332
|
+
? signerCandidates[signerCandidates.length - 1]
|
|
333
|
+
: accounts[0]);
|
|
334
|
+
if (!inferredPayer) {
|
|
335
|
+
throw new Error("Could not infer execute fee payer account. Provide a signer account or set options.payerAccount.");
|
|
314
336
|
}
|
|
315
|
-
const
|
|
337
|
+
const feeTailAccounts = [
|
|
338
|
+
{ pubkey: inferredPayer, isSigner: true, isWritable: true },
|
|
339
|
+
{ pubkey: feeVault.address, isSigner: false, isWritable: true },
|
|
340
|
+
{ pubkey: "11111111111111111111111111111111", isSigner: false, isWritable: false },
|
|
341
|
+
];
|
|
342
|
+
instructionAccounts.push(...feeTailAccounts);
|
|
316
343
|
const result = {
|
|
317
344
|
instruction: {
|
|
318
345
|
programId: programId,
|
|
@@ -328,7 +355,7 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
328
355
|
requiredSigners: [],
|
|
329
356
|
estimatedComputeUnits: options.computeUnitLimit ||
|
|
330
357
|
estimateComputeUnits(functionIndex, parameters.length),
|
|
331
|
-
adminAccount:
|
|
358
|
+
adminAccount: feeVault.address,
|
|
332
359
|
};
|
|
333
360
|
const shouldEstimateFees = options.estimateFees !== false && connection;
|
|
334
361
|
if (shouldEstimateFees) {
|
|
@@ -356,6 +383,8 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
356
383
|
vmStateAccount: options.vmStateAccount,
|
|
357
384
|
fiveVMProgramId: options.fiveVMProgramId,
|
|
358
385
|
abi: options.abi,
|
|
386
|
+
feeShardIndex: options.feeShardIndex,
|
|
387
|
+
payerAccount: options.payerAccount || signerKeypair.publicKey.toString(),
|
|
359
388
|
});
|
|
360
389
|
}
|
|
361
390
|
catch (metadataError) {
|
|
@@ -375,19 +404,12 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
375
404
|
transaction.add(computePriceIx);
|
|
376
405
|
}
|
|
377
406
|
const accountKeys = [...executionData.instruction.accounts];
|
|
378
|
-
if (options.vmStateAccount && accountKeys.length >= 2) {
|
|
379
|
-
for (let i = 0; i < accountKeys.length; i++) {
|
|
380
|
-
if (i === 1) {
|
|
381
|
-
accountKeys[i].pubkey = options.vmStateAccount;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
407
|
const signerPubkey = signerKeypair.publicKey.toString();
|
|
387
408
|
let signerFound = false;
|
|
388
409
|
for (const meta of accountKeys) {
|
|
389
410
|
if (meta.pubkey === signerPubkey) {
|
|
390
411
|
meta.isSigner = true;
|
|
412
|
+
meta.isWritable = true;
|
|
391
413
|
signerFound = true;
|
|
392
414
|
}
|
|
393
415
|
}
|
|
@@ -537,9 +559,15 @@ export async function executeScriptAccount(scriptAccount, functionIndex = 0, par
|
|
|
537
559
|
});
|
|
538
560
|
}
|
|
539
561
|
// Helpers
|
|
540
|
-
function encodeExecuteInstruction(functionIndex, encodedParams, paramCount) {
|
|
562
|
+
function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex, feeVaultBump) {
|
|
541
563
|
const parts = [];
|
|
542
564
|
parts.push(new Uint8Array([9]));
|
|
565
|
+
parts.push(new Uint8Array([
|
|
566
|
+
EXECUTE_FEE_HEADER_A,
|
|
567
|
+
EXECUTE_FEE_HEADER_B,
|
|
568
|
+
feeShardIndex & 0xff,
|
|
569
|
+
feeVaultBump & 0xff,
|
|
570
|
+
]));
|
|
543
571
|
// Function index as fixed u32
|
|
544
572
|
parts.push(encodeU32(functionIndex));
|
|
545
573
|
// Param count as fixed u32
|
|
@@ -582,7 +610,7 @@ function inferParameterType(value) {
|
|
|
582
610
|
return "string";
|
|
583
611
|
}
|
|
584
612
|
}
|
|
585
|
-
async function encodeParametersWithABI(parameters, functionDef, functionIndex,
|
|
613
|
+
async function encodeParametersWithABI(parameters, functionDef, functionIndex, _accounts = [], options = {}) {
|
|
586
614
|
const isAccountParam = (param) => {
|
|
587
615
|
if (!param)
|
|
588
616
|
return false;
|
|
@@ -598,51 +626,33 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, a
|
|
|
598
626
|
return type === 'pubkey';
|
|
599
627
|
};
|
|
600
628
|
const paramDefs = (functionDef.parameters || []);
|
|
601
|
-
|
|
629
|
+
const nonAccountParamDefs = paramDefs.filter((param) => !isAccountParam(param));
|
|
630
|
+
const fullParameterListProvided = parameters.length >= paramDefs.length;
|
|
631
|
+
if (fullParameterListProvided && parameters.length !== paramDefs.length) {
|
|
602
632
|
console.warn(`[FiveSDK] Parameter validation warning: Function '${functionDef.name}' expects ${paramDefs.length} parameters, but received ${parameters.length}.`);
|
|
603
633
|
}
|
|
604
634
|
const paramValues = {};
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
throw new Error(`Account index ${value} out of bounds`);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
if (accountPubkey) {
|
|
625
|
-
const accountIndex = accounts.indexOf(accountPubkey);
|
|
626
|
-
if (accountIndex >= 0) {
|
|
627
|
-
// MitoVM receives accounts excluding the script account.
|
|
628
|
-
// Account index 0 is the VM state account.
|
|
629
|
-
value = accountIndex + 1;
|
|
630
|
-
}
|
|
631
|
-
else {
|
|
632
|
-
throw new Error(`Account ${accountPubkey} not found in accounts array`);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
else if (isPubkeyParam(param)) {
|
|
637
|
-
if (value && typeof value === 'object' && typeof value.toBase58 === 'function') {
|
|
638
|
-
value = value.toBase58();
|
|
639
|
-
}
|
|
635
|
+
let argCursor = 0;
|
|
636
|
+
for (let index = 0; index < paramDefs.length; index++) {
|
|
637
|
+
const param = paramDefs[index];
|
|
638
|
+
if (isAccountParam(param)) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const sourceIndex = fullParameterListProvided ? index : argCursor;
|
|
642
|
+
if (sourceIndex >= parameters.length) {
|
|
643
|
+
throw new Error(`Missing value for parameter: ${param.name}`);
|
|
644
|
+
}
|
|
645
|
+
let value = parameters[sourceIndex];
|
|
646
|
+
if (isPubkeyParam(param)) {
|
|
647
|
+
if (value && typeof value === 'object' && typeof value.toBase58 === 'function') {
|
|
648
|
+
value = value.toBase58();
|
|
640
649
|
}
|
|
641
|
-
paramValues[param.name] = value;
|
|
642
650
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
651
|
+
paramValues[param.name] = value;
|
|
652
|
+
argCursor += 1;
|
|
653
|
+
}
|
|
654
|
+
const encoded = await BytecodeEncoder.encodeExecute(functionIndex, nonAccountParamDefs, paramValues, true, options);
|
|
655
|
+
return { encoded, paramCount: nonAccountParamDefs.length };
|
|
646
656
|
}
|
|
647
657
|
function estimateComputeUnits(functionIndex, parameterCount) {
|
|
648
658
|
return Math.max(5000, 1000 + parameterCount * 500 + functionIndex * 100);
|
package/dist/modules/fees.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FeeInformation } from "../types.js";
|
|
2
2
|
export declare function getFees(connection: any, fiveVMProgramId?: string): Promise<{
|
|
3
|
+
deployFeeLamports: number;
|
|
4
|
+
executeFeeLamports: number;
|
|
3
5
|
deployFeeBps: number;
|
|
4
6
|
executeFeeBps: number;
|
|
5
7
|
adminAccount: string | null;
|
package/dist/modules/fees.js
CHANGED
|
@@ -4,16 +4,20 @@ export async function getFees(connection, fiveVMProgramId) {
|
|
|
4
4
|
try {
|
|
5
5
|
const state = await getVMState(connection, fiveVMProgramId);
|
|
6
6
|
return {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
deployFeeLamports: state.deployFeeLamports,
|
|
8
|
+
executeFeeLamports: state.executeFeeLamports,
|
|
9
|
+
deployFeeBps: state.deployFeeLamports,
|
|
10
|
+
executeFeeBps: state.executeFeeLamports,
|
|
11
|
+
adminAccount: state.authority,
|
|
10
12
|
};
|
|
11
13
|
}
|
|
12
14
|
catch (error) {
|
|
13
15
|
return {
|
|
16
|
+
deployFeeLamports: 0,
|
|
17
|
+
executeFeeLamports: 0,
|
|
14
18
|
deployFeeBps: 0,
|
|
15
19
|
executeFeeBps: 0,
|
|
16
|
-
adminAccount: null
|
|
20
|
+
adminAccount: null,
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -22,17 +26,16 @@ export async function calculateDeployFee(bytecodeSize, connection, fiveVMProgram
|
|
|
22
26
|
const accountSize = 64 + bytecodeSize;
|
|
23
27
|
const rentLamports = await RentCalculator.calculateRentExemption(accountSize);
|
|
24
28
|
const vmState = await getVMState(connection, fiveVMProgramId);
|
|
25
|
-
const
|
|
26
|
-
const feeLamports = Math.floor((rentLamports * deployFeeBps) / 10000);
|
|
29
|
+
const deployFeeLamports = vmState.deployFeeLamports;
|
|
27
30
|
return {
|
|
28
|
-
feeBps:
|
|
31
|
+
feeBps: 0,
|
|
29
32
|
basisLamports: rentLamports,
|
|
30
|
-
feeLamports,
|
|
31
|
-
totalEstimatedCost: rentLamports +
|
|
33
|
+
feeLamports: deployFeeLamports,
|
|
34
|
+
totalEstimatedCost: rentLamports + deployFeeLamports,
|
|
32
35
|
costBreakdown: {
|
|
33
36
|
basis: RentCalculator.formatSOL(rentLamports),
|
|
34
|
-
fee: RentCalculator.formatSOL(
|
|
35
|
-
total: RentCalculator.formatSOL(rentLamports +
|
|
37
|
+
fee: RentCalculator.formatSOL(deployFeeLamports),
|
|
38
|
+
total: RentCalculator.formatSOL(rentLamports + deployFeeLamports),
|
|
36
39
|
},
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -56,17 +59,16 @@ export async function calculateExecuteFee(connection, fiveVMProgramId) {
|
|
|
56
59
|
const STANDARD_TX_FEE = 5000;
|
|
57
60
|
try {
|
|
58
61
|
const vmState = await getVMState(connection, fiveVMProgramId);
|
|
59
|
-
const
|
|
60
|
-
const feeLamports = Math.floor((STANDARD_TX_FEE * executeFeeBps) / 10000);
|
|
62
|
+
const executeFeeLamports = vmState.executeFeeLamports;
|
|
61
63
|
return {
|
|
62
|
-
feeBps:
|
|
64
|
+
feeBps: 0,
|
|
63
65
|
basisLamports: STANDARD_TX_FEE,
|
|
64
|
-
feeLamports,
|
|
65
|
-
totalEstimatedCost: STANDARD_TX_FEE +
|
|
66
|
+
feeLamports: executeFeeLamports,
|
|
67
|
+
totalEstimatedCost: STANDARD_TX_FEE + executeFeeLamports,
|
|
66
68
|
costBreakdown: {
|
|
67
69
|
basis: RentCalculator.formatSOL(STANDARD_TX_FEE),
|
|
68
|
-
fee: RentCalculator.formatSOL(
|
|
69
|
-
total: RentCalculator.formatSOL(STANDARD_TX_FEE +
|
|
70
|
+
fee: RentCalculator.formatSOL(executeFeeLamports),
|
|
71
|
+
total: RentCalculator.formatSOL(STANDARD_TX_FEE + executeFeeLamports),
|
|
70
72
|
},
|
|
71
73
|
};
|
|
72
74
|
}
|
|
@@ -91,7 +93,7 @@ export async function getFeeInformation(bytecodeSize, connection, fiveVMProgramI
|
|
|
91
93
|
calculateExecuteFee(connection, fiveVMProgramId),
|
|
92
94
|
getVMState(connection, fiveVMProgramId),
|
|
93
95
|
]);
|
|
94
|
-
const feesEnabled = vmState.
|
|
96
|
+
const feesEnabled = vmState.deployFeeLamports > 0 || vmState.executeFeeLamports > 0;
|
|
95
97
|
return {
|
|
96
98
|
deploy: deployFee,
|
|
97
99
|
execute: executeFee,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export declare function getVMState(connection: any, fiveVMProgramId?: string): Promise<{
|
|
2
2
|
authority: string;
|
|
3
3
|
scriptCount: number;
|
|
4
|
+
deployFeeLamports: number;
|
|
5
|
+
executeFeeLamports: number;
|
|
4
6
|
deployFeeBps: number;
|
|
5
7
|
executeFeeBps: number;
|
|
8
|
+
feeVaultShardCount: number;
|
|
9
|
+
vmStateBump: number;
|
|
6
10
|
isInitialized: boolean;
|
|
7
11
|
}>;
|
package/dist/modules/vm-state.js
CHANGED
|
@@ -30,11 +30,18 @@ export async function getVMState(connection, fiveVMProgramId) {
|
|
|
30
30
|
throw new Error(`VM State account data too small: expected 56, got ${accountData.length}`);
|
|
31
31
|
const authority = Base58Utils.encode(accountData.slice(0, 32));
|
|
32
32
|
const view = new DataView(accountData.buffer, accountData.byteOffset, accountData.byteLength);
|
|
33
|
+
const deployFeeLamports = view.getUint32(40, true);
|
|
34
|
+
const executeFeeLamports = view.getUint32(44, true);
|
|
33
35
|
return {
|
|
34
36
|
authority,
|
|
35
37
|
scriptCount: Number(view.getBigUint64(32, true)),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
deployFeeLamports,
|
|
39
|
+
executeFeeLamports,
|
|
40
|
+
// Backward-compatible aliases; deprecated.
|
|
41
|
+
deployFeeBps: deployFeeLamports,
|
|
42
|
+
executeFeeBps: executeFeeLamports,
|
|
43
|
+
feeVaultShardCount: accountData[50] || 10,
|
|
44
|
+
vmStateBump: accountData[51] || 0,
|
|
38
45
|
isInitialized: accountData[48] === 1
|
|
39
46
|
};
|
|
40
47
|
}
|
|
@@ -219,7 +219,7 @@ export class AccountTestFixture {
|
|
|
219
219
|
else if (spec.type === 'state' || spec.type === 'mutable') {
|
|
220
220
|
// Create state/mutable account with initial data
|
|
221
221
|
const space = 1024; // Default space
|
|
222
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
222
|
+
const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
|
|
223
223
|
// Serialize state data if provided
|
|
224
224
|
let initialData;
|
|
225
225
|
if (spec.state && spec.type === 'state') {
|
|
@@ -242,7 +242,7 @@ export class AccountTestFixture {
|
|
|
242
242
|
else if (spec.type === 'init') {
|
|
243
243
|
// Create init account (will be initialized by script)
|
|
244
244
|
const space = 1024;
|
|
245
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
245
|
+
const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
|
|
246
246
|
publicKey = await manager.createAccount(space, owner);
|
|
247
247
|
if (options.debug) {
|
|
248
248
|
console.log(` ${spec.name} (init): ${publicKey.toString()}`);
|
|
@@ -256,7 +256,7 @@ export class AccountTestFixture {
|
|
|
256
256
|
else {
|
|
257
257
|
// Create readonly account
|
|
258
258
|
const space = 0;
|
|
259
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
259
|
+
const owner = options.fiveVMProgramId || new PublicKey('4Qxf3pbCse2veUgZVMiAm3nWqJrYo2pT4suxHKMJdK1d');
|
|
260
260
|
publicKey = await manager.createAccount(space, owner);
|
|
261
261
|
if (options.debug) {
|
|
262
262
|
console.log(` ${spec.name} (readonly): ${publicKey.toString()}`);
|
|
@@ -13,6 +13,8 @@ export interface VSourceTest {
|
|
|
13
13
|
file: string;
|
|
14
14
|
functionName: string;
|
|
15
15
|
parameters?: any[];
|
|
16
|
+
expectedResult?: any;
|
|
17
|
+
expectsResult?: boolean;
|
|
16
18
|
description?: string;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
@@ -36,6 +38,8 @@ export interface DiscoveredTest {
|
|
|
36
38
|
source?: VSourceTest;
|
|
37
39
|
description?: string;
|
|
38
40
|
parameters?: any[];
|
|
41
|
+
expectedResult?: any;
|
|
42
|
+
expectsResult?: boolean;
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
41
45
|
* Discover tests from directory
|
|
@@ -64,6 +68,8 @@ export declare class TestDiscovery {
|
|
|
64
68
|
* Parse .v source file for test functions and parameters
|
|
65
69
|
*/
|
|
66
70
|
private static parseVFile;
|
|
71
|
+
private static splitParamsAndExpectation;
|
|
72
|
+
private static parseTokenValue;
|
|
67
73
|
/**
|
|
68
74
|
* Compile a .v test source file
|
|
69
75
|
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* extracts parameters from @test-params comments, and compiles source files.
|
|
7
7
|
*/
|
|
8
8
|
import { readFile, readdir, stat } from 'fs/promises';
|
|
9
|
-
import {
|
|
9
|
+
import { basename, join } from 'path';
|
|
10
10
|
import { FiveSDK } from '../FiveSDK.js';
|
|
11
11
|
/**
|
|
12
12
|
* Discover tests from directory
|
|
@@ -121,80 +121,62 @@ export class TestDiscovery {
|
|
|
121
121
|
const tests = [];
|
|
122
122
|
try {
|
|
123
123
|
const content = await readFile(file, 'utf8');
|
|
124
|
-
// Find all function definitions with potential test annotations
|
|
125
|
-
// Pattern 1: pub function with #[test] annotation
|
|
126
|
-
// Pattern 2: function with specific naming convention (test_*, *_test)
|
|
127
124
|
const lines = content.split('\n');
|
|
128
|
-
let
|
|
129
|
-
let currentParams = null;
|
|
130
|
-
let currentDescription = null;
|
|
125
|
+
let pendingParams;
|
|
131
126
|
for (let i = 0; i < lines.length; i++) {
|
|
132
127
|
const line = lines[i].trim();
|
|
133
128
|
// Check for @test-params comment
|
|
134
|
-
const paramsMatch = line.match(/@test-params
|
|
129
|
+
const paramsMatch = line.match(/@test-params(?:\s+(.*))?$/);
|
|
135
130
|
if (paramsMatch) {
|
|
136
131
|
try {
|
|
137
|
-
const paramsStr = paramsMatch[1].trim();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
const paramsStr = (paramsMatch[1] || '').trim();
|
|
133
|
+
if (paramsStr.length === 0) {
|
|
134
|
+
pendingParams = [];
|
|
135
|
+
}
|
|
136
|
+
else if (paramsStr.startsWith('[')) {
|
|
137
|
+
const parsed = JSON.parse(paramsStr);
|
|
138
|
+
pendingParams = Array.isArray(parsed) ? parsed : [];
|
|
141
139
|
}
|
|
142
140
|
else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return Number(p);
|
|
148
|
-
}
|
|
149
|
-
return p;
|
|
150
|
-
});
|
|
141
|
+
pendingParams = paramsStr
|
|
142
|
+
.split(/\s+/)
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.map((token) => this.parseTokenValue(token));
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
catch (error) {
|
|
154
148
|
console.warn(`Failed to parse @test-params in ${file}:${i + 1}: ${line}`);
|
|
149
|
+
pendingParams = undefined;
|
|
155
150
|
}
|
|
156
151
|
continue;
|
|
157
152
|
}
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const nextLine = lines[j].trim();
|
|
163
|
-
if (nextLine && !nextLine.startsWith('//')) {
|
|
164
|
-
const funcMatch = nextLine.match(/(?:pub\s+)?(?:fn|instruction|script)\s+(\w+)\s*\(/);
|
|
165
|
-
if (funcMatch) {
|
|
166
|
-
currentFunction = funcMatch[1];
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
// Check for pub function that matches test naming convention
|
|
174
|
-
const funcMatch = line.match(/pub\s+(?:fn|instruction|script)\s+(test_\w+|_?\w+_test)\s*\(/);
|
|
153
|
+
// Match canonical DSL test function forms:
|
|
154
|
+
// pub test_name(...)
|
|
155
|
+
// pub fn test_name(...)
|
|
156
|
+
const funcMatch = line.match(/^pub\s+(?:fn\s+)?(test_[A-Za-z0-9_]*|[A-Za-z0-9_]*_test)\s*\([^)]*\)\s*(?:->\s*([A-Za-z0-9_<>\[\]]+))?/);
|
|
175
157
|
if (funcMatch) {
|
|
176
158
|
const functionName = funcMatch[1];
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
159
|
+
const returnType = funcMatch[2];
|
|
160
|
+
const hasReturnValue = !!returnType;
|
|
161
|
+
const [parameters, expectedResult, expectsResult] = this.splitParamsAndExpectation(pendingParams, hasReturnValue);
|
|
162
|
+
const name = `${basename(file, '.v')}::${functionName}`;
|
|
163
|
+
tests.push({
|
|
164
|
+
name,
|
|
165
|
+
path: file,
|
|
166
|
+
type: 'v-source',
|
|
167
|
+
source: {
|
|
183
168
|
type: 'v-source',
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
currentDescription = null;
|
|
196
|
-
currentFunction = null;
|
|
197
|
-
}
|
|
169
|
+
file,
|
|
170
|
+
functionName,
|
|
171
|
+
parameters: parameters.length > 0 ? parameters : undefined,
|
|
172
|
+
expectedResult,
|
|
173
|
+
expectsResult
|
|
174
|
+
},
|
|
175
|
+
parameters: parameters.length > 0 ? parameters : undefined,
|
|
176
|
+
expectedResult,
|
|
177
|
+
expectsResult
|
|
178
|
+
});
|
|
179
|
+
pendingParams = undefined;
|
|
198
180
|
}
|
|
199
181
|
}
|
|
200
182
|
}
|
|
@@ -203,6 +185,29 @@ export class TestDiscovery {
|
|
|
203
185
|
}
|
|
204
186
|
return tests;
|
|
205
187
|
}
|
|
188
|
+
static splitParamsAndExpectation(values, hasReturnValue) {
|
|
189
|
+
const parsed = Array.isArray(values) ? values : [];
|
|
190
|
+
if (!hasReturnValue || parsed.length === 0) {
|
|
191
|
+
return [parsed, undefined, false];
|
|
192
|
+
}
|
|
193
|
+
const params = parsed.slice(0, parsed.length - 1);
|
|
194
|
+
return [params, parsed[parsed.length - 1], true];
|
|
195
|
+
}
|
|
196
|
+
static parseTokenValue(token) {
|
|
197
|
+
if ((token.startsWith('"') && token.endsWith('"')) ||
|
|
198
|
+
(token.startsWith("'") && token.endsWith("'"))) {
|
|
199
|
+
return token.slice(1, -1);
|
|
200
|
+
}
|
|
201
|
+
if (token === 'true')
|
|
202
|
+
return true;
|
|
203
|
+
if (token === 'false')
|
|
204
|
+
return false;
|
|
205
|
+
const asNumber = Number(token);
|
|
206
|
+
if (!Number.isNaN(asNumber)) {
|
|
207
|
+
return asNumber;
|
|
208
|
+
}
|
|
209
|
+
return token;
|
|
210
|
+
}
|
|
206
211
|
/**
|
|
207
212
|
* Compile a .v test source file
|
|
208
213
|
*/
|