Skip to content

Commit 0d1eb3f

Browse files
authored
Allow to decode contract call without function name (#297)
* Improve `AciContractCallEncoder:decodeCall` return type * Decode function details by contract calldata
1 parent 0eb64ba commit 0d1eb3f

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

src/AciContractCallEncoder.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,43 @@ class AciContractCallEncoder {
7272
* const data = encoder.decodeCall('Test', 'test_string', 'cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==')
7373
* console.log(`Decoded data: ${data}`)
7474
* // Outputs:
75-
* // Decoded data: ["whoolymoly"]
75+
* // Decoded data: { functionId: "f0cc2b95", args: ["whoolymoly"] }
7676
*
7777
* @param {string} contract - The contract name as defined in the ACI.
7878
* @param {string} funName - The function name as defined in the ACI.
7979
* @param {string} data - Encoded calldata in canonical format.
8080
* @returns {string} Decoded data
8181
*/
8282
decodeCall(contract, funName, data) {
83-
const {types, _required} = this._typeResolver.getCallTypes(contract, funName)
83+
const {types} = this._typeResolver.getCallTypes(contract, funName)
8484
const calldataType = FateTypeCalldata(funName, types)
8585

8686
return this._byteArrayEncoder.decodeWithType(data, calldataType)
8787
}
8888

89+
/**
90+
* Decodes function details by contract calldata
91+
*
92+
* @example
93+
* const data = encoder.decodeFunction('cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==')
94+
* console.log(`Decoded data: ${data}`)
95+
* // Outputs:
96+
* // Decoded data: {
97+
* // contractName: "Test",
98+
* // functionName: "test_string",
99+
* // functionId: "f0cc2b95",
100+
* // }
101+
*
102+
* @param {string} data - Encoded calldata in canonical format.
103+
* @returns {object} Decoded function details
104+
*/
105+
decodeFunction(data) {
106+
const {functionId} = this._byteArrayEncoder.decodeWithType(data, FateTypeCalldata())
107+
const {contractName, functionName} = this._typeResolver.getFunction(functionId)
108+
109+
return {contractName, functionName, functionId}
110+
}
111+
89112
/**
90113
* Decodes successful (resultType = ok) contract call return data
91114
*

src/AciTypeResolver.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import TypeResolver from './TypeResolver.js'
22
import TypeResolveError from './Errors/TypeResolveError.js'
33
import {FateTypeEvent} from './FateTypes.js'
4+
import {symbolIdentifier} from './utils/hash.js'
5+
import {byteArray2Hex} from './utils/int2ByteArray.js'
46

57
const isObject = (value) => {
68
return value && typeof value === 'object' && value.constructor === Object
@@ -140,6 +142,18 @@ class AciTypeResolver extends TypeResolver {
140142

141143
return [typeDef, vars]
142144
}
145+
146+
getFunction(functionId) {
147+
const { contract } = this.aci.at(-1)
148+
const functionName = contract.functions
149+
.map(e => e.name)
150+
.find((name) => byteArray2Hex(symbolIdentifier(name)) === functionId)
151+
if (functionName == null) {
152+
throw new TypeResolveError(`Unknown function id ${functionId}`)
153+
}
154+
155+
return { contractName: contract.name, functionName }
156+
}
143157
}
144158

145159
export default AciTypeResolver

src/api/AciContractCallEncoder.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class AciContractCallEncoder {
3939
* const data = encoder.decodeCall('Test', 'test_string', 'cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==')
4040
* console.log(`Decoded data: ${data}`)
4141
* // Outputs:
42-
* // Decoded data: ["whoolymoly"]
42+
* // Decoded data: { functionId: "f0cc2b95", args: ["whoolymoly"] }
4343
*
4444
* @param {string} contract - The contract name as defined in the ACI.
4545
* @param {string} funName - The function name as defined in the ACI.
@@ -50,6 +50,26 @@ class AciContractCallEncoder {
5050
return this._internalEncoder.decodeCall(contract, funName, data)
5151
}
5252

53+
/**
54+
* * Decodes function details by contract calldata
55+
*
56+
* @example
57+
* const data = encoder.decodeFunction('cb_KxHwzCuVGyl3aG9vbHltb2x5zwMSnw==')
58+
* console.log(`Decoded data: ${data}`)
59+
* // Outputs:
60+
* // Decoded data: {
61+
* // contractName: "Test",
62+
* // functionName: "test_string",
63+
* // functionId: "f0cc2b95",
64+
* // }
65+
*
66+
* @param {string} data - Encoded calldata in canonical format.
67+
* @returns {object} Decoded function details
68+
*/
69+
decodeFunction(data) {
70+
return this._internalEncoder.decodeFunction(data)
71+
}
72+
5373
/**
5474
* Decodes successful (resultType = ok) contract call return data
5575
*

src/main.d.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ export class AciContractCallEncoder {
2626

2727
encodeCall(contract: string, funName: string, args: any[]): `cb_${string}`;
2828

29-
decodeCall(contract: string, funName: string, data: `cb_${string}`): any;
29+
decodeCall(contract: string, funName: string, data: `cb_${string}`): {
30+
functionId: string;
31+
args: any[];
32+
};
33+
34+
decodeFunction(data: `cb_${string}`): {
35+
contractName: string;
36+
functionName: string;
37+
functionId: string;
38+
};
3039

3140
decodeResult(
3241
contract: string,

tests/AciContractCallEncoder.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ test('Decode calldata', t => {
8282
)
8383
})
8484

85+
test('Decode calldata without function name', t => {
86+
t.plan(1)
87+
const decoded = encoder.decodeFunction(
88+
'cb_KxGu5Sw8G6+CAAQBSzsrAgQGCK+EAAABAAIbFCg7KwIEBgj8xaf6',
89+
)
90+
91+
t.deepEqual(
92+
decoded,
93+
{
94+
contractName: CONTRACT,
95+
functionName: 'test_template_maze',
96+
functionId: 'aee52c3c',
97+
}
98+
)
99+
})
100+
85101
test('Decode implicit init (void) result', t => {
86102
t.plan(1)
87103
t.is(

0 commit comments

Comments
 (0)