• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cartesi / rollups-explorer / 10946891476

19 Sep 2024 06:35PM UTC coverage: 86.213% (+0.005%) from 86.208%
10946891476

push

github

brunomenezes
feat: Add support for decoding Voucher payload when available to remotely retrieve the destination contract ABI.

1133 of 1373 branches covered (82.52%)

Branch coverage included in aggregate %.

95 of 162 new or added lines in 6 files covered. (58.64%)

32 existing lines in 2 files now uncovered.

8347 of 9623 relevant lines covered (86.74%)

45.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.75
/apps/web/src/components/specification/decoder.ts
1
import {
1✔
2
    T,
3
    cond,
4
    head,
5
    includes,
6
    isEmpty,
7
    isNil,
8
    pathEq,
9
    pathOr,
10
    pipe,
11
} from "ramda";
1✔
12
import { isNotNilOrEmpty } from "ramda-adjunct";
1✔
13
import {
14
    AbiDecodingDataSizeTooSmallError,
15
    AbiFunction,
16
    AbiFunctionSignatureNotFoundError,
17
    AbiParameter,
18
    Hex,
19
    InvalidAbiParametersError,
20
    decodeAbiParameters,
21
    decodeFunctionData,
22
    parseAbiParameters,
23
    slice,
24
} from "viem";
1✔
25
import SpecificationModeNotSupportedError from "./errors/SpecificationModeNotSupported";
1✔
26
import { ABI_PARAMS, JSON_ABI, Specification, specModes } from "./types";
1✔
27

28
interface Piece {
29
    name: string;
30
    part: Hex | Uint8Array;
31
    decodedPart?: any;
32
}
33

34
export interface Envelope {
35
    spec: Specification;
36
    input: Hex | Uint8Array;
37
    pieces: Piece[];
38
    result: Record<string, any>;
39
    error?: Error;
40
}
41

42
const getPieces = (
1✔
43
    abiParams: readonly string[],
9✔
44
    encodedData: Hex | Uint8Array,
9✔
45
): Piece[] => {
9✔
46
    const abiParameters = parseAbiParameters(abiParams);
9✔
47

48
    const resultList = decodeAbiParameters(
9✔
49
        // @ts-ignore dynamic type complains
50
        abiParameters,
9✔
51
        encodedData,
9✔
52
    );
9✔
53

54
    return resultList.map((decodedVal, index) => {
9✔
55
        // @ts-ignore
56
        const { name } = abiParameters[index] as {
26✔
57
            name: string;
58
        };
59

60
        return {
26✔
61
            name,
26✔
62
            part: encodedData,
26✔
63
            decodedPart: decodedVal,
26✔
64
        } as Piece;
26✔
65
    });
9✔
66
};
9✔
67

68
const addPiecesToEnvelope = (e: Envelope): Envelope => {
1✔
69
    if (!e.error && e.spec.mode === "abi_params") {
46✔
70
        try {
46✔
71
            if (e.spec.sliceInstructions?.length) {
46✔
72
                e.spec.sliceInstructions.forEach((instruction, index) => {
40✔
73
                    const { from, to, name, type } = instruction;
85✔
74
                    const part = slice(e.input, from, to);
85✔
75
                    const decodedPart =
85✔
76
                        !isNil(type) && !isEmpty(type)
85✔
77
                            ? head(decodeAbiParameters([{ type, name }], part))
5✔
78
                            : part;
47✔
79

80
                    e.pieces.push({
85✔
81
                        name: name ?? `param${index}`,
85!
82
                        part,
85✔
83
                        decodedPart,
85✔
84
                    });
85✔
85
                });
40✔
86
            } else {
44✔
87
                const pieces = getPieces(e.spec.abiParams, e.input);
6✔
88
                e.pieces.push(...pieces);
6✔
89
            }
6✔
90
        } catch (error: any) {
46✔
91
            const message = pathOr(error.message, ["shortMessage"], error);
34✔
92
            const errorMeta = pathOr([], ["metaMessages"], error).join("\n");
34✔
93
            e.error = new Error(`${message}\n\n${errorMeta}`);
34✔
94
        }
34✔
95
    }
46✔
96

97
    return e;
46✔
98
};
46✔
99

100
const decodeTargetSliceAndAddToPieces = (e: Envelope): Envelope => {
1✔
101
    if (!e.error && e.spec.mode === "abi_params") {
46✔
102
        const targetName = e.spec.sliceTarget;
12✔
103
        const piece = e.pieces.find((piece) => piece.name === targetName);
12✔
104

105
        try {
12✔
106
            if (piece && piece.part) {
12✔
107
                const pieces = getPieces(e.spec.abiParams, piece.part);
3✔
108
                e.pieces.push(...pieces);
3✔
109
            }
3✔
110
        } catch (error: any) {
12✔
111
            const message = pathOr(error.message, ["shortMessage"], error);
1✔
112
            let errorMeta;
1✔
113
            let extra;
1✔
114

115
            if (error instanceof AbiDecodingDataSizeTooSmallError) {
1✔
116
                errorMeta = pathOr([], ["metaMessages"], error).join("\n");
1✔
117
                extra = `Slice name: "${targetName}" (Is it the right one?)`;
1✔
118
            }
1✔
119

120
            if (error instanceof InvalidAbiParametersError) {
1!
UNCOV
121
                errorMeta = `ABI Parameters : [ ${e.spec.abiParams.join(
×
UNCOV
122
                    ",",
×
123
                )} ]`;
×
124
                extra = "Check the ABI parameters defined.";
×
125
            }
×
126

127
            const errorMessage = `${message}\n\n${errorMeta ?? ""}\n${
1!
128
                extra ?? ""
1!
129
            }`;
1✔
130

131
            e.error = new Error(errorMessage);
1✔
132
        }
1✔
133
    }
12✔
134
    return e;
46✔
135
};
46✔
136

