• 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

86.67
/apps/web/src/components/inputs/inputDetailsView.tsx
1
"use client";
1✔
2
import { ContentType, InputDetails } from "@cartesi/rollups-explorer-ui";
1✔
3
import { Alert, Box, Group, Select, Stack, Text } from "@mantine/core";
1✔
4
import { find, omit, pathOr } from "ramda";
1✔
5
import { included, isNilOrEmpty, isNotNilOrEmpty } from "ramda-adjunct";
1✔
6
import { FC, useEffect, useState } from "react";
1✔
7
import { TbExclamationCircle } from "react-icons/tb";
1✔
8
import { useQuery } from "urql";
1✔
9
import { Address, Hex } from "viem";
10
import { InputItemFragment } from "../../graphql/explorer/operations";
11
import {
12
    InputDetailsDocument,
13
    InputDetailsQuery,
14
    InputDetailsQueryVariables,
15
} from "../../graphql/rollups/operations";
1✔
16
import { Voucher } from "../../graphql/rollups/types";
17
import getConfiguredChainId from "../../lib/getConfiguredChain";
1✔
18
import { useConnectionConfig } from "../../providers/connectionConfig/hooks";
1✔
19
import { theme } from "../../providers/theme";
1✔
20
import { NewSpecificationButton } from "../specification/components/NewSpecificationButton";
1✔
21
import { findSpecificationFor } from "../specification/conditionals";
1✔
22
import { Envelope, decodePayload } from "../specification/decoder";
1✔
23
import { useSpecification } from "../specification/hooks/useSpecification";
1✔
24
import { useSystemSpecifications } from "../specification/hooks/useSystemSpecifications";
1✔
25
import useVoucherDecoder from "../specification/hooks/useVoucherDecoder";
1✔
26
import { Specification } from "../specification/types";
27
import { stringifyContent } from "../specification/utils";
1✔
28
import VoucherExecution from "./voucherExecution";
1✔
29

30
interface ApplicationInputDataProps {
31
    input: InputItemFragment;
32
}
33

34
type InputTypes = "vouchers" | "reports" | "notices";
35

36
const destinationOrString = pathOr("", ["edges", "0", "node", "destination"]);
1✔
37
const payloadOrString = pathOr("", ["edges", 0, "node", "payload"]);
1✔
38

39
const updateForNextPage = (
1✔
40
    name: InputTypes,
3✔
41
    obj: InputDetailsQueryVariables,
3✔
42
) => {
3✔
43
    switch (name) {
3✔
44
        case "vouchers":
3✔
45
            return omit(["lastVouchers", "vouchersPrevPage"], obj);
1✔
46
        case "notices":
3✔
47
            return omit(["lastNotices", "noticesPrevPage"], obj);
1✔
48
        case "reports":
3✔
49
            return omit(["lastReports", "reportsPrevPage"], obj);
1✔
50
        default:
3!
UNCOV
51
            throw new Error(`${name} not supported.`);
×
52
    }
3✔
53
};
3✔
54

55
const updateForPrevPage = (
1✔
56
    name: InputTypes,
3✔
57
    obj: InputDetailsQueryVariables,
3✔
58
) => {
3✔
59
    switch (name) {
3✔
60
        case "vouchers":
3✔
61
            return omit(["firstVouchers", "vouchersNextPage"], obj);
1✔
62
        case "notices":
3✔
63
            return omit(["firstNotices", "noticesNextPage"], obj);
1✔
64
        case "reports":
3✔
65
            return omit(["firstReports", "reportsNextPage"], obj);
1✔
66
        default:
3!
UNCOV
67
            throw new Error(`${name} not supported.`);
×
68
    }
3✔
69
};
3✔
70

71
const getStringifiedDecodedValueOrPayload = (
1✔
72
    envelope: Envelope | undefined,
33✔
73
    originalPayload: string,
33✔
74
) => {
33✔
75
    if (envelope?.error) {
33✔
76
        console.error(envelope.error);
33✔
77
        return originalPayload;
33✔
78
    }
33!
79

80
    if (!envelope || isNilOrEmpty(envelope?.result)) {
33!
UNCOV
81
        return originalPayload;
×
UNCOV
82
    }
×
83

84
    return stringifyContent(envelope.result);
×
85
};
×
86

87
type UseDecodingOnInputResult = [
88
    string,
89
    {
90
        specApplied: Specification | null;
91
        userSpecifications: Specification[];
92
        systemSpecifications: Specification[];
93
        error?: Error;
94
        wasSpecManuallySelected: boolean;
95
    },
96
];
97

98
/**
99
 * Receive the input from a graphQL call and
100
 * It may find a specification that matches a defined condition, therefore decoding the content.
101
 * If an specification is not found it returns the original payload.
102
 * @param input Input data returned by graphQL.
103
 * @param specName Specification id to apply instead of check for matching conditions. (optional)
104
 * @returns { UseDecodingOnInputResult }
105
 */
