From eafa4cdf35a94453c81ed0f85f3fc76e21e63b1e Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 15 Nov 2024 20:22:31 +0500 Subject: [PATCH] Decode function details by contract calldata --- src/AciContractCallEncoder.js | 23 +++++++++++++++++++++++ src/AciTypeResolver.js | 14 ++++++++++++++ src/api/AciContractCallEncoder.js | 20 ++++++++++++++++++++ src/main.d.ts | 6 ++++++ tests/AciContractCallEncoder.js | 16 ++++++++++++++++ 5 files changed, 79 insertions(+) diff --git a/src/AciContractCallEncoder.js b/src/AciContractCallEncoder.js index 1b6ecb2..c741f0d 100644 --- a/src/AciContractCallEncoder.js +++ b/src/AciContractCallEncoder.js @@ -86,6 +86,29 @@ class AciContractCallEncoder { return this._byteArrayEncoder.decodeWithType(data, calldataType) } + /** + * Decodes function details by contract calldata + * + * @example + * const data = encoder.decodeFunction('cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==') + * console.log(`Decoded data: ${data}`) + * // Outputs: + * // Decoded data: { + * // contractName: "Test", + * // functionName: "test_string", + * // functionId: "f0cc2b95", + * // } + * + * @param {string} data - Encoded calldata in canonical format. + * @returns {object} Decoded function details + */ + decodeFunction(data) { + const {functionId} = this._byteArrayEncoder.decodeWithType(data, FateTypeCalldata()) + const {contractName, functionName} = this._typeResolver.getFunction(functionId) + + return {contractName, functionName, functionId} + } + /** * Decodes successful (resultType = ok) contract call return data * diff --git a/src/AciTypeResolver.js b/src/AciTypeResolver.js index bb1dbf2..6f462a2 100644 --- a/src/AciTypeResolver.js +++ b/src/AciTypeResolver.js @@ -1,6 +1,8 @@ import TypeResolver from './TypeResolver.js' import TypeResolveError from './Errors/TypeResolveError.js' import {FateTypeEvent} from './FateTypes.js' +import {symbolIdentifier} from './utils/hash.js' +import {byteArray2Hex} from './utils/int2ByteArray.js' const isObject = (value) => { return value && typeof value === 'object' && value.constructor === Object @@ -140,6 +142,18 @@ class AciTypeResolver extends TypeResolver { return [typeDef, vars] } + + getFunction(functionId) { + const { contract } = this.aci.at(-1) + const functionName = contract.functions + .map(e => e.name) + .find((name) => byteArray2Hex(symbolIdentifier(name)) === functionId) + if (functionName == null) { + throw new TypeResolveError(`Unknown function id ${functionId}`) + } + + return { contractName: contract.name, functionName } + } } export default AciTypeResolver diff --git a/src/api/AciContractCallEncoder.js b/src/api/AciContractCallEncoder.js index 76154b7..059acab 100644 --- a/src/api/AciContractCallEncoder.js +++ b/src/api/AciContractCallEncoder.js @@ -50,6 +50,26 @@ class AciContractCallEncoder { return this._internalEncoder.decodeCall(contract, funName, data) } + /** + * * Decodes function details by contract calldata + * + * @example + * const data = encoder.decodeFunction('cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==') + * console.log(`Decoded data: ${data}`) + * // Outputs: + * // Decoded data: { + * // contractName: "Test", + * // functionName: "test_string", + * // functionId: "f0cc2b95", + * // } + * + * @param {string} data - Encoded calldata in canonical format. + * @returns {object} Decoded function details + */ + decodeFunction(data) { + return this._internalEncoder.decodeFunction(data) + } + /** * Decodes successful (resultType = ok) contract call return data * diff --git a/src/main.d.ts b/src/main.d.ts index e215056..3cef459 100644 --- a/src/main.d.ts +++ b/src/main.d.ts @@ -31,6 +31,12 @@ export class AciContractCallEncoder { args: any[]; }; + decodeFunction(data: `cb_${string}`): { + contractName: string; + functionName: string; + functionId: string; + }; + decodeResult( contract: string, funName: string, diff --git a/tests/AciContractCallEncoder.js b/tests/AciContractCallEncoder.js index 8719798..ed83b47 100644 --- a/tests/AciContractCallEncoder.js +++ b/tests/AciContractCallEncoder.js @@ -82,6 +82,22 @@ test('Decode calldata', t => { ) }) +test('Decode calldata without function name', t => { + t.plan(1) + const decoded = encoder.decodeFunction( + 'cb_KxGu5Sw8G6+CAAQBSzsrAgQGCK+EAAABAAIbFCg7KwIEBgj8xaf6', + ) + + t.deepEqual( + decoded, + { + contractName: CONTRACT, + functionName: 'test_template_maze', + functionId: 'aee52c3c', + } + ) +}) + test('Decode implicit init (void) result', t => { t.plan(1) t.is(