Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

autoload: Add abiFillEmptyNames helper #150

Merged
merged 12 commits into from
Nov 8, 2024
258 changes: 258 additions & 0 deletions src/__tests__/abi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import { expect, describe, it } from "vitest";

import { ABI, fillEmptyNames } from "../abi";

describe("fillEmptyNames", () => {
type testCase = {
name: string;
abi: Array<ABI[number] | unknown>;
want: Array<ABI[number] | unknown>;
};

const testCases: testCase[] = [
{
name: "Basic test",
abi: [
{
type: "function",
selector: "0x95d376d7",
payable: false,
stateMutability: "payable",
inputs: [
{
type: "tuple",
name: "",
components: [
{ type: "uint32", name: "" },
{ type: "bytes", name: "" },
{ type: "bytes32", name: "" },
{ type: "uint64", name: "" },
{ type: "address", name: "" },
],
},
{ type: "bytes", name: "" },
],
outputs: [
{
type: "tuple",
name: "",
components: [
{ type: "uint32", name: "" },
{ type: "bytes", name: "" },
{ type: "bytes32", name: "" },
{ type: "uint64", name: "" },
{ type: "address", name: "" },
],
},
{ type: "bytes", name: "" },
],
sig: "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
name: "assignJob",
constant: false,
},
],
want: [
{
type: "function",
selector: "0x95d376d7",
payable: false,
stateMutability: "payable",
inputs: [
{
type: "tuple",
name: "",
components: [
{ type: "uint32", name: "_param0" },
{ type: "bytes", name: "_param1" },
{ type: "bytes32", name: "_param2" },
{ type: "uint64", name: "_param3" },
{ type: "address", name: "_param4" },
],
},
{ type: "bytes", name: "" },
],
outputs: [
{
type: "tuple",
name: "",
components: [
{ type: "uint32", name: "_param0" },
{ type: "bytes", name: "_param1" },
{ type: "bytes32", name: "_param2" },
{ type: "uint64", name: "_param3" },
{ type: "address", name: "_param4" },
],
},
{ type: "bytes", name: "" },
],
sig: "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
name: "assignJob",
constant: false,
},
],
},
{
name: "Nested tuple test",
abi: [
{
name: "test",
selector: "0x12345679",
inputs: [
{
name: "",
type: "tuple",
components: [
{
type: "tuple",
name: "",
components: [
{
type: "tuple",
name: "",
components: [
{ type: "uint256", name: "" },
{ type: "address", name: "x" },
],
},
{
type: "tuple",
name: "n1",
components: [
{ type: "uint256", name: "y" },
{ type: "address", name: "" },
],
},
],
},
],
},
],
stateMutability: "view",
type: "function",
},
],
want: [
{
name: "test",
selector: "0x12345679",
inputs: [
{
name: "",
type: "tuple",
components: [
{
type: "tuple",
name: "_param0",
components: [
{
type: "tuple",
name: "_param0",
components: [
{
type: "uint256",
name: "_param0",
},
{ type: "address", name: "x" },
],
},
{
type: "tuple",
name: "n1",
components: [
{ type: "uint256", name: "y" },
{
type: "address",
name: "_param1",
},
],
},
],
},
],
},
],
stateMutability: "view",
type: "function",
},
],
},
{
name: "Other Tuple types: Tuple[] and Tuple[k]",
abi: [
{
name: "test",
selector: "0x12345679",
inputs: [
{
name: "",
type: "tuple",
components: [
{
name: "",
type: "tuple[]",
components: [
{ name: "", type: "uint256" },
{ name: "", type: "uint256" },
],
},
{
name: "",
type: "tuple[2]",
components: [
{ name: "", type: "uint256" },
{ name: "", type: "uint256" },
],
},
],
},
],
stateMutability: "view",
type: "function",
},
],
want: [
{
name: "test",
selector: "0x12345679",
inputs: [
{
name: "",
type: "tuple",
components: [
{
name: "_param0",
type: "tuple[]",
components: [
{ name: "_param0", type: "uint256" },
{ name: "_param1", type: "uint256" },
],
},
{
name: "_param1",
type: "tuple[2]",
components: [
{ name: "_param0", type: "uint256" },
{ name: "_param1", type: "uint256" },
],
},
],
},
],
stateMutability: "view",
type: "function",
},
],
},
{
name: "Empty ABI",
abi: [],
want: [],
},
];

testCases.forEach((tc) => {
it(tc.name, () => {
expect(fillEmptyNames(tc.abi as ABI)).toStrictEqual(tc.want);
});
});
});
114 changes: 112 additions & 2 deletions src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export type ABIFunction = {
type: "function"; // TODO: constructor, receive, fallback
selector: string;
name?: string;
outputs?: {type: string, length?: number, name: string}[];
inputs?: {type: string, name: string}[];
outputs?: ABIOutput[];
inputs?: ABIInput[];
sig?: string;
sigAlts?: string[];
payable?: boolean;
Expand All @@ -21,4 +21,114 @@ export type ABIEvent = {
// TODO: ...
};

export type ABIInput = {
type: string;
name: string;
length?: number;
components?: ABIInOut[];
}

export type ABIOutput = {
type: string;
name: string;
components?: ABIInOut[];
}

export type ABIInOut = ABIInput|ABIOutput;

export type ABI = (ABIFunction|ABIEvent)[];

/**
* Fills tuple component's empty names in an ABI with generated names
*
* @example
* Input: {
* "type": "function",
* "selector": "0x95d376d7",
* "payable": false,
* "stateMutability": "payable",
* "inputs": [
* {
* "type": "tuple",
* "name": "",
* "components": [
* { "type": "uint32", "name": "" },
* { "type": "bytes", "name": "" },
* { "type": "bytes32", "name": "" },
* { "type": "uint64", "name": "" },
* { "type": "address", "name": "" }
* ]
* },
* { "type": "bytes", "name": "" }
* ],
* "sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
* "name": "assignJob",
* "constant": false
* }
*
* Output: {
* "type": "function",
* "selector": "0x95d376d7",
* "payable": false,
* "stateMutability": "payable",
* "inputs": [
* {
* "type": "tuple",
* "name": "",
* "components": [
* { "type": "uint32", "name": "field0" },
* { "type": "bytes", "name": "field1" },
* { "type": "bytes32", "name": "field2" },
* { "type": "uint64", "name": "field3" },
* { "type": "address", "name": "field4" }
* ]
* },
* { "type": "bytes", "name": "" }
* ],
* "sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
* "name": "assignJob",
* "constant": false
* }
*
* @param abi The ABI to process
* @returns A new ABI with tuple component names filled
*/
export function fillEmptyNames(abi: ABI): ABI {
function processComponents(components: ABIInOut[]): void {
components.forEach((component, index) => {
component.name ||= `_param${index}`;
if (isTupleType(component.type) && component.components) {
processComponents(component.components);
}
});
}

const result: ABI = abi.map((item) => {
if (item.type === "function") {
const func: ABIFunction = { ...item };
func.inputs?.forEach((input) => {
if (isTupleType(input.type) && input.components) {
processComponents(input.components);
}
});
func.outputs?.forEach((output) => {
if (isTupleType(output.type) && output.components) {
processComponents(output.components);
}
});
return func;
}
return item;
});

return result;
}

/**
* Checks if a type is a tuple type (e.g. "tuple", "tuple[]", "tuple[2]")
* @param type type to check
* @returns true if the type is a tuple type
*/
function isTupleType(type: string): boolean {
return type.startsWith("tuple");
}
3 changes: 3 additions & 0 deletions src/whatsabi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ export { proxies };
import * as providers from "./providers.js";
export { providers };

import * as abi from "./abi.js";
export { abi };

import * as errors from "./errors.js";
export { errors };
Loading