From 32e6a94ed6a6a216041af93bb7c43c4c3bbcc9e0 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 16 Jun 2023 17:00:49 +0200
Subject: [PATCH] use ethers abi parser to validate function abi
---
src/conditions/base/contract.ts | 49 +++++++++++++++-------
test/unit/conditions/base/contract.test.ts | 24 +----------
test/unit/testVariables.ts | 2 +-
3 files changed, 38 insertions(+), 37 deletions(-)
diff --git a/src/conditions/base/contract.ts b/src/conditions/base/contract.ts
index 83a13b6ae..c7f153f31 100644
--- a/src/conditions/base/contract.ts
+++ b/src/conditions/base/contract.ts
@@ -1,3 +1,4 @@
+import { ethers } from 'ethers';
import Joi from 'joi';
import { ETH_ADDRESS_REGEXP } from '../const';
@@ -6,31 +7,51 @@ import { RpcCondition, rpcConditionRecord } from './rpc';
export const STANDARD_CONTRACT_TYPES = ['ERC20', 'ERC721'];
-const functionAbiVariable = Joi.object({
- internalType: Joi.string(), // TODO is this needed?
- 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(),
+ inputs: Joi.array(),
+ outputs: Joi.array(),
+ stateMutability: Joi.string().valid('view', 'pure').required(),
}).custom((functionAbi, helper) => {
+ // Is `functionABI` a valid function fragment?
+ 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;
- if (functionAbi.name !== method) {
+ const functionFragment = asInterface.fragments.filter(
+ (f) => f.name === method
+ )[0];
+ if (!functionFragment) {
return helper.message({
- custom: '"method" must be the same as "functionAbi.name"',
+ custom: '"functionAbi" does not contain the method specified as "method"',
});
}
// Validate nr of parameters
const parameters = helper.state.ancestors[0].parameters;
- if (functionAbi.inputs?.length !== parameters.length) {
+ if (functionFragment.inputs.length !== parameters.length) {
return helper.message({
custom: '"parameters" must have the same length as "functionAbi.inputs"',
});
@@ -39,7 +60,7 @@ const functionAbiSchema = Joi.object({
return functionAbi;
});
-export const contractConditionRecord: Record = {
+export const contractConditionRecord = {
...rpcConditionRecord,
contractAddress: Joi.string().pattern(ETH_ADDRESS_REGEXP).required(),
standardContractType: Joi.string()
diff --git a/test/unit/conditions/base/contract.test.ts b/test/unit/conditions/base/contract.test.ts
index 25fdddd8b..05074ddd1 100644
--- a/test/unit/conditions/base/contract.test.ts
+++ b/test/unit/conditions/base/contract.test.ts
@@ -7,7 +7,7 @@ import {
import { ContractCondition } from '../../../../src/conditions/base';
import { USER_ADDRESS_PARAM } from '../../../../src/conditions/const';
import { fakeWeb3Provider } from '../../../utils';
-import { testContractConditionObj } from '../../testVariables';
+import { testContractConditionObj, testFunctionAbi } from '../../testVariables';
describe('validation', () => {
it('accepts on a valid schema', () => {
@@ -103,30 +103,10 @@ describe('accepts either standardContractType or functionAbi but not both or non
});
describe('supports custom function abi', () => {
- const fakeFunctionAbi = {
- name: 'myFunction',
- type: 'function',
- inputs: [
- {
- name: 'account',
- type: 'address',
- },
- {
- name: 'myCustomParam',
- type: 'uint256',
- },
- ],
- outputs: [
- {
- name: 'someValue',
- type: 'uint256',
- },
- ],
- };
const contractConditionObj = {
...testContractConditionObj,
standardContractType: undefined,
- functionAbi: fakeFunctionAbi,
+ functionAbi: testFunctionAbi,
method: 'myFunction',
parameters: [USER_ADDRESS_PARAM, ':customParam'],
returnValueTest: {
diff --git a/test/unit/testVariables.ts b/test/unit/testVariables.ts
index ef943513a..36031e816 100644
--- a/test/unit/testVariables.ts
+++ b/test/unit/testVariables.ts
@@ -48,6 +48,7 @@ export const testContractConditionObj = {
export const testFunctionAbi = {
name: 'myFunction',
type: 'function',
+ stateMutability: 'view',
inputs: [
{
internalType: 'address',
@@ -67,5 +68,4 @@ export const testFunctionAbi = {
type: 'uint256',
},
],
- stateMutability: 'view',
};