106
const useDecodingOnInput = (
1✔
107
    input: InputItemFragment,
33✔
108
    specId?: string,
33✔
109
): UseDecodingOnInputResult => {
33✔
110
    const { listSpecifications } = useSpecification();
33✔
111
    const { systemSpecificationAsList } = useSystemSpecifications();
33✔
112

113
    const userSpecifications = listSpecifications() ?? [];
33✔
114
    const specifications = [
33✔
115
        ...systemSpecificationAsList,
33✔
116
        ...userSpecifications,
33✔
117
    ];
33✔
118

119
    const specification = isNilOrEmpty(specId)
33✔
120
        ? findSpecificationFor(input, specifications)
33!
UNCOV
121
        : find((spec) => spec.id === specId, specifications) ?? null;
×
122

123
    const envelope = specification
33✔
124
        ? decodePayload(specification, input.payload as Hex)
33!
UNCOV
125
        : undefined;
×
126

127
    const result = getStringifiedDecodedValueOrPayload(envelope, input.payload);
33✔
128

129
    return [
33✔
130
        result,
33✔
131
        {
33✔
132
            specApplied: specification,
33✔
133
            systemSpecifications: systemSpecificationAsList,
33✔
134
            userSpecifications,
33✔
135
            error: envelope?.error,
33✔
136
            wasSpecManuallySelected: isNotNilOrEmpty(specId),
33✔
137
        },
33✔
138
    ];
33✔
139
};
33✔
140

141
const buildSelectData = (
1✔
142
    userSpecifications: Specification[],
33✔
143
    systemSpecifications: Specification[],
33✔
144
) => {
33✔
145
    const groups = [];
33✔
146

147
    if (userSpecifications.length) {
33!
UNCOV
148
        groups.push({
×
UNCOV
149
            group: "Your Specifications",
×
UNCOV
150
            items: userSpecifications.map((spec) => ({
×
151
                label: spec.name,
×
152
                value: spec.id!,
×
153
            })),
×
154
        });
×
155
    }
×
156

157
    if (systemSpecifications.length) {
33✔
158
        groups.push({
33✔
159
            group: "System Specifications",
33✔
160
            items: systemSpecifications.map((spec) => ({
33✔
161
                label: spec.name,
198✔
162
                value: spec.id!,
198✔
163
            })),
33✔
164
        });
33✔
165
    }
33✔
166

167
    return groups;
33✔
168
};
33✔
169

170
const chainId = Number.parseInt(getConfiguredChainId());
1✔
171

172
/**
173
 * InputDetailsView should be lazy rendered.
174
 * to avoid multiple eager network calls.
175
 */
