cli-functions.js (190 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import fs from 'fs';
import os from 'os';
import crypto from 'crypto';
import { execFile } from 'child_process';
import path from 'path';
import logger from './logger.js';
const compiledContractsDir = path.join(new URL('.', import.meta.url).pathname, 'compiled_contracts');
if (!fs.existsSync(compiledContractsDir)) {
fs.mkdirSync(compiledContractsDir, { recursive: true });
}
function handleExecFile(command, args, options = {}) {
return new Promise((resolve, reject) => {
const child = execFile(command, args, options);
let stdoutData = '';
let stderrData = '';
child.stdout.on('data', (data) => {
stdoutData += data;
});
child.stderr.on('data', (data) => {
stderrData += data;
});
child.on('error', (error) => {
logger.error(`Error spawning child process: ${error.message}`);
reject(`Error spawning child process: ${error.message}`);
});
child.on('close', (code) => {
if (code !== 0) {
const errorMessage = `Process exited with code ${code}\n${stderrData}`;
logger.error(errorMessage);
reject(errorMessage);
} else {
const output = (stdoutData + stderrData).trim();
resolve(output);
}
});
});
}
export async function createAccount(config, type = 'path') {
let configPath = config;
if (type === 'data') {
const filename = path.join(os.tmpdir(), `config-${crypto.randomUUID()}.tmp`);
fs.writeFileSync(filename, config);
configPath = filename;
}
const command = 'rescontract';
const args = ['create', '--config', configPath];
try {
const result = await handleExecFile(command, args);
if (type === 'data') {
fs.unlinkSync(configPath);
}
const address = result.match(/address: \"(0x[0-9a-fA-F]+)\"/)[1];
return address;
} catch (error) {
if (type === 'data' && fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
}
throw error;
}
}
export async function addAddress(config, address, type = 'path') {
let configPath = config;
if (type === 'data') {
const configFilename = path.join(os.tmpdir(), `config-${crypto.randomUUID()}.tmp`);
fs.writeFileSync(configFilename, config);
configPath = configFilename;
}
const command = 'rescontract';
const cliArgs = ['add_address', '-c', configPath, '-e', address];
try {
const result = await handleExecFile(command, cliArgs);
if (result.includes("Address added successfully")) {
return "Address added successfully";
} else {
throw new Error("Failed to add address. Unexpected output: " + result);
}
} catch (error) {
throw error;
} finally {
if (type === 'data' && fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
}
}
}
export async function compileContract(source, type = 'path') {
let sourcePath = source;
let outputPath = '';
if (type === 'data') {
const uniqueId = crypto.randomUUID();
const sourceFilename = path.join(os.tmpdir(), `contract-${uniqueId}.sol`);
outputPath = path.join(compiledContractsDir, `contract-${uniqueId}.json`);
fs.writeFileSync(sourceFilename, source);
sourcePath = sourceFilename;
} else {
outputPath = path.join(compiledContractsDir, 'MyContract.json');
}
const command = 'rescontract';
const args = ['compile', '--sol', sourcePath, '--output', outputPath];
try {
const result = await handleExecFile(command, args);
if (type === 'data') {
return path.basename(outputPath);
}
const successMessage = result.match(/Compiled successfully to (\/[^\s]+)/)[0];
return successMessage;
} catch (error) {
throw error;
} finally {
if (type === 'data') {
if (fs.existsSync(sourcePath)) fs.unlinkSync(sourcePath);
}
}
}
export async function deployContract(config, contract, name, args, owner, type = 'path') {
let configPath = config;
let contractPath = contract;
if (type === 'data') {
const configFilename = path.join(os.tmpdir(), `config-${crypto.randomUUID()}.tmp`);
fs.writeFileSync(configFilename, config);
configPath = configFilename;
contractPath = path.join(compiledContractsDir, contract);
}
const command = 'rescontract';
const argList = args.split(',').map((arg) => arg.trim());
const cliArgs = [
'deploy',
'--config',
configPath,
'--contract',
contractPath,
'--name',
name,
'--arguments',
argList.join(','),
'--owner',
owner,
];
try {
const result = await handleExecFile(command, cliArgs);
const ownerAddress = result.match(/owner_address: \"(0x[0-9a-fA-F]+)\"/)[1];
const contractAddress = result.match(/contract_address: \"(0x[0-9a-fA-F]+)\"/)[1];
const contractName = result.match(/contract_name: \"([^\"]+)\"/)[1];
return {
ownerAddress,
contractAddress,
contractName
};
} catch (error) {
throw error;
} finally {
if (type === 'data' && fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
}
}
}
export async function executeContract(config, sender, contract, functionName, args, type = 'path') {
let configPath = config;
if (type === 'data') {
const configFilename = path.join(os.tmpdir(), `config-${crypto.randomUUID()}.tmp`);
fs.writeFileSync(configFilename, config);
configPath = configFilename;
}
const command = 'rescontract';
const argList = args.split(',').map((arg) => arg.trim());
const cliArgs = [
'execute',
'--config',
configPath,
'--sender',
sender,
'--contract',
contract,
'--function-name',
functionName,
'--arguments',
argList.join(','),
];
try {
const result = await handleExecFile(command, cliArgs);
const success = result.includes("0x0000000000000000000000000000000000000000000000000000000000000001");
return success ? "Execution successful" : "Execution failed";
return result;
} catch (error) {
throw error;
} finally {
if (type === 'data' && fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
}
}
}