Skip to content

Commit

Permalink
refactor(contract)!: decode call result, make args flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Jan 28, 2022
1 parent 7defe11 commit b0a5cdc
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 91 deletions.
72 changes: 26 additions & 46 deletions src/actions/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
import fs from 'fs';
import path from 'path';
import { initClient, initClientByWalletFile } from '../utils/cli';
import { readJSONFile, readFile } from '../utils/helpers';
import { print, printTransaction, printUnderscored } from '../utils/print';

const readFile = (filename) => fs.readFileSync(path.resolve(process.cwd(), filename), 'utf-8');

// ## Function which compile your `source` code
export async function compile(file, options) {
const { json } = options;
const code = readFile(path.resolve(process.cwd(), file), 'utf-8');
const code = readFile(file);
if (!code) throw new Error('Contract file not found');

const sdk = await initClient(options);
Expand All @@ -41,57 +42,36 @@ export async function compile(file, options) {
}
}

function getContractParams({ descrPath, contractAddress, contractSource }) {
if (contractAddress && contractSource) {
const source = readFile(path.resolve(process.cwd(), contractSource), 'utf-8');
return { source, contractAddress };
}
function getContractParams({
descrPath, contractAddress, contractSource, contractAci,
}, { dummySource } = {}) {
if (descrPath) {
const { source, address } = readJSONFile(path.resolve(process.cwd(), descrPath));
return { source, contractAddress: address };
const { address, ...other } = JSON.parse(readFile(descrPath));
return { contractAddress: address, ...other };
}
throw new Error('--descrPath or --contractAddress and --contractSource requires');
return {
contractAddress,
// TODO: either remove calldata methods in cli or reconsider getContractInstance requirements
source: (contractSource && readFile(contractSource)) ?? (dummySource && 'invalid-source'),
aci: contractAci && JSON.parse(readFile(contractAci)),
};
}

// ## Function which compile your `source` code
export async function encodeData(source, fn, args = [], options) {
const sourceCode = readFile(path.resolve(process.cwd(), source), 'utf-8');
if (!sourceCode) throw new Error('Contract file not found');

export async function encodeCalldata(fn, args, options) {
const sdk = await initClient(options);

// Call `node` API which return `compiled code`
const callData = await sdk.contractEncodeCallDataAPI(sourceCode, fn, args);
if (options.json) {
print(JSON.stringify({ callData }));
} else {
print(`Contract encoded call data: ${callData}`);
}
const contract = await sdk.getContractInstance(getContractParams(options, { dummySource: true }));
const calldata = contract.calldata.encode(contract.aci.name, fn, args);
if (options.json) print({ calldata });
else print(`Contract encoded calldata: ${calldata}`);
}

// ## Function which compile your `source` code
export async function decodeCallData(data, options) {
const { sourcePath, code, fn } = options;
let sourceCode;

if (!sourcePath && !code) throw new Error('Contract source(--sourcePath) or contract code(--code) required!');
if (sourcePath) {
if (!fn) throw new Error('Function name required in decoding by source!');
sourceCode = readFile(path.resolve(process.cwd(), sourcePath), 'utf-8');
if (!sourceCode) throw new Error('Contract file not found');
} else if (code.slice(0, 2) !== 'cb') throw new Error('Code must be like "cb_23dasdafgasffg...." ');

export async function decodeCallResult(fn, calldata, options) {
const sdk = await initClient(options);

// Call `node` API which return `compiled code`
const decoded = code
? await sdk.contractDecodeCallDataByCodeAPI(code, data)
: await sdk.contractDecodeCallDataBySourceAPI(sourceCode, fn, data);

if (options.json) {
print(JSON.stringify({ decoded }));
} else {
print('Decoded Call Data:');
const contract = await sdk.getContractInstance(getContractParams(options, { dummySource: true }));
const decoded = contract.calldata.decode(contract.aci.name, fn, calldata);
if (options.json) print({ decoded });
else {
print('Contract decoded call result:');
print(decoded);
}
}
Expand All @@ -109,7 +89,7 @@ export async function deploy(walletPath, contractPath, callData = '', options) {
// deploy descriptor
if (callData.split('_')[0] !== 'cb') throw new Error('"callData" should be a string with "cb" prefix');
const sdk = await initClientByWalletFile(walletPath, options);
const contractFile = readFile(path.resolve(process.cwd(), contractPath), 'utf-8');
const contractFile = readFile(contractPath);

const ownerId = await sdk.address();
const { bytecode: code } = await sdk.contractCompile(contractFile);
Expand Down
44 changes: 30 additions & 14 deletions src/commands/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,24 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
// We'll use `commander` for parsing options
import { Command } from 'commander';
import { Argument, Option, Command } from 'commander';
import { SCHEMA } from '@aeternity/aepp-sdk';
import { NODE_URL, COMPILER_URL, GAS } from '../utils/constant';
import { getCmdFromArguments } from '../utils/cli';
import * as Contract from '../actions/contract';

const callArgs = new Argument('[args]', 'JSON-encoded arguments array of contract call')
.argParser((argsText) => {
const args = JSON.parse(argsText);
if (!Array.isArray(args)) throw new Error(`Call arguments should be an array, got ${argsText} instead`);
return args;
})
.default([]);

const descriptorPathOption = new Option('-d --descrPath [descrPath]', 'Path to contract descriptor file');
const contractSourceFilenameOption = new Option('--contractSource [contractSource]', 'Contract source code file name');
const contractAciFilenameOption = new Option('--contractAci [contractAci]', 'Contract ACI file name');

export default () => {
const program = new Command().name('aecli contract');

Expand All @@ -44,29 +56,33 @@ export default () => {
.description('Compile a contract')
.action((file, ...args) => Contract.compile(file, getCmdFromArguments(args)));

// ## Initialize `encode callData` command
// ## Initialize `encode-calldata` command
//
// You can use this command to prepare `callData`
//
// Example: `aecli contract encodeData ./mycontract.contract testFn 1 2`
program
.command('encodeData <source> <fn> [args...]')
.description('Encode contract call data')
.action((source, fn, args, ...otherArgs) => Contract.encodeData(source, fn, args, getCmdFromArguments(otherArgs)));
.command('encode-calldata <fn>')
.addArgument(callArgs)
.addOption(descriptorPathOption)
.addOption(contractSourceFilenameOption)
.addOption(contractAciFilenameOption)
.description('Encode contract calldata')
.action((fn, args, ...otherArgs) => Contract.encodeCalldata(fn, args, getCmdFromArguments(otherArgs)));

// ## Initialize `decode call data` command
// ## Initialize `decode-calldata` command
//
// You can use this command to decode contract call data using source or bytecode
// You can use this command to decode contract calldata using source or bytecode
//
// Example bytecode: `aecli contract decodeCallData cb_asdasdasd... --code cb_asdasdasdasd....`
// Example source cdoe: `aecli contract decodeCallData cb_asdasdasd... --sourcePath ./contractSource --fn someFunction`
// Example source code: `aecli contract decodeCallData cb_asdasdasd... --sourcePath ./contractSource --fn someFunction`
program
.command('decodeCallData <data>')
.option('--sourcePath [sourcePath]', 'Path to contract source')
.option('--code [code]', 'Compiler contract code')
.option('--fn [fn]', 'Function name')
.description('Decode contract call data')
.action((data, ...args) => Contract.decodeCallData(data, getCmdFromArguments(args)));
.command('decode-call-result <fn> <data>')
.addOption(descriptorPathOption)
.addOption(contractSourceFilenameOption)
.addOption(contractAciFilenameOption)
.description('Decode contract calldata')
.action((fn, data, ...args) => Contract.decodeCallResult(fn, data, getCmdFromArguments(args)));

// ## Initialize `call` command
//
Expand Down
94 changes: 63 additions & 31 deletions test/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import fs from 'fs';
import {
after, before, describe, it,
} from 'mocha';
import { expect } from 'chai';
import { executeProgram, getSdk, WALLET_NAME } from './index';
import contractProgramFactory from '../src/commands/contract';

Expand All @@ -31,79 +32,110 @@ contract Identity =
entrypoint test(x : int, y: int) = x + y
`;

const CALL_DATA = 'cb_KxGSiyA2KwIEFfUrtQ==';
const DECODED_CALL_DATA = { arguments: [{ type: 'int', value: 1 }, { type: 'int', value: 2 }], function: 'test' };

describe('CLI Contract Module', () => {
const contractFile = 'testContract';
let deployDescriptor; let sdk; let bytecode; let
cAddress;
const contractSourceFile = 'testContract';
const contractAciFile = 'testContractAci';
let deployDescriptorFile;
let sdk;
let cAddress;

before(async () => {
fs.writeFileSync(contractFile, testContractSource);
fs.writeFileSync(contractSourceFile, testContractSource);
sdk = await getSdk();
fs.writeFileSync(contractAciFile, JSON.stringify(await sdk.contractGetACI(testContractSource)));
});

after(() => {
sdk.removeWallet();
if (fs.existsSync(deployDescriptor)) fs.unlinkSync(deployDescriptor);
if (fs.existsSync(contractFile)) fs.unlinkSync(contractFile);
if (fs.existsSync(deployDescriptorFile)) fs.unlinkSync(deployDescriptorFile);
if (fs.existsSync(contractSourceFile)) fs.unlinkSync(contractSourceFile);
if (fs.existsSync(contractAciFile)) fs.unlinkSync(contractAciFile);
});

it('Compile Contract', async () => {
const compiled = await sdk.contractCompile(testContractSource);
const compiledCLI = await executeContract(['compile', contractFile]);
const compiledCLI = await executeContract(['compile', contractSourceFile]);
const bytecodeCLI = compiledCLI.split(':')[1].trim();
bytecode = compiled.bytecode;

bytecodeCLI.should.be.equal(compiled.bytecode);
});

it('Encode callData', async () => {
const { callData } = await executeContract(['encodeData', contractFile, 'test', '1', '2', '--json']);
callData.should.be.equal(CALL_DATA);
});

it('Decode callData', async () => {
const { decoded } = await executeContract(['decodeCallData', CALL_DATA, '--code', bytecode, '--json']);
decoded.should.be.eql(DECODED_CALL_DATA);
});

it('Deploy Contract', async () => {
const { callData } = await executeContract(['encodeData', contractFile, 'init', '--json']);
const res = await executeContract(['deploy', WALLET_NAME, '--password', 'test', contractFile, callData, '--json']);
const { calldata } = await executeContract(['encode-calldata', '--contractSource', contractSourceFile, 'init', '--json']);
const res = await executeContract(['deploy', WALLET_NAME, '--password', 'test', contractSourceFile, calldata, '--json']);
const { result: { contractId }, transaction, descPath } = res;
deployDescriptor = descPath;
const [name, pref, add] = deployDescriptor.split('.');
deployDescriptorFile = descPath;
const [name, pref, add] = deployDescriptorFile.split('.');
cAddress = contractId;
contractId.should.be.a('string');
transaction.should.be.a('string');
name.should.be.equal(contractFile);
name.should.be.equal(contractSourceFile);
pref.should.be.equal('deploy');
add.should.be.equal((await sdk.address()).split('_')[1]);
});

it('Call Contract by descriptor', async () => {
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--descrPath', deployDescriptor, 'test', '1', '2']);
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--descrPath', deployDescriptorFile, 'test', '1', '2']);
callResponse.result.returnValue.should.contain('cb_');
callResponse.decodedResult.should.be.equal('3');
});

it('Call Contract static by descriptor', async () => {
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--descrPath', deployDescriptor, 'test', '1', '2', '--callStatic']);
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--descrPath', deployDescriptorFile, 'test', '1', '2', '--callStatic']);
callResponse.result.returnValue.should.contain('cb_');
callResponse.decodedResult.should.equal('3');
});

it('Call Contract by contract address', async () => {
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--contractAddress', cAddress, '--contractSource', contractFile, 'test', '1', '2']);
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--contractAddress', cAddress, '--contractSource', contractSourceFile, 'test', '1', '2']);
callResponse.result.returnValue.should.contain('cb_');
callResponse.decodedResult.should.equal('3');
});

it('Call Contract static by contract address', async () => {
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--contractAddress', cAddress, '--contractSource', contractFile, 'test', '1', '2', '--callStatic']);
const callResponse = await executeContract(['call', WALLET_NAME, '--password', 'test', '--json', '--contractAddress', cAddress, '--contractSource', contractSourceFile, 'test', '1', '2', '--callStatic']);
callResponse.result.returnValue.should.contain('cb_');
callResponse.decodedResult.should.equal('3');
});

describe('Calldata', () => {
it('encodes calldata', async () => {
const { calldata } = await executeContract([
'encode-calldata',
'test', '[1, 2]',
'--contractSource', contractSourceFile,
'--json',
]);
expect(calldata).to.be.equal('cb_KxGSiyA2KwIEFfUrtQ==');
});

it('encodes calldata by aci', async () => {
const { calldata } = await executeContract([
'encode-calldata',
'test', '[1, 2]',
'--contractAci', contractAciFile,
'--json',
]);
expect(calldata).to.be.equal('cb_KxGSiyA2KwIEFfUrtQ==');
});

it('encodes calldata by deploy descriptor', async () => {
const { calldata } = await executeContract([
'encode-calldata',
'test', '[1, 2]',
'--descrPath', deployDescriptorFile,
'--json',
]);
expect(calldata).to.be.equal('cb_KxGSiyA2KwIEFfUrtQ==');
});

it('decodes call result', async () => {
const { decoded } = await executeContract([
'decode-call-result',
'test', 'cb_BvMDXHk=',
'--contractSource', contractSourceFile,
'--json',
]);
decoded.should.be.equal('3');
});
});
});

0 comments on commit b0a5cdc

Please sign in to comment.