176
const InputDetailsView: FC<ApplicationInputDataProps> = ({ input }) => {
1✔
177
    const { getConnection, hasConnection, showConnectionModal } =
34✔
178
        useConnectionConfig();
34✔
179
    const appId = input.application.address as Address;
34✔
180
    const inputIdx = input.index;
34✔
181
    const connection = getConnection(appId);
34✔
182
    const [selectedSpec, setSelectedSpec] = useState<string>("");
34✔
183
    const [variables, updateQueryVars] = useState<InputDetailsQueryVariables>({
34✔
184
        firstNotices: 1,
34✔
185
        firstReports: 1,
34✔
186
        firstVouchers: 1,
34✔
187
        inputIdx,
34✔
188
    });
34✔
189

190
    const [result, execQuery] = useQuery<
34✔
191
        InputDetailsQuery,
192
        InputDetailsQueryVariables
193
    >({
34✔
194
        query: InputDetailsDocument,
34✔
195
        pause: true,
34✔
196
        variables,
34✔
197
    });
34✔
198

199
    const reports = result.data?.input.reports;
34✔
200
    const notices = result.data?.input.notices;
34✔
201
    const vouchers = result.data?.input.vouchers;
34✔
202

203
    const showNotices =
34✔
204
        !connection ||
34✔
205
        (connection && notices && payloadOrString(notices) !== "");
19✔
206
    const showReports =
34✔
207
        !connection ||
34✔
208
        (connection && reports && payloadOrString(reports) !== "");
19✔
209
    const showVouchers =
34✔
210
        !connection ||
34✔
211
        (connection && vouchers && payloadOrString(vouchers) !== "");
19✔
212
    const vouchersForExecution = (vouchers?.edges?.map((e) => e.node) ??
34✔
213
        []) as Partial<Voucher>[];
14✔
214
    const showVoucherForExecution =
34✔
215
        showVouchers && vouchersForExecution.length > 0;
34✔
216

217
    const [
34✔
218
        inputContent,
34✔
219
        {
34✔
220
            specApplied,
34✔
221
            error,
34✔
222
            systemSpecifications,
34✔
223
            userSpecifications,
34✔
224
            wasSpecManuallySelected,
34✔
225
        },
34✔
226
    ] = useDecodingOnInput(input, selectedSpec);
34✔
227

228
    const voucherDecoderRes = useVoucherDecoder({
34✔
229
        payload: payloadOrString(vouchers) as Hex,
34✔
230
        destination: destinationOrString(vouchers) as Hex,
34✔
231
        chainId: chainId,
34✔
232
    });
34✔
233

234
    const selectData = buildSelectData(
34✔
235
        userSpecifications,
34✔
236
        systemSpecifications,
34✔
237
    );
34✔
238

239
    useEffect(() => {
34✔
240
        if (connection) execQuery({ url: connection.url });
21✔
241
    }, [connection, execQuery, variables]);
34✔
242

243
    const isSystemSpecAppliedManually =
34✔
244
        wasSpecManuallySelected && included(systemSpecifications, specApplied);
34!
245

246
    const [voucherContentType, setVoucherContentType] =
34✔
247
        useState<ContentType>("raw");
34✔
248

249
    const voucherContent =
34✔
250
        voucherContentType === "raw" || voucherDecoderRes.data === null
34!
251
            ? payloadOrString(vouchers)
33!
NEW
252
            : voucherDecoderRes.data;
×
253

254
    return (
34✔
255
        <Box py="md">
34✔
256
            <InputDetails>
34✔
257
                <InputDetails.InputContent
34✔
258
                    content={inputContent}
34✔
259
                    contentType="raw"
34✔
260
                >
261
                    <Stack gap="sm">
34✔
262
                        <Group>
34✔
263
                            <Select
34✔
264
                                label="Decode Specification"
34✔
265
                                description="When a specification condition(s) match(es), it will be auto-selected."
34✔
266
                                placeholder="Decode content with..."
34✔
267
                                value={specApplied?.id ?? selectedSpec}
34!
268
                                size="md"
34✔
269
                                checkIconPosition="right"
34✔
270
                                data={selectData}
34✔
271
                                onChange={(value) => {
34✔
UNCOV
272
                                    setSelectedSpec(value ?? "");
×
UNCOV
273
                                }}
×
274
                                error={
34✔
275
                                    error
34✔
276
                                        ? `We're not able to decode using ${specApplied?.name}`
33!
UNCOV
277
                                        : null
×
278
                                }
279
                            />
34✔
280
                        </Group>
34✔
281
                        {isSystemSpecAppliedManually && (
34!
UNCOV
282
                            <Group>
×
UNCOV
283
                                <Alert
×
UNCOV
284
                                    data-testid="system-spec-applied-warning"
×
UNCOV
285
                                    icon={
×
UNCOV
286
                                        <TbExclamationCircle
×
UNCOV
287
                                            size={theme.other.iconSize}
×
UNCOV
288
                                        />
×
289
                                    }
UNCOV
290
                                    color="orange"
×
291
                                    title="System Specifications"
×
292
                                >
×
293
                                    Be careful when manually selecting system
294
                                    specifications.
UNCOV
295
                                    <br /> It may show readable information by
×
296
                                    sheer luck of byte length.
UNCOV
297
                                    <br /> They are always auto-selected.
×
UNCOV
298
                                </Alert>
×
UNCOV
299
                            </Group>
×
300
                        )}
301

302
                        {!specApplied && (
34!
303
                            <Group gap={3}>
×
304
                                <Text c="dimmed">
×
305
                                    {`Is this Application ABI encoding it's inputs?`}
×
306
                                </Text>
×
307
                                <NewSpecificationButton
×
UNCOV
308
                                    p={0}
×
309
                                    variant="transparent"
×
310
                                    btnText="Add a Spec!"
×
311
                                />
×
UNCOV
312
                            </Group>
×
313
                        )}
314
                    </Stack>
34✔
315
                </InputDetails.InputContent>
34✔
316

317
                {showReports && (
34✔
318
                    <InputDetails.ReportContent
33✔
319
                        content={payloadOrString(reports)}
33✔
320
                        contentType="raw"
33✔
321
                        onConnect={() => showConnectionModal(appId)}
33✔
322
                        isLoading={result.fetching}
33✔
323
                        isConnected={hasConnection(appId)}
33✔
324
                        paging={{
33✔
325
                            total: reports?.totalCount ?? 0,
33✔
326
                            onNextPage: () => {
33✔
327
                                updateQueryVars((vars) => {
1✔
328
                                    const newVars = {
1✔
329
                                        ...vars,
1✔
330
                                        firstReports: 1,
1✔
331
                                        reportsNextPage:
1✔
332
                                            reports?.pageInfo.endCursor,
1✔
333
                                    };
1✔
334

335
                                    return updateForNextPage(
1✔
336
                                        "reports",
1✔
337
                                        newVars,
1✔
338
                                    );
1✔
339
                                });
1✔
340
                            },
1✔
341
                            onPreviousPage: () => {
33✔
342
                                updateQueryVars((vars) => {
1✔
343
                                    const newVars = {
1✔
344
                                        ...vars,
1✔
345
                                        lastReports: 1,
1✔
346
                                        reportsPrevPage:
1✔
347
                                            reports?.pageInfo.startCursor,
1✔
348
                                    };
1✔
349
                                    return updateForPrevPage(
1✔
350
                                        "reports",
1✔
351
                                        newVars,
1✔
352
                                    );
1✔
353
                                });
1✔
354
                            },
1✔
355
                        }}
33✔
356
                    />
33✔
357
                )}
358

359
                {showNotices && (
34✔
360
                    <InputDetails.NoticeContent
28✔
361
                        content={payloadOrString(notices)}
28✔
362
                        contentType="raw"
28✔
363
                        onConnect={() => showConnectionModal(appId)}
28✔
364
                        isLoading={result.fetching}
28✔
365
                        isConnected={hasConnection(appId)}
28✔
366
                        paging={{
28✔
367
                            total: notices?.totalCount ?? 0,
28✔
368
                            onNextPage: () => {
28✔
369
                                updateQueryVars((vars) => {
1✔
370
                                    const newVars = {
1✔
371
                                        ...vars,
1✔
372
                                        firstNotices: 1,
1✔
373
                                        noticesNextPage:
1✔
374
                                            notices?.pageInfo.endCursor,
1✔
375
                                    };
1✔
376
                                    return updateForNextPage(
1✔
377
                                        "notices",
1✔
378
                                        newVars,
1✔
379
                                    );
1✔
380
                                });
1✔
381
                            },
1✔
382
                            onPreviousPage: () => {
28✔
383
                                updateQueryVars((vars) => {
1✔
384
                                    const newVars = {
1✔
385
                                        ...vars,
1✔
386
                                        lastNotices: 1,
1✔
387
                                        noticesPrevPage:
1✔
388
                                            notices?.pageInfo.startCursor,
1✔
389
                                    };
1✔
390

391
                                    return updateForPrevPage(
1✔
392
                                        "notices",
1✔
393
                                        newVars,
1✔
394
                                    );
1✔
395
                                });
1✔
396
                            },
1✔
397
                        }}
28✔
398
                    />
28✔
399
                )}
400

401
                {showVouchers && (
34✔
402
                    <InputDetails.VoucherContent
28✔
403
                        content={voucherContent}
28✔
404
                        contentType={voucherContentType}
28✔
405
                        onContentTypeChange={setVoucherContentType}
28✔
406
                        onConnect={() => showConnectionModal(appId)}
28✔
407
                        isLoading={result.fetching}
28✔
408
                        isConnected={hasConnection(appId)}
28✔
409
                        paging={{
28✔
410
                            total: vouchers?.totalCount ?? 0,
28✔
411
                            onNextPage: () => {
28✔
412
                                updateQueryVars((vars) => {
1✔
413
                                    const newVars = {
1✔
414
                                        ...vars,
1✔
415
                                        firstVouchers: 1,
1✔
416
                                        vouchersNextPage:
1✔
417
                                            vouchers?.pageInfo.endCursor,
1✔
418
                                    };
1✔
419

420
                                    return updateForNextPage(
1✔
421
                                        "vouchers",
1✔
422
                                        newVars,
1✔
423
                                    );
1✔
424
                                });
1✔
425
                            },
1✔
426
                            onPreviousPage: () => {
28✔
427
                                updateQueryVars((vars) => {
1✔
428
                                    const newVars = {
1✔
429
                                        ...vars,
1✔
430
                                        lastVouchers: 1,
1✔
431
                                        vouchersPrevPage:
1✔
432
                                            vouchers?.pageInfo.startCursor,
1✔
433
                                    };
1✔
434

435
                                    return updateForPrevPage(
1✔
436
                                        "vouchers",
1✔
437
                                        newVars,
1✔
438
                                    );
1✔
439
                                });
1✔
440
                            },
1✔
441
                        }}
28✔
442
                    >
443
                        {showVoucherForExecution ? (
28✔
444
                            <VoucherExecution
14✔
445
                                appId={appId}
14✔
446
                                voucher={vouchersForExecution[0]}
14✔
447
                            />
14✔
448
                        ) : null}
14✔
449
                    </InputDetails.VoucherContent>
28✔
450
                )}
451
            </InputDetails>
34✔
452
        </Box>
34✔
453
    );
454
};
34✔
455

456
export default InputDetailsView;
1✔
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