137
const prepareResultFromPieces = (e: Envelope): Envelope => {
1✔
138
    if (!e.error && e.spec.mode === "abi_params") {
46✔
139
        const sliceTarget = e.spec.sliceTarget;
11✔
140
        e.result = e.pieces.reduce((prev, { name, decodedPart }, index) => {
11✔
141
            /**
142
             * Adding a unwrap effect
143
             * decoded target is not included in the result
144
             */
145
            if (sliceTarget === name) return prev;
41✔
146

147
            const key = name ?? `params${index}`;
41!
148
            return { ...prev, [key]: decodedPart };
41✔
149
        }, {});
11✔
150
    }
11✔
151

152
    return e;
46✔
153
};
46✔
154

155
type AbiParameterInfo = {
156
    param: AbiParameter;
157
    index: number;
158
};
159

160
/**
161
 * Check the AbiParameter in the following precedence order [name, type]
162
 * and returns the first available.
163
 * Fallback to `arg-{index}` based on the parameter position passed to the abi function.
164
 */
165
const getAbiParamIdentifier = cond<[info: AbiParameterInfo], string>([
1✔
166
    [(info) => isNotNilOrEmpty(info.param.name), pathOr("", ["param", "name"])],
1✔
167
    [(info) => isNotNilOrEmpty(info.param.type), pathOr("", ["param", "type"])],
1✔
168
    [T, (info) => `arg-${info.index}`],
1✔
169
]);
1✔
170

171
const prepareResultForJSONABI = (e: Envelope): Envelope => {
1✔
172
    if (e.spec.mode === "json_abi") {
2✔
173
        try {
2✔
174
            const { functionName, args } = decodeFunctionData({
2✔
175
                abi: e.spec.abi,
2✔
176
                data: e.input as Hex,
2✔
177
            });
2✔
178

179
            const orderedNamedArgs: [string, any][] = [];
2✔
180

181
            if (args && args?.length > 0) {
2✔
182
                const abiItem = e.spec.abi.find(
1✔
183
                    (item) =>
1✔
184
                        item.type === "function" && item.name === functionName,
2✔
185
                ) as AbiFunction;
1✔
186

187
                // respecting order of arguments but including abi names
188
                abiItem.inputs.forEach((param, index) => {
1✔
189
                    const name = getAbiParamIdentifier({ param, index });
6✔
190
                    orderedNamedArgs.push([name, args[index]]);
6✔
191
                });
1✔
192
            }
1✔
193

194
            e.result = {
1✔
195
                functionName,
1✔
196
                args,
1✔
197
                orderedNamedArgs,
1✔
198
            };
1✔
199
        } catch (err: any) {
1✔
200
            const message =
1✔
201
                err instanceof AbiFunctionSignatureNotFoundError
1✔
202
                    ? err.shortMessage
1!
UNCOV
203
                    : err.message;
×
204
            e.error = new Error(message);
1✔
205
        }
1✔
206
    }
2✔
207

208
    return e;
2✔
209
};
2✔
210

211
const transform: (e: Envelope) => Envelope = cond([
1✔
212
    [
1✔
213
        pathEq(ABI_PARAMS, ["spec", "mode"]),
1✔
214
        pipe(
1✔
215
            addPiecesToEnvelope,
1✔
216
            decodeTargetSliceAndAddToPieces,
1✔
217
            prepareResultFromPieces,
1✔
218
        ),
1✔
219
    ],
1✔
220
    [pathEq(JSON_ABI, ["spec", "mode"]), prepareResultForJSONABI],
1✔
221
    [
1✔
222
        T,
1✔
223
        (e: Envelope) => {
1✔
UNCOV
224
            e.error = new SpecificationModeNotSupportedError(e.spec);
×
UNCOV
225
            return e;
×
UNCOV
226
        },
×
227
    ],
1✔
228
]);
1✔
229

230
/**
231
 * Decode the payload data based on the specification passed. The return
232
 * contains the result but also could contain an error as a value, therefore
233
 * the callee should decide what to do with it.
234
 * @param spec {Specification}
235
 * @param payload {Hex | Uint8Array}
236
 * @throws {SpecificationModeNotSupportedError}
237
 * @returns {Envelope}
238
 */
239
export function decodePayload(
1✔
240
    spec: Specification,
49✔
241
    input: Hex | Uint8Array,
49✔
242
): Envelope {
49✔
243
    if (!includes(spec.mode, specModes)) {
49✔
244
        throw new SpecificationModeNotSupportedError(spec);
1✔
245
    }
1✔
246

247
    const envelope: Envelope = {
48✔
248
        spec,
48✔
249
        input,
48✔
250
        pieces: [],
48✔
251
        result: {},
48✔
252
    };
48✔
253

254
    return transform(envelope);
48✔
255
}
48✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc