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

Validate constructor/call args against ABI types #690

Draft
wants to merge 1 commit into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/core/src/internal/execution/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ function ethersResultIntoEvmTuple(
* @param iface The interface to search in.
* @param functionName The function name to search for. MUST be validated first.
*/
function getFunctionFragment(
export function getFunctionFragment(
iface: Interface,
functionName: string
): FunctionFragment {
Expand Down Expand Up @@ -639,7 +639,10 @@ function getFunctionFragment(
* @param iface The interface to search in.
* @param eventName The event name to search for. MUST be validated first.
*/
function getEventFragment(iface: Interface, eventName: string): EventFragment {
export function getEventFragment(
iface: Interface,
eventName: string
): EventFragment {
const { ethers } = require("ethers") as typeof import("ethers");

// TODO: Add support for event overloading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
filterToAccountRuntimeValues,
retrieveNestedRuntimeValues,
validateAccountRuntimeValue,
validateArgs,
} from "../utils";

export async function validateNamedContractDeployment(
Expand Down Expand Up @@ -75,6 +76,10 @@ export async function validateNamedContractDeployment(
name: missingParams[0].name,
})
);
} else {
errors.push(
...validateArgs(artifact, future.constructorArgs, deploymentParameters)
);
}

if (isModuleParameterRuntimeValue(future.value)) {
Expand Down
176 changes: 176 additions & 0 deletions packages/core/src/internal/validation/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { IgnitionError } from "../../errors";
import {
isAccountRuntimeValue,
isContractFuture,
isFuture,
isNamedStaticCallFuture,
isReadEventArgumentFuture,
isRuntimeValue,
} from "../../type-guards";
import { Artifact } from "../../types/artifact";
import { DeploymentParameters } from "../../types/deploy";
import {
AccountRuntimeValue,
ArgumentType,
ReadEventArgumentFuture,
RuntimeValue,
StaticCallFuture,
} from "../../types/module";
import { ERRORS } from "../errors-list";
import { getEventFragment, getFunctionFragment } from "../execution/abi";

export function validateAccountRuntimeValue(
arv: AccountRuntimeValue,
Expand Down Expand Up @@ -72,3 +80,171 @@ function checkForValues(

return null;
}

export function validateArgs(
artifact: Artifact,
args: ArgumentType[],
deploymentParameters: DeploymentParameters,
functionName?: string
): IgnitionError[] {
// loop through, probably recursively, and resolve to primitive type strings
const argTypes = args.map((arg) =>
resolveTypeForArg(artifact, arg, deploymentParameters)
);

// get an array of primitive type strings representing the valid type for each arg, derived from the ABI and likely with help from ethers
const validTypes = resolveValidABITypesForArgs(artifact, functionName);

// compare each argument against the valid type
const errors = [];
for (let i = 0; i < argTypes.length; i++) {
if (argTypes[i] !== validTypes[i]) {
errors.push(new IgnitionError("invalid arg type"));
}
}

return errors;
}

function resolveTypeForArg(
artifact: Artifact,
arg: ArgumentType,
deploymentParameters: DeploymentParameters
): string {
if (isRuntimeValue(arg)) {
if (isAccountRuntimeValue(arg)) {
return "string";
}

return (
typeof deploymentParameters[arg.moduleId]?.[arg.name] ?? arg.defaultValue
);
}

if (isFuture(arg)) {
if (isContractFuture(arg)) {
return "string";
}

if (isNamedStaticCallFuture(arg)) {
return resolveTypeForStaticCall(artifact, arg);
}

if (isReadEventArgumentFuture(arg)) {
return resolveTypeForEvent(artifact, arg);
}
}

// todo: handle arrays
if (Array.isArray(arg)) {
return "array";
}

// todo: handle objects
if (typeof arg === "object") {
return "object";
}

return typeof arg;
}

function resolveTypeForStaticCall(
artifact: Artifact,
future: StaticCallFuture<string, string>
): string {
const { ethers } = require("ethers") as typeof import("ethers");

const iface = new ethers.Interface(artifact.abi);
const functionFragment = getFunctionFragment(iface, future.functionName);

if (typeof future.nameOrIndex === "string") {
for (const output of functionFragment.outputs) {
if (output.name === future.nameOrIndex) {
return resolveABITypeToPrimitive(output.baseType);
}
}

throw new Error(`output name not found: ${future.nameOrIndex}`);
} else {
return resolveABITypeToPrimitive(
functionFragment.outputs[future.nameOrIndex].baseType
);
}
}

function resolveTypeForEvent(
artifact: Artifact,
future: ReadEventArgumentFuture
): string {
const { ethers } = require("ethers") as typeof import("ethers");

const iface = new ethers.Interface(artifact.abi);
const eventFragment = getEventFragment(iface, future.eventName);

if (typeof future.nameOrIndex === "string") {
for (const input of eventFragment.inputs) {
if (input.name === future.nameOrIndex) {
return resolveABITypeToPrimitive(input.baseType);
}
}

throw new Error(`input name not found: ${future.nameOrIndex}`);
} else {
return resolveABITypeToPrimitive(
eventFragment.inputs[future.nameOrIndex].baseType
);
}
}

function resolveValidABITypesForArgs(
artifact: Artifact,
functionName?: string
): string[] {
const { ethers } = require("ethers") as typeof import("ethers");

const iface = new ethers.Interface(artifact.abi);
const fragment =
functionName === undefined
? iface.deploy
: getFunctionFragment(iface, functionName);

return fragment.inputs.map((input) =>
resolveABITypeToPrimitive(input.baseType)
);
}

function resolveABITypeToPrimitive(abiType: string): string {
if (abiType.startsWith("array")) {
return "array";
}

if (abiType.startsWith("tuple")) {
return "object";
}

if (abiType.startsWith("uint") || abiType.startsWith("int")) {
return "bigint";
}

if (abiType.startsWith("fixed") || abiType.startsWith("ufixed")) {
return "number";
}

if (abiType.startsWith("bool")) {
return "boolean";
}

if (abiType.startsWith("bytes")) {
return "string";
}

if (abiType.startsWith("address")) {
return "string";
}

if (abiType.startsWith("string")) {
return "string";
}

throw new Error(`unrecognized ABI type: ${abiType}`);
}
Loading