hardhat-ignition icon indicating copy to clipboard operation
hardhat-ignition copied to clipboard

Better ABI Validation: check type of constructor/call args against abi

Open kanej opened this issue 2 years ago • 2 comments

Add to the general validation phase a check that each argument in the call/constructor args of the relevant futures matches the type expected by the abi.

TODO

  • [ ] Draw up a test plan that will effectively act as a specification
  • [ ] Spike type validation for single future (i.e. named contract deploy)

kanej avatar Jun 20 '23 13:06 kanej

The types of futures that accept arguments and need validation are:

  • contract deploys (not libraries)
  • function calls
  • static calls

For arguments, these are the types of inputs we need to be able to validate:

  • raw primitives (number, bigint, string, boolean)
  • contract futures (resolve to address type)
  • static call futures (resolves to any solidity param type)
  • read argument event futures (resolves to any solidity param type)
  • runtime value
    • account (resolves to address type)
    • module parameter (resolves to number, bigint, string, boolean)
  • a record of any of the above
  • an array of any of the above

For raw primitives, we can let ethers do the heavy lifting for us with something like:

const iface = new ethers.Interface(artifact.abi);

try {
  iface.encodeDeploy([future.constructorArgs])
} catch {
  throw new Error('failed validation')
}

however, this approach would only work for primitive values, so I think primitives should instead just be accounted for within our custom loop that could look something like this (pseudocode):

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

  // 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 = resolveValidTypesForFunctionArgs(artifact, functionName);

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

  return errors;
}

zoeyTM avatar Feb 05 '24 06:02 zoeyTM

@zoeyTM if I am following. During validation we add additional checks to args (either call or constructor) that recursively cross checks the types of the passed values with the ABI's type.

Does ethers v6 give us any util functions to help with this? It seems viem has parseAbi, but I wouldn't want to switch to viem in just his case (and a full viem switch is likely out of scope).

Do you think cross checking the types is extensive custom code? Or is leveraging either ethers/viem the way to go?

kanej avatar Feb 05 '24 10:02 kanej