@5ive-tech/sdk 1.1.2
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 +279 -0
- package/dist/FiveSDK.d.ts +336 -0
- package/dist/FiveSDK.js +395 -0
- package/dist/accounts/index.d.ts +254 -0
- package/dist/accounts/index.js +543 -0
- package/dist/assets/vm/dummy.file +0 -0
- package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
- package/dist/assets/vm/five_vm_wasm.js +3754 -0
- package/dist/assets/vm/five_vm_wasm_bg.js +3307 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
- package/dist/assets/vm/package.json +11 -0
- package/dist/bin/gen-types.d.ts +2 -0
- package/dist/bin/gen-types.js +35 -0
- package/dist/compiler/BytecodeCompiler.d.ts +83 -0
- package/dist/compiler/BytecodeCompiler.js +379 -0
- package/dist/config/ConfigManager.d.ts +13 -0
- package/dist/config/ConfigManager.js +27 -0
- package/dist/config/ProgramIdResolver.d.ts +62 -0
- package/dist/config/ProgramIdResolver.js +104 -0
- package/dist/crypto/index.d.ts +211 -0
- package/dist/crypto/index.js +451 -0
- package/dist/encoding/ParameterEncoder.d.ts +31 -0
- package/dist/encoding/ParameterEncoder.js +278 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +28 -0
- package/dist/lib/bytecode-encoder.d.ts +62 -0
- package/dist/lib/bytecode-encoder.js +281 -0
- package/dist/logging/index.d.ts +9 -0
- package/dist/logging/index.js +10 -0
- package/dist/metadata/index.d.ts +213 -0
- package/dist/metadata/index.js +296 -0
- package/dist/modules/accounts.d.ts +60 -0
- package/dist/modules/accounts.js +275 -0
- package/dist/modules/deploy.d.ts +90 -0
- package/dist/modules/deploy.js +1118 -0
- package/dist/modules/execute.d.ts +90 -0
- package/dist/modules/execute.js +649 -0
- package/dist/modules/fees.d.ts +14 -0
- package/dist/modules/fees.js +112 -0
- package/dist/modules/namespaces.d.ts +39 -0
- package/dist/modules/namespaces.js +190 -0
- package/dist/modules/state-diff.d.ts +35 -0
- package/dist/modules/state-diff.js +342 -0
- package/dist/modules/vm-state.d.ts +7 -0
- package/dist/modules/vm-state.js +44 -0
- package/dist/program/AccountResolver.d.ts +67 -0
- package/dist/program/AccountResolver.js +134 -0
- package/dist/program/BorshSchemaGenerator.d.ts +8 -0
- package/dist/program/BorshSchemaGenerator.js +57 -0
- package/dist/program/FiveProgram.d.ts +144 -0
- package/dist/program/FiveProgram.js +282 -0
- package/dist/program/FunctionBuilder.d.ts +114 -0
- package/dist/program/FunctionBuilder.js +347 -0
- package/dist/program/ProgramAccount.d.ts +38 -0
- package/dist/program/ProgramAccount.js +170 -0
- package/dist/program/TypeGenerator.d.ts +90 -0
- package/dist/program/TypeGenerator.js +195 -0
- package/dist/program/index.d.ts +24 -0
- package/dist/program/index.js +21 -0
- package/dist/project/config.d.ts +5 -0
- package/dist/project/config.js +33 -0
- package/dist/project/toml.d.ts +6 -0
- package/dist/project/toml.js +43 -0
- package/dist/project/workspace.d.ts +160 -0
- package/dist/project/workspace.js +73 -0
- package/dist/testing/AccountMetaGenerator.d.ts +121 -0
- package/dist/testing/AccountMetaGenerator.js +261 -0
- package/dist/testing/AccountTestFixture.d.ts +211 -0
- package/dist/testing/AccountTestFixture.js +530 -0
- package/dist/testing/OnChainAccountManager.d.ts +81 -0
- package/dist/testing/OnChainAccountManager.js +260 -0
- package/dist/testing/StateSerializer.d.ts +65 -0
- package/dist/testing/StateSerializer.js +330 -0
- package/dist/testing/TestDiscovery.d.ts +79 -0
- package/dist/testing/TestDiscovery.js +274 -0
- package/dist/testing/TestRunner.d.ts +117 -0
- package/dist/testing/TestRunner.js +346 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +13 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.js +32 -0
- package/dist/utils/abi.d.ts +31 -0
- package/dist/utils/abi.js +92 -0
- package/dist/utils/transaction.d.ts +5 -0
- package/dist/utils/transaction.js +48 -0
- package/dist/validation/InputValidator.d.ts +142 -0
- package/dist/validation/InputValidator.js +332 -0
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.js +4 -0
- package/dist/wasm/compiler/AbiLogic.d.ts +4 -0
- package/dist/wasm/compiler/AbiLogic.js +37 -0
- package/dist/wasm/compiler/AnalysisLogic.d.ts +6 -0
- package/dist/wasm/compiler/AnalysisLogic.js +61 -0
- package/dist/wasm/compiler/CompilationLogic.d.ts +10 -0
- package/dist/wasm/compiler/CompilationLogic.js +431 -0
- package/dist/wasm/compiler/FiveCompiler.d.ts +48 -0
- package/dist/wasm/compiler/FiveCompiler.js +183 -0
- package/dist/wasm/compiler/InfoLogic.d.ts +6 -0
- package/dist/wasm/compiler/InfoLogic.js +24 -0
- package/dist/wasm/compiler/OptimizationLogic.d.ts +2 -0
- package/dist/wasm/compiler/OptimizationLogic.js +13 -0
- package/dist/wasm/compiler/ValidationLogic.d.ts +7 -0
- package/dist/wasm/compiler/ValidationLogic.js +26 -0
- package/dist/wasm/compiler/index.d.ts +2 -0
- package/dist/wasm/compiler/index.js +2 -0
- package/dist/wasm/compiler/types.d.ts +8 -0
- package/dist/wasm/compiler/types.js +1 -0
- package/dist/wasm/compiler/utils.d.ts +8 -0
- package/dist/wasm/compiler/utils.js +75 -0
- package/dist/wasm/index.d.ts +9 -0
- package/dist/wasm/index.js +12 -0
- package/dist/wasm/instance.d.ts +1 -0
- package/dist/wasm/instance.js +26 -0
- package/dist/wasm/loader.d.ts +7 -0
- package/dist/wasm/loader.js +112 -0
- package/dist/wasm/vm.d.ts +33 -0
- package/dist/wasm/vm.js +250 -0
- package/package.json +59 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-Chain Account Manager for Five VM Account-System Testing
|
|
3
|
+
*
|
|
4
|
+
* Creates and manages real Solana accounts on-chain for comprehensive account-system testing.
|
|
5
|
+
* Handles account creation, funding, initialization, and cleanup.
|
|
6
|
+
*/
|
|
7
|
+
import { Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
8
|
+
import { RentCalculator } from '../crypto/index.js';
|
|
9
|
+
import { PDAUtils } from '../crypto/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Manages creation and lifecycle of real Solana accounts for testing
|
|
12
|
+
*/
|
|
13
|
+
export class OnChainAccountManager {
|
|
14
|
+
constructor(connection, payer, options = {}) {
|
|
15
|
+
this.connection = connection;
|
|
16
|
+
this.payer = payer;
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.createdAccounts = [];
|
|
19
|
+
this.signers = new Map();
|
|
20
|
+
this.options.maxRetries = this.options.maxRetries || 3;
|
|
21
|
+
this.options.retryDelay = this.options.retryDelay || 1000;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a signer account with generated keypair
|
|
25
|
+
* Transfers SOL from payer to fund the account
|
|
26
|
+
*/
|
|
27
|
+
async createSignerAccount(lamports = LAMPORTS_PER_SOL // 1 SOL default
|
|
28
|
+
) {
|
|
29
|
+
const keypair = Keypair.generate();
|
|
30
|
+
const publicKey = keypair.publicKey;
|
|
31
|
+
if (this.options.debug) {
|
|
32
|
+
console.log(`[OnChainAccountManager] Creating signer account: ${publicKey.toString()}`);
|
|
33
|
+
}
|
|
34
|
+
// Ensure payer has sufficient balance
|
|
35
|
+
await this.ensureSufficientBalance(lamports);
|
|
36
|
+
// Transfer SOL from payer to new account
|
|
37
|
+
const transaction = new Transaction().add(SystemProgram.transfer({
|
|
38
|
+
fromPubkey: this.payer.publicKey,
|
|
39
|
+
toPubkey: publicKey,
|
|
40
|
+
lamports: lamports,
|
|
41
|
+
}));
|
|
42
|
+
try {
|
|
43
|
+
const signature = await sendAndConfirmTransaction(this.connection, transaction, [this.payer], { commitment: 'confirmed' });
|
|
44
|
+
if (this.options.debug) {
|
|
45
|
+
console.log(`[OnChainAccountManager] Signer account funded: ${signature}`);
|
|
46
|
+
}
|
|
47
|
+
this.createdAccounts.push(publicKey);
|
|
48
|
+
this.signers.set(publicKey.toString(), keypair);
|
|
49
|
+
return { publicKey, keypair };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
53
|
+
throw new Error(`Failed to create signer account: ${errorMessage}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a regular account with specified space and owner
|
|
58
|
+
* Uses SystemProgram.createAccount for account creation
|
|
59
|
+
*/
|
|
60
|
+
async createAccount(space, owner, lamports) {
|
|
61
|
+
const keypair = Keypair.generate();
|
|
62
|
+
const publicKey = keypair.publicKey;
|
|
63
|
+
// Calculate rent-exempt lamports if not provided
|
|
64
|
+
const requiredLamports = lamports || RentCalculator.calculateRentExemption(space);
|
|
65
|
+
if (this.options.debug) {
|
|
66
|
+
console.log(`[OnChainAccountManager] Creating account: ${publicKey.toString()} ` +
|
|
67
|
+
`(space=${space}, lamports=${requiredLamports})`);
|
|
68
|
+
}
|
|
69
|
+
// Ensure payer has sufficient balance
|
|
70
|
+
await this.ensureSufficientBalance(requiredLamports);
|
|
71
|
+
// Create the account using SystemProgram
|
|
72
|
+
const transaction = new Transaction().add(SystemProgram.createAccount({
|
|
73
|
+
fromPubkey: this.payer.publicKey,
|
|
74
|
+
newAccountPubkey: publicKey,
|
|
75
|
+
lamports: requiredLamports,
|
|
76
|
+
space: space,
|
|
77
|
+
programId: owner,
|
|
78
|
+
}));
|
|
79
|
+
try {
|
|
80
|
+
const signature = await sendAndConfirmTransaction(this.connection, transaction, [this.payer, keypair], { commitment: 'confirmed' });
|
|
81
|
+
if (this.options.debug) {
|
|
82
|
+
console.log(`[OnChainAccountManager] Account created: ${signature}`);
|
|
83
|
+
}
|
|
84
|
+
this.createdAccounts.push(publicKey);
|
|
85
|
+
return publicKey;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
+
throw new Error(`Failed to create account: ${errorMessage}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create and initialize a state account with initial data
|
|
94
|
+
*/
|
|
95
|
+
async createStateAccount(space, owner, initialData, lamports) {
|
|
96
|
+
const accountAddress = await this.createAccount(space, owner, lamports);
|
|
97
|
+
// Write initial data if provided
|
|
98
|
+
if (initialData && initialData.length > 0) {
|
|
99
|
+
await this.writeAccountData(accountAddress, initialData);
|
|
100
|
+
}
|
|
101
|
+
if (this.options.debug) {
|
|
102
|
+
console.log(`[OnChainAccountManager] State account initialized: ${accountAddress.toString()}`);
|
|
103
|
+
}
|
|
104
|
+
return accountAddress;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a PDA account at a specific seed path
|
|
108
|
+
*/
|
|
109
|
+
async createPDAAccount(seeds, programId, space, owner, lamports) {
|
|
110
|
+
const pda = await PDAUtils.findProgramAddress(seeds, programId.toString());
|
|
111
|
+
const pdaAddress = new PublicKey(pda.address);
|
|
112
|
+
if (this.options.debug) {
|
|
113
|
+
console.log(`[OnChainAccountManager] Creating PDA: ${pdaAddress.toString()} ` +
|
|
114
|
+
`(bump=${pda.bump})`);
|
|
115
|
+
}
|
|
116
|
+
// Check if PDA already exists
|
|
117
|
+
const exists = await this.checkAccountExists(pdaAddress);
|
|
118
|
+
if (exists) {
|
|
119
|
+
if (this.options.debug) {
|
|
120
|
+
console.log(`[OnChainAccountManager] PDA already exists, skipping creation`);
|
|
121
|
+
}
|
|
122
|
+
return { publicKey: pdaAddress, bump: pda.bump };
|
|
123
|
+
}
|
|
124
|
+
// Create PDA account
|
|
125
|
+
const createdAddress = await this.createAccount(space, owner || programId, lamports);
|
|
126
|
+
return { publicKey: createdAddress, bump: pda.bump };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Write data to an account (requires account to be writable)
|
|
130
|
+
*/
|
|
131
|
+
async writeAccountData(accountAddress, data) {
|
|
132
|
+
if (this.options.debug) {
|
|
133
|
+
console.log(`[OnChainAccountManager] Writing ${data.length} bytes to account: ${accountAddress.toString()}`);
|
|
134
|
+
}
|
|
135
|
+
// Note: Writing data directly to an account requires special instructions
|
|
136
|
+
// This is a simplified implementation. In practice, you would use the Five VM program
|
|
137
|
+
// or SystemProgram.allocate + loader to write data.
|
|
138
|
+
// Log that data would be written.
|
|
139
|
+
if (this.options.debug) {
|
|
140
|
+
console.log(`[OnChainAccountManager] Data would be written via Five VM program`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if an account exists
|
|
145
|
+
*/
|
|
146
|
+
async checkAccountExists(publicKey) {
|
|
147
|
+
try {
|
|
148
|
+
const accountInfo = await this.connection.getAccountInfo(publicKey);
|
|
149
|
+
return accountInfo !== null;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (this.options.debug) {
|
|
153
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
154
|
+
console.log(`[OnChainAccountManager] Error checking account: ${errorMessage}`);
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Ensure payer has sufficient balance
|
|
161
|
+
*/
|
|
162
|
+
async ensureSufficientBalance(required) {
|
|
163
|
+
const balance = await this.connection.getBalance(this.payer.publicKey);
|
|
164
|
+
// Add buffer for transaction fees (0.01 SOL)
|
|
165
|
+
const feeBuffer = 0.01 * LAMPORTS_PER_SOL;
|
|
166
|
+
const totalRequired = required + feeBuffer;
|
|
167
|
+
if (balance < totalRequired) {
|
|
168
|
+
throw new Error(`Insufficient balance: ${(balance / LAMPORTS_PER_SOL).toFixed(4)} SOL available, ` +
|
|
169
|
+
`${(totalRequired / LAMPORTS_PER_SOL).toFixed(4)} SOL required. ` +
|
|
170
|
+
`Please fund the payer account: ${this.payer.publicKey.toString()}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create account with retry logic
|
|
175
|
+
*/
|
|
176
|
+
async createAccountWithRetry(createFn, options = {}) {
|
|
177
|
+
const maxRetries = options.maxRetries || this.options.maxRetries || 3;
|
|
178
|
+
const retryDelay = options.retryDelay || this.options.retryDelay || 1000;
|
|
179
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
180
|
+
try {
|
|
181
|
+
return await createFn();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
185
|
+
if (attempt === maxRetries) {
|
|
186
|
+
throw new Error(`Failed to create account after ${maxRetries} attempts: ${errorMessage}`);
|
|
187
|
+
}
|
|
188
|
+
if (this.options.debug) {
|
|
189
|
+
console.log(`[OnChainAccountManager] Account creation failed (attempt ${attempt}/${maxRetries}), ` +
|
|
190
|
+
`retrying in ${retryDelay}ms...`);
|
|
191
|
+
}
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
throw new Error('Unreachable');
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get a signer keypair if it was created by this manager
|
|
199
|
+
*/
|
|
200
|
+
getSignerKeypair(publicKey) {
|
|
201
|
+
const key = typeof publicKey === 'string' ? publicKey : publicKey.toString();
|
|
202
|
+
return this.signers.get(key);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get all created accounts
|
|
206
|
+
*/
|
|
207
|
+
getCreatedAccounts() {
|
|
208
|
+
return [...this.createdAccounts];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Cleanup: Close all created accounts and transfer remaining SOL back to payer
|
|
212
|
+
* This helps with test isolation and prevents account accumulation on testnet
|
|
213
|
+
*/
|
|
214
|
+
async cleanup() {
|
|
215
|
+
if (!this.options.cleanup) {
|
|
216
|
+
if (this.options.debug) {
|
|
217
|
+
console.log(`[OnChainAccountManager] Cleanup disabled, skipping`);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.options.debug) {
|
|
222
|
+
console.log(`[OnChainAccountManager] Cleaning up ${this.createdAccounts.length} accounts`);
|
|
223
|
+
}
|
|
224
|
+
// Close accounts and transfer lamports back to payer
|
|
225
|
+
for (const accountAddress of this.createdAccounts) {
|
|
226
|
+
try {
|
|
227
|
+
// Get account info to determine if it's an account we can close
|
|
228
|
+
const accountInfo = await this.connection.getAccountInfo(accountAddress);
|
|
229
|
+
if (accountInfo) {
|
|
230
|
+
// Only try to close accounts owned by SystemProgram (data accounts)
|
|
231
|
+
// Don't try to close program accounts or special accounts
|
|
232
|
+
if (accountInfo.owner.equals(SystemProgram.programId)) {
|
|
233
|
+
const transaction = new Transaction().add(SystemProgram.transfer({
|
|
234
|
+
fromPubkey: accountAddress,
|
|
235
|
+
toPubkey: this.payer.publicKey,
|
|
236
|
+
lamports: accountInfo.lamports,
|
|
237
|
+
}));
|
|
238
|
+
const signer = this.getSignerKeypair(accountAddress);
|
|
239
|
+
if (signer) {
|
|
240
|
+
await sendAndConfirmTransaction(this.connection, transaction, [signer], { commitment: 'confirmed' });
|
|
241
|
+
if (this.options.debug) {
|
|
242
|
+
console.log(`[OnChainAccountManager] Closed account: ${accountAddress.toString()}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
250
|
+
if (this.options.debug) {
|
|
251
|
+
console.log(`[OnChainAccountManager] Error closing account ${accountAddress.toString()}: ` +
|
|
252
|
+
`${errorMessage}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
this.createdAccounts = [];
|
|
257
|
+
this.signers.clear();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
export default OnChainAccountManager;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Serializer for Five VM Account Data
|
|
3
|
+
*
|
|
4
|
+
* Serializes state account data based on Five account field definitions.
|
|
5
|
+
* Handles conversion of JavaScript types to Five VM bytecode format.
|
|
6
|
+
*/
|
|
7
|
+
export interface StateFieldDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
}
|
|
11
|
+
export interface StateDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
fields: StateFieldDefinition[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Serializes state account data to Five VM bytecode format
|
|
17
|
+
*/
|
|
18
|
+
export declare class StateSerializer {
|
|
19
|
+
/**
|
|
20
|
+
* Serialize complete state object based on definition
|
|
21
|
+
*/
|
|
22
|
+
static serialize(stateDefinition: StateDefinition, data: Record<string, any>, options?: {
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
}): Uint8Array;
|
|
25
|
+
/**
|
|
26
|
+
* Serialize a single field value based on its type
|
|
27
|
+
*/
|
|
28
|
+
static serializeField(type: string, value: any, options?: {
|
|
29
|
+
debug?: boolean;
|
|
30
|
+
}): Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* Serialize an integer value (u8, u16, u32, u64, i8, i16, i32, i64)
|
|
33
|
+
*/
|
|
34
|
+
private static serializeInteger;
|
|
35
|
+
/**
|
|
36
|
+
* Serialize a public key (base58 to 32-byte array)
|
|
37
|
+
*/
|
|
38
|
+
private static serializePubkey;
|
|
39
|
+
/**
|
|
40
|
+
* Serialize a UTF-8 string with length prefix
|
|
41
|
+
* Format: u32 length (little-endian) + UTF-8 bytes
|
|
42
|
+
*/
|
|
43
|
+
private static serializeString;
|
|
44
|
+
/**
|
|
45
|
+
* Serialize an array of values
|
|
46
|
+
*/
|
|
47
|
+
private static serializeArray;
|
|
48
|
+
/**
|
|
49
|
+
* Get default value for a type
|
|
50
|
+
*/
|
|
51
|
+
private static getDefaultValue;
|
|
52
|
+
/**
|
|
53
|
+
* Convert byte array to hex string for debugging
|
|
54
|
+
*/
|
|
55
|
+
private static toHexString;
|
|
56
|
+
/**
|
|
57
|
+
* Calculate total size of a state based on definition
|
|
58
|
+
*/
|
|
59
|
+
static calculateSize(stateDefinition: StateDefinition): number;
|
|
60
|
+
/**
|
|
61
|
+
* Get size of a single field type in bytes
|
|
62
|
+
*/
|
|
63
|
+
private static getFieldSize;
|
|
64
|
+
}
|
|
65
|
+
export default StateSerializer;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Serializer for Five VM Account Data
|
|
3
|
+
*
|
|
4
|
+
* Serializes state account data based on Five account field definitions.
|
|
5
|
+
* Handles conversion of JavaScript types to Five VM bytecode format.
|
|
6
|
+
*/
|
|
7
|
+
import { Base58Utils } from '../crypto/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Serializes state account data to Five VM bytecode format
|
|
10
|
+
*/
|
|
11
|
+
export class StateSerializer {
|
|
12
|
+
/**
|
|
13
|
+
* Serialize complete state object based on definition
|
|
14
|
+
*/
|
|
15
|
+
static serialize(stateDefinition, data, options = {}) {
|
|
16
|
+
if (options.debug) {
|
|
17
|
+
console.log(`[StateSerializer] Serializing ${stateDefinition.name} with ${stateDefinition.fields.length} fields`);
|
|
18
|
+
}
|
|
19
|
+
// Collect all field buffers
|
|
20
|
+
let totalSize = 0;
|
|
21
|
+
const fieldBuffers = [];
|
|
22
|
+
for (const field of stateDefinition.fields) {
|
|
23
|
+
const value = data[field.name];
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
if (options.debug) {
|
|
26
|
+
console.warn(`[StateSerializer] Field "${field.name}" not provided, using default value for type ${field.type}`);
|
|
27
|
+
}
|
|
28
|
+
// Use default value based on type
|
|
29
|
+
const defaultValue = this.getDefaultValue(field.type);
|
|
30
|
+
const buffer = this.serializeField(field.type, defaultValue, options);
|
|
31
|
+
fieldBuffers.push(buffer);
|
|
32
|
+
totalSize += buffer.length;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const buffer = this.serializeField(field.type, value, options);
|
|
36
|
+
fieldBuffers.push(buffer);
|
|
37
|
+
totalSize += buffer.length;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Concatenate all field buffers in order
|
|
41
|
+
const result = new Uint8Array(totalSize);
|
|
42
|
+
let offset = 0;
|
|
43
|
+
for (const buffer of fieldBuffers) {
|
|
44
|
+
result.set(buffer, offset);
|
|
45
|
+
offset += buffer.length;
|
|
46
|
+
}
|
|
47
|
+
if (options.debug) {
|
|
48
|
+
console.log(`[StateSerializer] Serialized to ${totalSize} bytes, hex: ${this.toHexString(result)}`);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Serialize a single field value based on its type
|
|
54
|
+
*/
|
|
55
|
+
static serializeField(type, value, options = {}) {
|
|
56
|
+
// Normalize type name (remove whitespace, convert to lowercase)
|
|
57
|
+
const normalizedType = type.toLowerCase().trim();
|
|
58
|
+
if (options.debug) {
|
|
59
|
+
console.log(`[StateSerializer] Serializing field type="${normalizedType}" value="${value}"`);
|
|
60
|
+
}
|
|
61
|
+
// Handle integer types
|
|
62
|
+
if (normalizedType === 'u8' || normalizedType === 'u16' ||
|
|
63
|
+
normalizedType === 'u32' || normalizedType === 'u64' ||
|
|
64
|
+
normalizedType === 'i8' || normalizedType === 'i16' ||
|
|
65
|
+
normalizedType === 'i32' || normalizedType === 'i64') {
|
|
66
|
+
return this.serializeInteger(normalizedType, value);
|
|
67
|
+
}
|
|
68
|
+
// Handle boolean type
|
|
69
|
+
if (normalizedType === 'bool' || normalizedType === 'boolean') {
|
|
70
|
+
return new Uint8Array([value ? 1 : 0]);
|
|
71
|
+
}
|
|
72
|
+
// Handle public key type
|
|
73
|
+
if (normalizedType === 'pubkey' || normalizedType === 'publickey' ||
|
|
74
|
+
normalizedType === 'publickey' || normalizedType === 'account') {
|
|
75
|
+
return this.serializePubkey(value);
|
|
76
|
+
}
|
|
77
|
+
// Handle string type (UTF-8 encoded with length prefix)
|
|
78
|
+
if (normalizedType === 'string') {
|
|
79
|
+
return this.serializeString(value);
|
|
80
|
+
}
|
|
81
|
+
// Handle array types
|
|
82
|
+
if (normalizedType.endsWith('[]')) {
|
|
83
|
+
const elementType = normalizedType.slice(0, -2);
|
|
84
|
+
return this.serializeArray(elementType, value, options);
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Serialize an integer value (u8, u16, u32, u64, i8, i16, i32, i64)
|
|
90
|
+
*/
|
|
91
|
+
static serializeInteger(type, value) {
|
|
92
|
+
let numValue;
|
|
93
|
+
if (typeof value === 'string') {
|
|
94
|
+
// Try parsing as BigInt first for 64-bit values
|
|
95
|
+
try {
|
|
96
|
+
numValue = BigInt(value);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
numValue = parseInt(value, 10);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
numValue = value;
|
|
104
|
+
}
|
|
105
|
+
// Determine size based on type
|
|
106
|
+
let size;
|
|
107
|
+
let isSigned;
|
|
108
|
+
switch (type) {
|
|
109
|
+
case 'u8':
|
|
110
|
+
size = 1;
|
|
111
|
+
isSigned = false;
|
|
112
|
+
break;
|
|
113
|
+
case 'u16':
|
|
114
|
+
size = 2;
|
|
115
|
+
isSigned = false;
|
|
116
|
+
break;
|
|
117
|
+
case 'u32':
|
|
118
|
+
size = 4;
|
|
119
|
+
isSigned = false;
|
|
120
|
+
break;
|
|
121
|
+
case 'u64':
|
|
122
|
+
size = 8;
|
|
123
|
+
isSigned = false;
|
|
124
|
+
break;
|
|
125
|
+
case 'i8':
|
|
126
|
+
size = 1;
|
|
127
|
+
isSigned = true;
|
|
128
|
+
break;
|
|
129
|
+
case 'i16':
|
|
130
|
+
size = 2;
|
|
131
|
+
isSigned = true;
|
|
132
|
+
break;
|
|
133
|
+
case 'i32':
|
|
134
|
+
size = 4;
|
|
135
|
+
isSigned = true;
|
|
136
|
+
break;
|
|
137
|
+
case 'i64':
|
|
138
|
+
size = 8;
|
|
139
|
+
isSigned = true;
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
throw new Error(`Unsupported integer type: ${type}`);
|
|
143
|
+
}
|
|
144
|
+
const buffer = new Uint8Array(size);
|
|
145
|
+
const view = new DataView(buffer.buffer);
|
|
146
|
+
// Write value in little-endian format
|
|
147
|
+
if (size === 8 && typeof numValue === 'bigint') {
|
|
148
|
+
if (isSigned) {
|
|
149
|
+
view.setBigInt64(0, numValue, true);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
view.setBigUint64(0, numValue, true);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const numericValue = typeof numValue === 'bigint' ? Number(numValue) : numValue;
|
|
157
|
+
switch (type) {
|
|
158
|
+
case 'u8':
|
|
159
|
+
view.setUint8(0, numericValue);
|
|
160
|
+
break;
|
|
161
|
+
case 'u16':
|
|
162
|
+
view.setUint16(0, numericValue, true);
|
|
163
|
+
break;
|
|
164
|
+
case 'u32':
|
|
165
|
+
view.setUint32(0, numericValue, true);
|
|
166
|
+
break;
|
|
167
|
+
case 'u64':
|
|
168
|
+
view.setBigUint64(0, BigInt(numericValue), true);
|
|
169
|
+
break;
|
|
170
|
+
case 'i8':
|
|
171
|
+
view.setInt8(0, numericValue);
|
|
172
|
+
break;
|
|
173
|
+
case 'i16':
|
|
174
|
+
view.setInt16(0, numericValue, true);
|
|
175
|
+
break;
|
|
176
|
+
case 'i32':
|
|
177
|
+
view.setInt32(0, numericValue, true);
|
|
178
|
+
break;
|
|
179
|
+
case 'i64':
|
|
180
|
+
view.setBigInt64(0, BigInt(numericValue), true);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return buffer;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Serialize a public key (base58 to 32-byte array)
|
|
188
|
+
*/
|
|
189
|
+
static serializePubkey(value) {
|
|
190
|
+
if (value instanceof Uint8Array) {
|
|
191
|
+
if (value.length !== 32) {
|
|
192
|
+
throw new Error(`Invalid pubkey: expected 32 bytes, got ${value.length}`);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
// Assume base58-encoded public key string
|
|
198
|
+
const decoded = Base58Utils.decode(value);
|
|
199
|
+
if (decoded.length !== 32) {
|
|
200
|
+
throw new Error(`Invalid pubkey: expected 32 bytes after decoding, got ${decoded.length}`);
|
|
201
|
+
}
|
|
202
|
+
return decoded;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
206
|
+
throw new Error(`Failed to decode pubkey: ${errorMessage}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Serialize a UTF-8 string with length prefix
|
|
211
|
+
* Format: u32 length (little-endian) + UTF-8 bytes
|
|
212
|
+
*/
|
|
213
|
+
static serializeString(value) {
|
|
214
|
+
const encoder = new TextEncoder();
|
|
215
|
+
const stringBytes = encoder.encode(value);
|
|
216
|
+
const length = stringBytes.length;
|
|
217
|
+
// Create buffer: 4 bytes for length + string bytes
|
|
218
|
+
const buffer = new Uint8Array(4 + length);
|
|
219
|
+
const view = new DataView(buffer.buffer);
|
|
220
|
+
// Write length as u32 in little-endian
|
|
221
|
+
view.setUint32(0, length, true);
|
|
222
|
+
// Write string bytes
|
|
223
|
+
buffer.set(stringBytes, 4);
|
|
224
|
+
return buffer;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Serialize an array of values
|
|
228
|
+
*/
|
|
229
|
+
static serializeArray(elementType, values, options = {}) {
|
|
230
|
+
const elementBuffers = [];
|
|
231
|
+
let totalSize = 4; // 4 bytes for array length
|
|
232
|
+
// First, serialize all elements
|
|
233
|
+
for (const value of values) {
|
|
234
|
+
const buffer = this.serializeField(elementType, value, options);
|
|
235
|
+
elementBuffers.push(buffer);
|
|
236
|
+
totalSize += buffer.length;
|
|
237
|
+
}
|
|
238
|
+
// Create result buffer with length prefix
|
|
239
|
+
const result = new Uint8Array(totalSize);
|
|
240
|
+
const view = new DataView(result.buffer);
|
|
241
|
+
// Write array length as u32 in little-endian
|
|
242
|
+
view.setUint32(0, values.length, true);
|
|
243
|
+
// Write all elements
|
|
244
|
+
let offset = 4;
|
|
245
|
+
for (const buffer of elementBuffers) {
|
|
246
|
+
result.set(buffer, offset);
|
|
247
|
+
offset += buffer.length;
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get default value for a type
|
|
253
|
+
*/
|
|
254
|
+
static getDefaultValue(type) {
|
|
255
|
+
const normalizedType = type.toLowerCase().trim();
|
|
256
|
+
if (normalizedType === 'bool' || normalizedType === 'boolean') {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (normalizedType === 'u8' || normalizedType === 'u16' ||
|
|
260
|
+
normalizedType === 'u32' || normalizedType === 'u64' ||
|
|
261
|
+
normalizedType === 'i8' || normalizedType === 'i16' ||
|
|
262
|
+
normalizedType === 'i32' || normalizedType === 'i64') {
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
if (normalizedType === 'pubkey' || normalizedType === 'publickey' ||
|
|
266
|
+
normalizedType === 'account') {
|
|
267
|
+
return '11111111111111111111111111111111'; // Default system program
|
|
268
|
+
}
|
|
269
|
+
if (normalizedType === 'string') {
|
|
270
|
+
return '';
|
|
271
|
+
}
|
|
272
|
+
if (normalizedType.endsWith('[]')) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Convert byte array to hex string for debugging
|
|
279
|
+
*/
|
|
280
|
+
static toHexString(bytes) {
|
|
281
|
+
return Array.from(bytes)
|
|
282
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
283
|
+
.join('');
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Calculate total size of a state based on definition
|
|
287
|
+
*/
|
|
288
|
+
static calculateSize(stateDefinition) {
|
|
289
|
+
let totalSize = 0;
|
|
290
|
+
for (const field of stateDefinition.fields) {
|
|
291
|
+
totalSize += this.getFieldSize(field.type);
|
|
292
|
+
}
|
|
293
|
+
return totalSize;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get size of a single field type in bytes
|
|
297
|
+
*/
|
|
298
|
+
static getFieldSize(type) {
|
|
299
|
+
const normalizedType = type.toLowerCase().trim();
|
|
300
|
+
switch (normalizedType) {
|
|
301
|
+
case 'u8':
|
|
302
|
+
case 'i8':
|
|
303
|
+
case 'bool':
|
|
304
|
+
return 1;
|
|
305
|
+
case 'u16':
|
|
306
|
+
case 'i16':
|
|
307
|
+
return 2;
|
|
308
|
+
case 'u32':
|
|
309
|
+
case 'i32':
|
|
310
|
+
return 4;
|
|
311
|
+
case 'u64':
|
|
312
|
+
case 'i64':
|
|
313
|
+
return 8;
|
|
314
|
+
case 'pubkey':
|
|
315
|
+
case 'publickey':
|
|
316
|
+
case 'account':
|
|
317
|
+
return 32;
|
|
318
|
+
case 'string':
|
|
319
|
+
// Variable-length, return minimum (4 bytes for length)
|
|
320
|
+
return 4;
|
|
321
|
+
default:
|
|
322
|
+
if (normalizedType.endsWith('[]')) {
|
|
323
|
+
// Variable-length array
|
|
324
|
+
return 4; // Minimum for length prefix
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export default StateSerializer;
|