Skip to content

Commit

Permalink
use ethers abi parser to validate function abi
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Jun 16, 2023
1 parent c0d33d5 commit b0e00fe
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 32 deletions.
103 changes: 71 additions & 32 deletions src/conditions/base/contract.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ethers } from 'ethers';
import Joi from 'joi';

import { ETH_ADDRESS_REGEXP } from '../const';
Expand All @@ -6,38 +7,76 @@ import { RpcCondition, rpcConditionSchema } from './rpc';

export const STANDARD_CONTRACT_TYPES = ['ERC20', 'ERC721'];

const functionAbiVariable = Joi.object({
internalType: Joi.string().required(),
name: Joi.string().required(),
type: Joi.string().required(),
});

const functionAbiSchema = Joi.object({
name: Joi.string().required(),
type: Joi.string().valid('function').required(),
inputs: Joi.array().items(functionAbiVariable),
outputs: Joi.array().items(functionAbiVariable),
// TODO: Should we restrict this to 'view'?
// stateMutability: Joi.string().valid('view').required(),
}).custom((functionAbi, helper) => {
// Validate method name
const method = helper.state.ancestors[0].method;
if (functionAbi.name !== method) {
return helper.message({
custom: '"method" must be the same as "functionAbi.name"',
});
}

// Validate nr of parameters
const parameters = helper.state.ancestors[0].parameters;
if (functionAbi.inputs?.length !== parameters.length) {
return helper.message({
custom: '"parameters" must have the same length as "functionAbi.inputs"',
});
}

return functionAbi;
});
const functionAbiSchema = Joi
.object
// We can either define a schema here and use it in the custom validator below,
// {
// name: Joi.string().required(),
// type: Joi.string().valid('function').required(),
// inputs: Joi.array(),
// outputs: Joi.array(),
// stateMutability: Joi.string().valid('view', 'pure').required(),
// }
// Or rely on the fact that the ethers.js interface will throw if the ABI is invalid
// But then we also need to make few checks ourselves
()
.custom((functionAbi, helper) => {
// Validate function ABI - Can we parse it?
let asInterface;
try {
asInterface = new ethers.utils.Interface([functionAbi]);
} catch (e: unknown) {
const { message } = e as Error;
return helper.message({
custom: message,
});
}

if (!asInterface.fragments) {
return helper.message({
custom: '"functionAbi" is missing a function fragment',
});
}

if (asInterface.fragments.length > 1) {
return helper.message({
custom: '"functionAbi" must contain exactly one function fragment',
});
}

// Now we just need to validate against the parent schema

// Validate method name
const method = helper.state.ancestors[0].method;
const functionFragment = asInterface.fragments.filter(
(f) => f.name === method
)[0];
if (!functionFragment) {
return helper.message({
custom:
'"functionAbi" does not contain the method specified as "method"',
});
}

// Validate nr of parameters
const parameters = helper.state.ancestors[0].parameters;
if (functionFragment.inputs.length !== parameters.length) {
return helper.message({
custom:
'"parameters" must have the same length as "functionAbi.inputs"',
});
}

// Further validate contents of the function
// TODO: functionFragment.stateMutability is not accessible for some reason
// if (["view", "pure"].includes(functionFragment.stateMutability)) {
// return helper.message({
// custom: '"functionAbi" must be a view or pure function',
// });
// }

return functionAbi;
});

const contractMethodSchemas: Record<string, Joi.Schema> = {
...rpcConditionSchema,
Expand Down
1 change: 1 addition & 0 deletions test/unit/testVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const testContractConditionObj = {
export const testFunctionAbi = {
name: 'myFunction',
type: 'function',
stateMutability: 'view',
inputs: [
{
internalType: 'address',
Expand Down

1 comment on commit b0e00fe

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundled size for the package is listed below:

build/main/src/kits: 19.53 KB
build/main/src/characters: 89.84 KB
build/main/src/policies: 19.53 KB
build/main/src/sdk/strategy: 35.16 KB
build/main/src/sdk: 50.78 KB
build/main/src/conditions/predefined: 19.53 KB
build/main/src/conditions/base: 54.69 KB
build/main/src/conditions/context: 39.06 KB
build/main/src/conditions: 152.34 KB
build/main/src/agents: 31.25 KB
build/main/src: 433.59 KB
build/main/types/ethers-contracts/factories: 82.03 KB
build/main/types/ethers-contracts: 156.25 KB
build/main/types: 160.16 KB
build/main/test: 46.88 KB
build/main: 691.41 KB
build/module/src/kits: 19.53 KB
build/module/src/characters: 89.84 KB
build/module/src/policies: 19.53 KB
build/module/src/sdk/strategy: 31.25 KB
build/module/src/sdk: 46.88 KB
build/module/src/conditions/predefined: 19.53 KB
build/module/src/conditions/base: 54.69 KB
build/module/src/conditions/context: 39.06 KB
build/module/src/conditions: 152.34 KB
build/module/src/agents: 31.25 KB
build/module/src: 425.78 KB
build/module/types/ethers-contracts/factories: 82.03 KB
build/module/types/ethers-contracts: 156.25 KB
build/module/types: 160.16 KB
build/module/test: 42.97 KB
build/module: 679.69 KB
build: 1.34 MB

Please sign in to comment.