@5ive-tech/cli 1.0.4
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 +226 -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.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/cli.d.ts +47 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +343 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +872 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +431 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy-and-execute.d.ts +3 -0
- package/dist/commands/deploy-and-execute.d.ts.map +1 -0
- package/dist/commands/deploy-and-execute.js +317 -0
- package/dist/commands/deploy-and-execute.js.map +1 -0
- package/dist/commands/deploy.d.ts +21 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +806 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/donate.d.ts +4 -0
- package/dist/commands/donate.d.ts.map +1 -0
- package/dist/commands/donate.js +104 -0
- package/dist/commands/donate.js.map +1 -0
- package/dist/commands/execute.d.ts +6 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +749 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/fmt.d.ts +3 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +327 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +224 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +45 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +119 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +887 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/local.d.ts +3 -0
- package/dist/commands/local.d.ts.map +1 -0
- package/dist/commands/local.js +703 -0
- package/dist/commands/local.js.map +1 -0
- package/dist/commands/namespace.d.ts +3 -0
- package/dist/commands/namespace.d.ts.map +1 -0
- package/dist/commands/namespace.js +328 -0
- package/dist/commands/namespace.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +486 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +890 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/version.d.ts +6 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +339 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +69 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +261 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +105 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/project/ProjectLoader.d.ts +12 -0
- package/dist/project/ProjectLoader.d.ts.map +1 -0
- package/dist/project/ProjectLoader.js +115 -0
- package/dist/project/ProjectLoader.js.map +1 -0
- package/dist/types.d.ts +334 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/AccountFixtureGenerator.d.ts +48 -0
- package/dist/utils/AccountFixtureGenerator.d.ts.map +1 -0
- package/dist/utils/AccountFixtureGenerator.js +265 -0
- package/dist/utils/AccountFixtureGenerator.js.map +1 -0
- package/dist/utils/FiveFileManager.d.ts +96 -0
- package/dist/utils/FiveFileManager.d.ts.map +1 -0
- package/dist/utils/FiveFileManager.js +329 -0
- package/dist/utils/FiveFileManager.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +72 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +314 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/cli-ui.d.ts +39 -0
- package/dist/utils/cli-ui.d.ts.map +1 -0
- package/dist/utils/cli-ui.js +75 -0
- package/dist/utils/cli-ui.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +25 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +50 -0
- package/dist/utils/fileUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +53 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +287 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/compiler.d.ts +101 -0
- package/dist/wasm/compiler.d.ts.map +1 -0
- package/dist/wasm/compiler.js +906 -0
- package/dist/wasm/compiler.js.map +1 -0
- package/dist/wasm/loader.d.ts +2 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +90 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/vm.d.ts +32 -0
- package/dist/wasm/vm.d.ts.map +1 -0
- package/dist/wasm/vm.js +440 -0
- package/dist/wasm/vm.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
// Execute command.
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { extname, isAbsolute, join, resolve } from 'path';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { FiveSDK, ProgramIdResolver } from '@5ive-tech/sdk';
|
|
7
|
+
import { ConfigManager } from '../config/ConfigManager.js';
|
|
8
|
+
import { FiveFileManager } from '../utils/FiveFileManager.js';
|
|
9
|
+
import { loadBuildManifest, loadProjectConfig } from '../project/ProjectLoader.js';
|
|
10
|
+
import { section, success as uiSuccess, error as uiError, hint, keyValue } from '../utils/cli-ui.js';
|
|
11
|
+
/**
|
|
12
|
+
* Five execute command implementation
|
|
13
|
+
*/
|
|
14
|
+
export const executeCommand = {
|
|
15
|
+
name: 'execute',
|
|
16
|
+
description: 'Execute Five VM bytecode',
|
|
17
|
+
aliases: ['exec', 'run'],
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
flags: '-i, --input <file>',
|
|
21
|
+
description: 'Input data file (JSON format)',
|
|
22
|
+
required: false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
flags: '-a, --accounts <file>',
|
|
26
|
+
description: 'Accounts configuration file (JSON format) [local execution only] ',
|
|
27
|
+
required: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
flags: '--accounts-json <json>',
|
|
31
|
+
description: 'Additional accounts for on-chain execution as JSON array or object of pubkeys',
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
flags: '-f, --function <index>',
|
|
36
|
+
description: 'Execute specific function by index',
|
|
37
|
+
required: false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
flags: '-p, --params <file>',
|
|
41
|
+
description: 'Function parameters file (JSON format)',
|
|
42
|
+
required: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
flags: '--max-cu <units>',
|
|
46
|
+
description: 'Maximum compute units (default: 1000000)',
|
|
47
|
+
defaultValue: 1000000
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flags: '--validate',
|
|
51
|
+
description: 'Validate bytecode before execution',
|
|
52
|
+
defaultValue: false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
flags: '--partial',
|
|
56
|
+
description: 'Enable partial execution (stops at system calls)',
|
|
57
|
+
defaultValue: true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
flags: '--format <format>',
|
|
61
|
+
description: 'Output format',
|
|
62
|
+
choices: ['text', 'json', 'table'],
|
|
63
|
+
defaultValue: 'text'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
flags: '--trace',
|
|
67
|
+
description: 'Show execution trace',
|
|
68
|
+
defaultValue: false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
flags: '--state',
|
|
72
|
+
description: 'Show VM state after execution',
|
|
73
|
+
defaultValue: false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
flags: '-t, --target <target>',
|
|
77
|
+
description: 'Override target network (devnet, testnet, mainnet, local)',
|
|
78
|
+
required: false
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
flags: '-n, --network <url>',
|
|
82
|
+
description: 'Override network RPC URL',
|
|
83
|
+
required: false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
flags: '-k, --keypair <file>',
|
|
87
|
+
description: 'Override keypair file path',
|
|
88
|
+
required: false
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
flags: '--local',
|
|
92
|
+
description: 'Force local execution (overrides config)',
|
|
93
|
+
defaultValue: false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
flags: '--script-account <account>',
|
|
97
|
+
description: 'Execute deployed script by account ID (on-chain execution)',
|
|
98
|
+
required: false
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
flags: '--vm-state-account <account>',
|
|
102
|
+
description: 'VM state account address (optional, required for on-chain execution if known)',
|
|
103
|
+
required: false
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
flags: '--program-id <id>',
|
|
107
|
+
description: 'Override Five VM program ID (for custom deployments)',
|
|
108
|
+
required: false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
flags: '--project <path>',
|
|
112
|
+
description: 'Project directory or five.toml path',
|
|
113
|
+
required: false
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
arguments: [
|
|
117
|
+
{
|
|
118
|
+
name: 'bytecode',
|
|
119
|
+
description: 'Five VM artifact file (.five/.bin) or script account ID',
|
|
120
|
+
required: false
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
examples: [
|
|
124
|
+
{
|
|
125
|
+
command: 'five execute program.five',
|
|
126
|
+
description: 'Execute using configured target (default)'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: 'five execute program.five --local',
|
|
130
|
+
description: 'Force local execution (overrides config)'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
command: 'five execute program.five --target devnet',
|
|
134
|
+
description: 'Execute on devnet (overrides config)'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: 'five execute program.five -f 0 -p params.json',
|
|
138
|
+
description: 'Execute function 0 with parameters'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
command: 'five execute program.five --validate --trace --format json',
|
|
142
|
+
description: 'Validate and execute with JSON trace output'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
command: 'five execute src/main.v -f 0 --local --params "[10, 5]"',
|
|
146
|
+
description: 'Compile and execute Five source locally with parameters'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
command: 'five execute --script-account 459SanqV8nQDDYW3gWq5JZZAPCMYs78Z5ZnrtH4eFffw -f 0',
|
|
150
|
+
description: 'Execute deployed script by account ID on-chain'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
command: 'five execute --script-account 459SanqV8nQDDYW3gWq5JZZAPCMYs78Z5ZnrtH4eFffw -f 0 --program-id 9MHGM73eszNUtmJS6ypDCESguxWhCBnkUPpTMyLGqURH',
|
|
154
|
+
description: 'Execute with custom Five VM program ID (for custom deployments)'
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
handler: async (args, options, context) => {
|
|
158
|
+
const { logger } = context;
|
|
159
|
+
try {
|
|
160
|
+
const projectContext = await loadProjectConfig(options.project, process.cwd());
|
|
161
|
+
const manifest = projectContext ? await loadBuildManifest(projectContext.rootDir) : null;
|
|
162
|
+
// Apply project defaults to options if not provided
|
|
163
|
+
if (!options.target && projectContext?.config.cluster) {
|
|
164
|
+
options.target = projectContext.config.cluster;
|
|
165
|
+
}
|
|
166
|
+
if (!options.network && projectContext?.config.rpcUrl) {
|
|
167
|
+
options.network = projectContext.config.rpcUrl;
|
|
168
|
+
}
|
|
169
|
+
if (!options.keypair && projectContext?.config.keypairPath) {
|
|
170
|
+
options.keypair = projectContext.config.keypairPath;
|
|
171
|
+
}
|
|
172
|
+
let inputFile = args[0] || manifest?.artifact_path;
|
|
173
|
+
if (inputFile && projectContext && !isAbsolute(inputFile)) {
|
|
174
|
+
inputFile = join(projectContext.rootDir, inputFile);
|
|
175
|
+
}
|
|
176
|
+
const scriptAccount = options.scriptAccount;
|
|
177
|
+
// Debug: Log received options
|
|
178
|
+
if (context.options.verbose) {
|
|
179
|
+
console.log('[execute] Received options:', Object.keys(options));
|
|
180
|
+
console.log('[execute] scriptAccount value:', scriptAccount);
|
|
181
|
+
}
|
|
182
|
+
// Validate input - either bytecode file OR script account required
|
|
183
|
+
if (!inputFile && !scriptAccount) {
|
|
184
|
+
throw new Error('No bytecode or script account provided. Pass a .five/.bin file, use --project to load the last build artifact, or provide --script-account for on-chain execution.');
|
|
185
|
+
}
|
|
186
|
+
// Apply config with CLI overrides
|
|
187
|
+
const configManager = ConfigManager.getInstance();
|
|
188
|
+
const overrides = {
|
|
189
|
+
target: options.target,
|
|
190
|
+
network: options.network,
|
|
191
|
+
keypair: options.keypair
|
|
192
|
+
};
|
|
193
|
+
const config = await configManager.applyOverrides(overrides);
|
|
194
|
+
// Resolve program ID AFTER target override is applied, using the correct target
|
|
195
|
+
// Precedence: CLI flag → project config → config file (per-target) → env var
|
|
196
|
+
if (!options.programId) {
|
|
197
|
+
const configuredProgramId = await configManager.getProgramId(config.target);
|
|
198
|
+
options.programId = projectContext?.config.programId || configuredProgramId || process.env.FIVE_PROGRAM_ID;
|
|
199
|
+
}
|
|
200
|
+
// Show target context prefix
|
|
201
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
202
|
+
// Force local execution if --local flag is used, but not if script account is specified
|
|
203
|
+
const forceLocal = options.local || false;
|
|
204
|
+
const executeLocally = (config.target === 'wasm' || forceLocal) && !scriptAccount;
|
|
205
|
+
// Log execution mode only in verbose
|
|
206
|
+
if (context.options.verbose) {
|
|
207
|
+
if (scriptAccount) {
|
|
208
|
+
logger.info(`${targetPrefix} Executing deployed script account on-chain`);
|
|
209
|
+
}
|
|
210
|
+
else if (executeLocally) {
|
|
211
|
+
logger.info(`${ConfigManager.getTargetPrefix('wasm')} Executing Five VM bytecode locally`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
logger.info(`${targetPrefix} Executing Five VM bytecode`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Show config details only if explicitly enabled and verbose
|
|
218
|
+
if (config.showConfig && !executeLocally && context.options.verbose) {
|
|
219
|
+
logger.info(`Target: ${config.target}`);
|
|
220
|
+
logger.info(`Network: ${config.networks[config.target].rpcUrl}`);
|
|
221
|
+
logger.info(`Keypair: ${config.keypairPath}`);
|
|
222
|
+
}
|
|
223
|
+
if (scriptAccount) {
|
|
224
|
+
await executeScriptAccount(scriptAccount, options, context, config);
|
|
225
|
+
}
|
|
226
|
+
else if (executeLocally) {
|
|
227
|
+
await executeLocallyWithSDK(inputFile, options, context, config);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
await executeOnChain(inputFile, options, context, config);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
logger.error('Execution failed:', error);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Execute locally using Five SDK
|
|
241
|
+
*/
|
|
242
|
+
async function executeLocallyWithSDK(inputFile, options, context, config) {
|
|
243
|
+
const { logger } = context;
|
|
244
|
+
// Initialize for local execution
|
|
245
|
+
if (context.options.verbose) {
|
|
246
|
+
const spinner = ora('Preparing local execution...').start();
|
|
247
|
+
spinner.succeed('Five SDK ready for local execution');
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
let result;
|
|
251
|
+
if (extname(inputFile) === '.v') {
|
|
252
|
+
// Compile and execute Five source file
|
|
253
|
+
logger.info(`Compiling and executing Five source: ${inputFile}`);
|
|
254
|
+
const sourceCode = await readFile(inputFile, 'utf8');
|
|
255
|
+
// For .v files, we don't have ABI yet, so we'll just use the provided function or default to 0
|
|
256
|
+
// The real fix here would be to compile first, extract ABI, then auto-detect the public function
|
|
257
|
+
const functionIndex = parseFunctionIndex(options.function) || 0;
|
|
258
|
+
const parameters = parseParameters(options.params);
|
|
259
|
+
// Parse accounts if provided
|
|
260
|
+
const accounts = options.accounts ? parseAccounts(options.accounts) : undefined;
|
|
261
|
+
result = await FiveSDK.execute(sourceCode, functionIndex, parameters, {
|
|
262
|
+
debug: options.trace || context.options.debug,
|
|
263
|
+
trace: options.trace,
|
|
264
|
+
optimize: true,
|
|
265
|
+
computeUnitLimit: options.maxCu,
|
|
266
|
+
accounts
|
|
267
|
+
});
|
|
268
|
+
// Display compilation info with proper type guard
|
|
269
|
+
if ('compilation' in result && result.compilation) {
|
|
270
|
+
console.log(`Compilation: ${result.compilation.success ? 'OK' : 'FAIL'}`);
|
|
271
|
+
if ('bytecodeSize' in result && result.bytecodeSize) {
|
|
272
|
+
console.log(`Bytecode size: ${result.bytecodeSize} bytes`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Execute existing bytecode file
|
|
278
|
+
logger.info(`Executing bytecode: ${inputFile}`);
|
|
279
|
+
// Load file using centralized manager
|
|
280
|
+
const fileManager = FiveFileManager.getInstance();
|
|
281
|
+
const loadedFile = await fileManager.loadFile(inputFile, {
|
|
282
|
+
validateFormat: true
|
|
283
|
+
});
|
|
284
|
+
const bytecode = loadedFile.bytecode;
|
|
285
|
+
const abi = loadedFile.abi;
|
|
286
|
+
if (options.trace || context.options.debug) {
|
|
287
|
+
console.log(`[CLI] Loaded ${loadedFile.format.toUpperCase()} file: bytecode=${bytecode.length} bytes`);
|
|
288
|
+
if (abi && abi.functions) {
|
|
289
|
+
console.log(`[CLI] Available functions: ${Object.keys(abi.functions).length}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// FUNCTION RESOLUTION FIX: Auto-detect public function when no -f flag provided
|
|
293
|
+
let functionIndex = parseFunctionIndex(options.function);
|
|
294
|
+
if (functionIndex === undefined && abi && abi.functions) {
|
|
295
|
+
// SYSTEMATIC APPROACH: Public functions always have the lowest indices (0, 1, 2...)
|
|
296
|
+
// So we always default to function index 0, which is guaranteed to be public if any functions exist
|
|
297
|
+
functionIndex = 0;
|
|
298
|
+
if (context.options.verbose) {
|
|
299
|
+
let functionName = 'function_0';
|
|
300
|
+
if (Array.isArray(abi.functions)) {
|
|
301
|
+
// FIVEABI format: find function with index 0
|
|
302
|
+
const func0 = abi.functions.find((f) => f.index === 0);
|
|
303
|
+
if (func0 && func0.name)
|
|
304
|
+
functionName = func0.name;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// SimpleABI format: find function with index 0
|
|
308
|
+
const func0Entry = Object.entries(abi.functions).find(([_, f]) => f.index === 0);
|
|
309
|
+
if (func0Entry)
|
|
310
|
+
functionName = func0Entry[0];
|
|
311
|
+
}
|
|
312
|
+
logger.info(`Auto-detected public function: ${functionName} (index 0 - first public function)`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (functionIndex === undefined) {
|
|
316
|
+
functionIndex = 0; // No ABI available, use legacy default
|
|
317
|
+
}
|
|
318
|
+
// Ensure functionIndex is never undefined
|
|
319
|
+
if (functionIndex === undefined) {
|
|
320
|
+
functionIndex = 0;
|
|
321
|
+
}
|
|
322
|
+
const parameters = parseParameters(options.params);
|
|
323
|
+
// Parse accounts if provided
|
|
324
|
+
const accounts = options.accounts ? parseAccounts(options.accounts) : undefined;
|
|
325
|
+
result = await FiveSDK.executeLocally(bytecode, functionIndex, parameters, {
|
|
326
|
+
debug: options.trace || context.options.debug,
|
|
327
|
+
trace: options.trace,
|
|
328
|
+
computeUnitLimit: options.maxCu,
|
|
329
|
+
abi: abi, // Pass ABI for function name resolution
|
|
330
|
+
accounts
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// Display execution results
|
|
334
|
+
displayLocalExecutionResult(result, options, logger);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
throw new Error(`Local execution failed: ${error}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Execute deployed script account on-chain using Five SDK
|
|
342
|
+
*/
|
|
343
|
+
async function executeScriptAccount(scriptAccount, options, context, config) {
|
|
344
|
+
const { logger } = context;
|
|
345
|
+
const { Connection, Keypair } = await import('@solana/web3.js');
|
|
346
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
347
|
+
logger.info(`${targetPrefix} Executing script account: ${scriptAccount}`);
|
|
348
|
+
try {
|
|
349
|
+
// Show configuration
|
|
350
|
+
console.log('\n' + section('Execution Configuration'));
|
|
351
|
+
console.log(` Script Account: ${scriptAccount}`);
|
|
352
|
+
console.log(` Target: ${config.target}`);
|
|
353
|
+
console.log(` Network: ${config.networks[config.target].rpcUrl}`);
|
|
354
|
+
console.log(` Keypair: ${config.keypairPath}`);
|
|
355
|
+
// Set up connection and keypair
|
|
356
|
+
const rpcUrl = config.networks[config.target].rpcUrl;
|
|
357
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
358
|
+
// Load keypair
|
|
359
|
+
const keypairPath = config.keypairPath;
|
|
360
|
+
const keypairData = JSON.parse(await readFile(keypairPath, 'utf8'));
|
|
361
|
+
const deployerKeypair = Keypair.fromSecretKey(new Uint8Array(keypairData));
|
|
362
|
+
console.log(` Executor: ${deployerKeypair.publicKey.toBase58()}`);
|
|
363
|
+
const spinner = ora('Executing script account on-chain...').start();
|
|
364
|
+
try {
|
|
365
|
+
// Get function index, parameters, and additional accounts
|
|
366
|
+
const functionIndex = options.function ? parseInt(options.function) : 0;
|
|
367
|
+
const parameters = parseParameters(options.params);
|
|
368
|
+
// Parse additional accounts for on-chain execution
|
|
369
|
+
let additionalAccounts = [];
|
|
370
|
+
if (options.accountsJson) {
|
|
371
|
+
try {
|
|
372
|
+
const parsed = JSON.parse(options.accountsJson);
|
|
373
|
+
if (Array.isArray(parsed)) {
|
|
374
|
+
additionalAccounts = parsed;
|
|
375
|
+
}
|
|
376
|
+
else if (typeof parsed === 'object' && parsed !== null) {
|
|
377
|
+
// Preserve insertion order from the JSON string
|
|
378
|
+
const orderedKeys = Object.keys(parsed);
|
|
379
|
+
additionalAccounts = orderedKeys.map(k => parsed[k]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
console.warn('Warning: Failed to parse --accounts-json, ignoring.');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Validate program ID for script account execution
|
|
387
|
+
let resolvedProgramId;
|
|
388
|
+
if (config.target !== 'wasm') {
|
|
389
|
+
try {
|
|
390
|
+
resolvedProgramId = ProgramIdResolver.resolve(options.programId);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
throw new Error(`Program ID required for script account execution on ${config.target}. ` +
|
|
394
|
+
`Provide via: --program-id <pubkey>, five.toml programId, ` +
|
|
395
|
+
`or: five config set --program-id <pubkey>`);
|
|
396
|
+
}
|
|
397
|
+
options.programId = resolvedProgramId;
|
|
398
|
+
}
|
|
399
|
+
// Fetch current fees from VM state
|
|
400
|
+
let fees;
|
|
401
|
+
try {
|
|
402
|
+
fees = await FiveSDK.getFees(connection, options.programId);
|
|
403
|
+
if (fees.executeFeeBps > 0) {
|
|
404
|
+
if (context.options.verbose || options.debug) {
|
|
405
|
+
console.log('\n' + section('VM Fees'));
|
|
406
|
+
console.log(keyValue('Execution Fee', `${(fees.executeFeeBps / 100).toFixed(2)}%`));
|
|
407
|
+
if (fees.adminAccount) {
|
|
408
|
+
console.log(keyValue('Admin Account', fees.adminAccount));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Attach admin account to options
|
|
412
|
+
options.adminAccount = fees.adminAccount;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
if (context.options.debug) {
|
|
417
|
+
logger.debug(`Could not fetch VM fees: ${e instanceof Error ? e.message : String(e)}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Execute script account on-chain with additional accounts
|
|
421
|
+
const executeOptions = {
|
|
422
|
+
debug: options.trace || context.options.debug,
|
|
423
|
+
network: config.target,
|
|
424
|
+
computeUnitLimit: options.maxCu || 1400000,
|
|
425
|
+
maxRetries: 3,
|
|
426
|
+
vmStateAccount: options.vmStateAccount,
|
|
427
|
+
adminAccount: options.adminAccount // Pass admin account
|
|
428
|
+
};
|
|
429
|
+
// Add program ID override if provided
|
|
430
|
+
if (options.programId) {
|
|
431
|
+
executeOptions.fiveVMProgramId = options.programId;
|
|
432
|
+
}
|
|
433
|
+
const result = await FiveSDK.executeOnSolana(scriptAccount, connection, deployerKeypair, functionIndex, parameters, additionalAccounts, executeOptions);
|
|
434
|
+
spinner.succeed('Script account execution completed');
|
|
435
|
+
// Display results
|
|
436
|
+
displayOnChainExecutionResult(result, options, logger);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
spinner.fail('Script account execution failed');
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
throw new Error(`Script account execution failed: ${error}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Execute on-chain using Five SDK
|
|
449
|
+
*/
|
|
450
|
+
async function executeOnChain(inputFile, options, context, config) {
|
|
451
|
+
const { logger } = context;
|
|
452
|
+
const { Connection, Keypair } = await import('@solana/web3.js');
|
|
453
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
454
|
+
logger.info(`${targetPrefix} On-chain execution using Five SDK`);
|
|
455
|
+
try {
|
|
456
|
+
// Show configuration
|
|
457
|
+
console.log('\n' + section('Execution Configuration'));
|
|
458
|
+
console.log(` Target: ${config.target}`);
|
|
459
|
+
console.log(` Network: ${config.networks[config.target].rpcUrl}`);
|
|
460
|
+
console.log(` Keypair: ${config.keypairPath}`);
|
|
461
|
+
// Check if input is a script account (base58 string) or bytecode file
|
|
462
|
+
let scriptAccount;
|
|
463
|
+
if (inputFile.length > 30 && inputFile.length < 50 && !inputFile.includes('/') && !inputFile.includes('.')) {
|
|
464
|
+
// Looks like a base58 script account address
|
|
465
|
+
scriptAccount = inputFile;
|
|
466
|
+
console.log(`Using script account: ${scriptAccount}`);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
// It's a file - need to deploy first or prompt user
|
|
470
|
+
console.log(uiError('On-chain execution requires a deployed script account.'));
|
|
471
|
+
console.log(hint(`deploy first: five deploy ${inputFile}`));
|
|
472
|
+
console.log(hint(`then execute: five execute <SCRIPT_ACCOUNT> --target ${config.target}`));
|
|
473
|
+
console.log(hint(`or run locally: five execute ${inputFile} --local`));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
// Setup connection
|
|
477
|
+
const rpcUrl = config.networks[config.target].rpcUrl;
|
|
478
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
479
|
+
// Load signer keypair
|
|
480
|
+
const signerKeypair = await loadKeypair(config.keypairPath, logger);
|
|
481
|
+
// Parse execution options
|
|
482
|
+
const functionName = parseFunctionIndex(options.function) || 0;
|
|
483
|
+
const parameters = parseParameters(options.params);
|
|
484
|
+
const accounts = []; // No additional accounts for simple execution
|
|
485
|
+
console.log(`\nExecuting function ${functionName} with ${parameters.length} parameters...`);
|
|
486
|
+
// Fetch current fees from VM state
|
|
487
|
+
let fees;
|
|
488
|
+
try {
|
|
489
|
+
fees = await FiveSDK.getFees(connection, options.programId);
|
|
490
|
+
if (fees.executeFeeBps > 0) {
|
|
491
|
+
if (context.options.verbose || options.debug) {
|
|
492
|
+
console.log('\n' + section('VM Fees'));
|
|
493
|
+
console.log(keyValue('Execution Fee', `${(fees.executeFeeBps / 100).toFixed(2)}%`));
|
|
494
|
+
if (fees.adminAccount) {
|
|
495
|
+
console.log(keyValue('Admin Account', fees.adminAccount));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Attach admin account to options
|
|
499
|
+
options.adminAccount = fees.adminAccount;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch (e) {
|
|
503
|
+
if (context.options.debug) {
|
|
504
|
+
logger.debug(`Could not fetch VM fees: ${e instanceof Error ? e.message : String(e)}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Execute using Five SDK
|
|
508
|
+
const spinner = ora('Executing on-chain via Five SDK...').start();
|
|
509
|
+
const executeOptions = {
|
|
510
|
+
debug: options.debug || context.options.debug || false,
|
|
511
|
+
network: config.target,
|
|
512
|
+
computeUnitLimit: options.maxCu,
|
|
513
|
+
maxRetries: 3,
|
|
514
|
+
vmStateAccount: options.vmStateAccount,
|
|
515
|
+
adminAccount: options.adminAccount // Pass admin account
|
|
516
|
+
};
|
|
517
|
+
// Add program ID override if provided
|
|
518
|
+
if (options.programId) {
|
|
519
|
+
executeOptions.fiveVMProgramId = options.programId;
|
|
520
|
+
}
|
|
521
|
+
const result = await FiveSDK.executeOnSolana(scriptAccount, connection, signerKeypair, functionName, parameters, accounts, executeOptions);
|
|
522
|
+
if (result.success) {
|
|
523
|
+
spinner.succeed('On-chain execution completed successfully!');
|
|
524
|
+
displayOnChainExecutionResult(result, options, logger);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
spinner.fail('On-chain execution failed');
|
|
528
|
+
displayOnChainExecutionResult(result, options, logger);
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
logger.error('On-chain execution failed:', error);
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Parse function index - handle both numeric strings and function names
|
|
539
|
+
*/
|
|
540
|
+
function parseFunctionIndex(functionOption) {
|
|
541
|
+
if (!functionOption) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
// If it's already a number, return it
|
|
545
|
+
if (typeof functionOption === 'number') {
|
|
546
|
+
return functionOption;
|
|
547
|
+
}
|
|
548
|
+
// If it's a string that looks like a number, convert it
|
|
549
|
+
if (typeof functionOption === 'string') {
|
|
550
|
+
const numericValue = parseInt(functionOption, 10);
|
|
551
|
+
if (!isNaN(numericValue) && numericValue.toString() === functionOption) {
|
|
552
|
+
return numericValue;
|
|
553
|
+
}
|
|
554
|
+
// Otherwise, treat it as a function name
|
|
555
|
+
return functionOption;
|
|
556
|
+
}
|
|
557
|
+
return functionOption;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Parse parameters from JSON string or file
|
|
561
|
+
*/
|
|
562
|
+
function parseParameters(paramsOption) {
|
|
563
|
+
if (!paramsOption) {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
// Try to parse as JSON directly
|
|
568
|
+
if (typeof paramsOption === 'string' && paramsOption.startsWith('[')) {
|
|
569
|
+
return JSON.parse(paramsOption);
|
|
570
|
+
}
|
|
571
|
+
// Handle parameter files
|
|
572
|
+
if (typeof paramsOption === 'string') {
|
|
573
|
+
const filePath = isAbsolute(paramsOption) ? paramsOption : resolve(process.cwd(), paramsOption);
|
|
574
|
+
if (existsSync(filePath)) {
|
|
575
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
576
|
+
return JSON.parse(fileContent);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// If it looks like a file path but didn't exist, and doesn't look like JSON
|
|
580
|
+
if (typeof paramsOption === 'string' && (paramsOption.endsWith('.json') || paramsOption.includes('/'))) {
|
|
581
|
+
throw new Error(`Parameter file not found or invalid: ${paramsOption}`);
|
|
582
|
+
}
|
|
583
|
+
return [];
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
throw new Error(`Failed to parse parameters: ${error}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Parse accounts from comma-separated string
|
|
591
|
+
*/
|
|
592
|
+
function parseAccounts(accountsOption) {
|
|
593
|
+
if (!accountsOption) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
// If it's a string, split by comma and trim whitespace
|
|
598
|
+
if (typeof accountsOption === 'string') {
|
|
599
|
+
const accounts = accountsOption
|
|
600
|
+
.split(',')
|
|
601
|
+
.map(account => account.trim())
|
|
602
|
+
.filter(account => account.length > 0);
|
|
603
|
+
return accounts.length > 0 ? accounts : undefined;
|
|
604
|
+
}
|
|
605
|
+
// If it's already an array, return as-is
|
|
606
|
+
if (Array.isArray(accountsOption)) {
|
|
607
|
+
return accountsOption;
|
|
608
|
+
}
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
throw new Error(`Failed to parse accounts: ${error}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Display on-chain execution results
|
|
617
|
+
*/
|
|
618
|
+
function displayOnChainExecutionResult(result, options, logger) {
|
|
619
|
+
if (options.format === 'json') {
|
|
620
|
+
console.log(JSON.stringify(result, null, 2));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
console.log('\n' + section('On-Chain Execution'));
|
|
624
|
+
if (result.success) {
|
|
625
|
+
console.log(uiSuccess('Execution succeeded'));
|
|
626
|
+
if (result.transactionId) {
|
|
627
|
+
console.log(`Transaction: ${result.transactionId}`);
|
|
628
|
+
}
|
|
629
|
+
if (result.computeUnitsUsed !== undefined) {
|
|
630
|
+
console.log(`Compute units: ${result.computeUnitsUsed}`);
|
|
631
|
+
}
|
|
632
|
+
if (result.result !== undefined) {
|
|
633
|
+
console.log(`Result: ${result.result}`);
|
|
634
|
+
}
|
|
635
|
+
if (result.logs && result.logs.length > 0) {
|
|
636
|
+
console.log('\n' + section('Logs'));
|
|
637
|
+
result.logs.forEach((log) => {
|
|
638
|
+
// Filter out system logs and show only Five VM logs
|
|
639
|
+
if (log.includes('Five') || log.includes('success') || log.includes('error')) {
|
|
640
|
+
console.log(` ${log}`);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
console.log(uiError('Execution failed'));
|
|
647
|
+
if (result.error) {
|
|
648
|
+
console.log(`Error: ${result.error}`);
|
|
649
|
+
}
|
|
650
|
+
if (result.transactionId) {
|
|
651
|
+
console.log(`Transaction: ${result.transactionId}`);
|
|
652
|
+
}
|
|
653
|
+
if (result.logs && result.logs.length > 0) {
|
|
654
|
+
console.log('\n' + section('Error Logs'));
|
|
655
|
+
result.logs.forEach((log) => {
|
|
656
|
+
console.log(` ${log}`);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Load Solana keypair from file
|
|
663
|
+
*/
|
|
664
|
+
async function loadKeypair(keypairPath, logger) {
|
|
665
|
+
const { readFile } = await import('fs/promises');
|
|
666
|
+
const { Keypair } = await import('@solana/web3.js');
|
|
667
|
+
// Expand tilde in path
|
|
668
|
+
const path = keypairPath.startsWith('~/')
|
|
669
|
+
? keypairPath.replace('~', process.env.HOME || '')
|
|
670
|
+
: keypairPath;
|
|
671
|
+
try {
|
|
672
|
+
const keypairData = await readFile(path, 'utf8');
|
|
673
|
+
const secretKey = Uint8Array.from(JSON.parse(keypairData));
|
|
674
|
+
const keypair = Keypair.fromSecretKey(secretKey);
|
|
675
|
+
if (logger.debug) {
|
|
676
|
+
logger.debug(`Loaded keypair from: ${path}`);
|
|
677
|
+
logger.debug(`Public key: ${keypair.publicKey.toString()}`);
|
|
678
|
+
}
|
|
679
|
+
return keypair;
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
throw new Error(`Failed to load keypair from ${path}: ${error}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Display local execution results
|
|
687
|
+
*/
|
|
688
|
+
function displayLocalExecutionResult(result, options, logger) {
|
|
689
|
+
console.log('\n' + section('Local Execution'));
|
|
690
|
+
if (result.success) {
|
|
691
|
+
console.log(uiSuccess('Execution succeeded'));
|
|
692
|
+
if (result.result !== undefined) {
|
|
693
|
+
console.log(` Result: ${JSON.stringify(result.result)}`);
|
|
694
|
+
}
|
|
695
|
+
if (result.executionTime) {
|
|
696
|
+
console.log(` Time: ${result.executionTime}ms`);
|
|
697
|
+
}
|
|
698
|
+
if (result.computeUnitsUsed) {
|
|
699
|
+
console.log(` Compute units: ${result.computeUnitsUsed}`);
|
|
700
|
+
}
|
|
701
|
+
if (result.logs && result.logs.length > 0) {
|
|
702
|
+
console.log('\n' + section('Logs'));
|
|
703
|
+
result.logs.forEach((log) => {
|
|
704
|
+
console.log(` ${log}`);
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
if (result.trace && options.trace) {
|
|
708
|
+
console.log('\n' + section('Trace'));
|
|
709
|
+
// Display trace information if available
|
|
710
|
+
if (Array.isArray(result.trace)) {
|
|
711
|
+
result.trace.slice(0, 10).forEach((step, i) => {
|
|
712
|
+
console.log(` ${i}: ${JSON.stringify(step)}`);
|
|
713
|
+
});
|
|
714
|
+
if (result.trace.length > 10) {
|
|
715
|
+
console.log(` ... and ${result.trace.length - 10} more steps`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
console.log(uiError('Execution failed'));
|
|
722
|
+
if (result.error) {
|
|
723
|
+
if (typeof result.error === 'object' && result.error.message) {
|
|
724
|
+
console.log(` Error: ${result.error.message}`);
|
|
725
|
+
if (result.error.type) {
|
|
726
|
+
console.log(` Type: ${result.error.type}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
const errorMessage = typeof result.error === 'object'
|
|
731
|
+
? JSON.stringify(result.error, null, 2)
|
|
732
|
+
: result.error;
|
|
733
|
+
console.log(` Error: ${errorMessage}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (result.compilationErrors && result.compilationErrors.length > 0) {
|
|
737
|
+
console.log('\n Compilation errors:');
|
|
738
|
+
result.compilationErrors.forEach((error) => {
|
|
739
|
+
console.log(` - ${error.message || error}`);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Display output format
|
|
744
|
+
if (options.format === 'json') {
|
|
745
|
+
console.log('\n' + section('JSON Output'));
|
|
746
|
+
console.log(JSON.stringify(result, null, 2));
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
//# sourceMappingURL=execute.js.map
|