From 32ff72af6c2d49ef8df9144899d77a7a00e265b6 Mon Sep 17 00:00:00 2001 From: zoeyTM Date: Tue, 12 Sep 2023 16:51:29 -0400 Subject: [PATCH] add validation rules for static call and tests --- packages/core/src/internal/execution/abi.ts | 37 +++++++++ .../stageOne/validateNamedStaticCall.ts | 12 ++- packages/core/test/staticCall.ts | 78 +++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/packages/core/src/internal/execution/abi.ts b/packages/core/src/internal/execution/abi.ts index 15d3760cb..9bdf9d866 100644 --- a/packages/core/src/internal/execution/abi.ts +++ b/packages/core/src/internal/execution/abi.ts @@ -697,6 +697,43 @@ function getEventArgumentParamType( return paramType; } +/** + * Validates the param type of a static call return value, throwing a validation error if it's not found. + */ +export function validateFunctionArgumentParamType( + contractName: string, + functionName: string, + artifact: Artifact, + argument: string | number +): void { + const { ethers } = require("ethers") as typeof import("ethers"); + const iface = new ethers.Interface(artifact.abi); + const functionFragment = getFunctionFragment(iface, functionName); + + if (typeof argument === "string") { + let hasArg = false; + for (const output of functionFragment.outputs) { + if (output.name === argument) { + hasArg = true; + } + } + + if (!hasArg) { + throw new IgnitionValidationError( + `Function ${functionName} of contract ${contractName} has no return value named ${argument}` + ); + } + } else { + const paramType = functionFragment.outputs[argument]; + + if (paramType === undefined) { + throw new IgnitionValidationError( + `Function ${functionName} of contract ${contractName} has only ${functionFragment.outputs.length} return values, but value ${argument} was requested` + ); + } + } +} + /** * Returns true if the given param type has a dynamic size. */ diff --git a/packages/core/src/internal/validation/stageOne/validateNamedStaticCall.ts b/packages/core/src/internal/validation/stageOne/validateNamedStaticCall.ts index 919f3bf3b..46ababb8d 100644 --- a/packages/core/src/internal/validation/stageOne/validateNamedStaticCall.ts +++ b/packages/core/src/internal/validation/stageOne/validateNamedStaticCall.ts @@ -2,7 +2,10 @@ import { IgnitionValidationError } from "../../../errors"; import { isArtifactType } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; import { NamedStaticCallFuture } from "../../../types/module"; -import { validateArtifactFunction } from "../../execution/abi"; +import { + validateArtifactFunction, + validateFunctionArgumentParamType, +} from "../../execution/abi"; export async function validateNamedStaticCall( future: NamedStaticCallFuture, @@ -26,4 +29,11 @@ export async function validateNamedStaticCall( future.args, true ); + + validateFunctionArgumentParamType( + future.contract.contractName, + future.functionName, + artifact, + future.nameOrIndex + ); } diff --git a/packages/core/test/staticCall.ts b/packages/core/test/staticCall.ts index e0f6bbb43..596875453 100644 --- a/packages/core/test/staticCall.ts +++ b/packages/core/test/staticCall.ts @@ -673,6 +673,84 @@ describe("static call", () => { /Function inc in contract Another is not 'pure' or 'view' and cannot be statically called/ ); }); + + it("should not validate a nameOrIndex that is invalid (nonexistent name)", async () => { + const fakeArtifact: Artifact = { + abi: [ + { + inputs: [], + name: "inc", + outputs: [ + { + internalType: "bool", + name: "b", + type: "bool", + }, + ], + stateMutability: "pure", + type: "function", + }, + ], + contractName: "", + bytecode: "", + linkReferences: {}, + }; + + const module = buildModule("Module1", (m) => { + const another = m.contractFromArtifact("Another", fakeArtifact, []); + m.staticCall(another, "inc", [], "a"); + + return { another }; + }); + + const future = getFuturesFromModule(module).find( + (v) => v.type === FutureType.NAMED_STATIC_CALL + ); + + await assert.isRejected( + validateNamedStaticCall(future as any, setupMockArtifactResolver()), + /Function inc of contract Another has no return value named a/ + ); + }); + + it("should not validate a nameOrIndex that is invalid (out of range)", async () => { + const fakeArtifact: Artifact = { + abi: [ + { + inputs: [], + name: "inc", + outputs: [ + { + internalType: "bool", + name: "b", + type: "bool", + }, + ], + stateMutability: "pure", + type: "function", + }, + ], + contractName: "", + bytecode: "", + linkReferences: {}, + }; + + const module = buildModule("Module1", (m) => { + const another = m.contractFromArtifact("Another", fakeArtifact, []); + m.staticCall(another, "inc", [], 2); + + return { another }; + }); + + const future = getFuturesFromModule(module).find( + (v) => v.type === FutureType.NAMED_STATIC_CALL + ); + + await assert.isRejected( + validateNamedStaticCall(future as any, setupMockArtifactResolver()), + /Function inc of contract Another has only 1 return values, but value 2 was requested/ + ); + }); }); describe("stage two", () => {