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

SizeCredit / sdk / 18748944295

23 Oct 2025 12:51PM UTC coverage: 54.651% (-1.6%) from 56.275%
18748944295

push

github

web-flow
v1.8.1 (#12)

* v1.8.1

* Fix encoding issue

* Add new error decoder test

95 of 177 branches covered (53.67%)

Branch coverage included in aggregate %.

7 of 30 new or added lines in 9 files covered. (23.33%)

4 existing lines in 1 file now uncovered.

328 of 597 relevant lines covered (54.94%)

3115.09 hits per line

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

83.76
/src/decoder/calldata.ts
1
import { ethers } from "ethers";
6✔
2
import { Result } from "@ethersproject/abi";
3

4
import SizeFactoryV1_8 from "../v1.8/abi/SizeFactory.json";
6✔
5
import SizeFactoryV1_7 from "../v1.7/abi/SizeFactory.json";
6✔
6
import SizeV1_7 from "../v1.7/abi/Size.json";
6✔
7
import SizeV1_8 from "../v1.8/abi/Size.json";
6✔
8
import CollectionsManagerV1_8 from "../v1.8/abi/CollectionsManager.json";
6✔
9
import ERC20 from "../erc20/abi/ERC20.json";
6✔
10

11
import { Action, isActionSet } from "../Authorization";
6✔
12

13
export class CalldataDecoder {
6✔
14
  private readonly abi: ethers.utils.Interface;
15
  private readonly labels: Record<string, string>;
16

17
  constructor(labels: Record<string, string> = {}) {
×
18
    const abis = [
330✔
19
      ...CollectionsManagerV1_8.abi,
20
      ...SizeFactoryV1_8.abi,
21
      ...SizeFactoryV1_7.abi,
22
      ...SizeV1_7.abi,
23
      ...SizeV1_8.abi,
24
      ...ERC20.abi,
25
    ];
26

27
    this.abi = CalldataDecoder.buildInterface(abis);
330✔
28
    this.labels = Object.fromEntries(
330✔
29
      Object.entries(labels).map(([key, value]) => [key.toLowerCase(), value]),
1,449✔
30
    );
31
  }
32

33
  private indent(level: number): string {
34
    return "  ".repeat(level);
1,695✔
35
  }
36

37
  private static buildInterface(abi: any[]): ethers.utils.Interface {
38
    const seen = new Set<string>();
330✔
39
    const deduped = abi
330✔
40
      .filter((item) => item.type === "function")
139,260✔
41
      .filter((item) => {
42
        const sig = `${item.name}(${item.inputs
86,790✔
43
          .map((e: any) => CalldataDecoder.formatType(e))
113,190✔
44
          .join(",")})`;
45
        if (seen.has(sig)) return false;
86,790✔
46
        seen.add(sig);
54,120✔
47
        return true;
54,120✔
48
      });
49
    return new ethers.utils.Interface(deduped);
330✔
50
  }
51

52
  decode(data: string): string {
53
    try {
38✔
54
      const tx = this.abi.parseTransaction({ data });
38✔
55
      return this.recursiveFormat(
37✔
56
        tx.name,
57
        tx.args,
58
        tx.functionFragment.inputs,
59
        0,
60
      );
61
    } catch {
62
      return "Unknown function call or invalid calldata";
1✔
63
    }
64
  }
65

66
  private static formatType(input: any): string {
67
    if (input.type === "tuple") {
263,670✔
68
      const components = input.components
39,270✔
69
        .map((e: any) => CalldataDecoder.formatType(e))
150,480✔
70
        .join(",");
71
      return `(${components})${input.type.endsWith("[]") ? "[]" : ""}`;
39,270!
72
    }
73

74
    if (input.type.startsWith("tuple") && input.type.endsWith("]")) {
224,400!
UNCOV
75
      const components = input.components
×
UNCOV
76
        .map((e: any) => CalldataDecoder.formatType(e))
×
77
        .join(",");
UNCOV
78
      const arrayPart = input.type.slice("tuple".length);
×
UNCOV
79
      return `(${components})${arrayPart}`;
×
80
    }
81

82
    return input.type;
224,400✔
83
  }
84

85
  private toString(value: any): string {
86
    const str = value.toString();
871✔
87
    if (Array.isArray(value)) {
871✔
88
      return `[${value.map((item: any) => this.toString(item)).join(",")}]`;
135✔
89
    } else {
90
      return this.labels[str.toLowerCase()] || str;
816✔
91
    }
92
  }
93

94
  private decodeAuthorizationBitmap(bitmap: bigint): string {
95
    const actions: Action[] = [];
56✔
96
    for (let i = 0; i < Action.NUMBER_OF_ACTIONS; i++) {
56✔
97
      if (isActionSet(bitmap, i)) {
672✔
98
        actions.push(i);
79✔
99
      }
100
    }
101
    return `[${actions.map((a) => Action[a]).join(",")}]`;
79✔
102
  }
103

104
  private recursiveFormat(
105
    name: string,
106
    args: Result,
107
    inputs: ethers.utils.ParamType[],
108
    level: number,
109
  ): string {
110
    const formattedArgs = args.map((arg, i) => {
272✔
111
      const input = inputs[i];
387✔
112

113
      // Special handling for setAuthorization function
114
      if (name === "setAuthorization" && input.type === "uint256") {
387✔
115
        return this.decodeAuthorizationBitmap(BigInt(arg.toString()));
56✔
116
      }
117

118
      if (
331✔
119
        input.type === "bytes" &&
445✔
120
        typeof arg === "string" &&
121
        arg.startsWith("0x")
122
      ) {
123
        return this.tryDecodeNested(arg, level + 1);
57✔
124
      }
125

126
      if (input.type === "bytes[]" && Array.isArray(arg)) {
274✔
127
        const inner = arg.map((innerData: string) =>
53✔
128
          this.tryDecodeNested(innerData, level + 2),
178✔
129
        );
130
        return (
53✔
131
          "[\n" +
132
          this.indent(level + 2) +
133
          inner.join(",\n" + this.indent(level + 2)) +
134
          "\n" +
135
          this.indent(level + 1) +
136
          "]"
137
        );
138
      }
139

140
      if (input.type.startsWith("tuple") && typeof arg === "object") {
221✔
141
        return this.formatTuple(arg, input, level);
103✔
142
      }
143

144
      if (Array.isArray(arg)) {
118✔
145
        return (
1✔
146
          "[" + arg.map((item: any) => this.toString(item)).join(", ") + "]"
1✔
147
        );
148
      }
149

150
      return this.toString(arg);
117✔
151
    });
152

153
    return `${name}(\n${this.indent(level + 1)}${formattedArgs.join(",\n" + this.indent(level + 1))}\n${this.indent(level)})`;
272✔
154
  }
155

156
  private formatTuple(
157
    arg: any,
158
    input: ethers.utils.ParamType,
159
    level: number,
160
  ): string {
161
    const components = input.components || [];
240!
162

163
    const namedArgs = components.map((component) => {
240✔
164
      const value = arg[component.name];
755✔
165

166
      // If the component is a tuple, recursively format it
167
      if (component.type.startsWith("tuple") && typeof value === "object") {
755✔
168
        return `${component.name}: ${this.formatTuple(value, component, level + 1)}`;
137✔
169
      }
170

171
      // If the component is an array of tuples, format each tuple
172
      if (component.type.startsWith("tuple[]") && Array.isArray(value)) {
618!
173
        const formattedTuples = value.map((item: any) => {
×
174
          const arrayComponent = component.arrayChildren;
×
175
          if (!arrayComponent) {
×
176
            return this.toString(item);
×
177
          }
178
          return this.formatTuple(item, arrayComponent, level + 2);
×
179
        });
180
        return `${component.name}: [\n${this.indent(level + 2)}${formattedTuples.join(",\n" + this.indent(level + 2))}\n${this.indent(level + 1)}]`;
×
181
      }
182

183
      return `${component.name}: ${this.toString(value)}`;
618✔
184
    });
185

186
    return (
240✔
187
      "{\n" +
188
      this.indent(level + 2) +
189
      namedArgs.join(",\n" + this.indent(level + 2)) +
190
      "\n" +
191
      this.indent(level + 1) +
192
      "}"
193
    );
194
  }
195

196
  private tryDecodeNested(data: string, level: number): string {
197
    try {
235✔
198
      const nested = this.abi.parseTransaction({ data });
235✔
199
      return this.recursiveFormat(
235✔
200
        nested.name,
201
        nested.args,
202
        nested.functionFragment.inputs,
203
        level,
204
      );
205
    } catch {
206
      return data;
×
207
    }
208
  }
209
}
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

© 2026 Coveralls, Inc