diff --git a/packages/taco/src/conditions/schemas/contract.ts b/packages/taco/src/conditions/schemas/contract.ts index 6f0c9ac1..e1a1de2f 100644 --- a/packages/taco/src/conditions/schemas/contract.ts +++ b/packages/taco/src/conditions/schemas/contract.ts @@ -5,9 +5,9 @@ import { AbiParameter } from 'abitype/zod'; import { paramOrContextParamSchema } from './context'; import { rpcConditionSchema } from './rpc'; -import { parseAbi, parseAbiItem } from 'abitype'; +import { parseAbiItem } from 'abitype'; -const functionAbiSchema = z +const verboseAbiSchema = z .object({ name: z.string(), type: z.literal('function'), @@ -48,8 +48,6 @@ const functionAbiSchema = z }, ); -export type FunctionAbiProps = z.infer; - export const humanReadableAbiSchema = z .string().startsWith("function ") .refine( @@ -67,6 +65,10 @@ export const humanReadableAbiSchema = z ) .transform(parseAbiItem); +const abiSchema = z.union([verboseAbiSchema, humanReadableAbiSchema]) + +export type FunctionAbiProps = z.infer; + export const ContractConditionType = 'contract'; export const contractConditionSchema = rpcConditionSchema .extend({ @@ -76,7 +78,7 @@ export const contractConditionSchema = rpcConditionSchema contractAddress: z.string().regex(ETH_ADDRESS_REGEXP).length(42), standardContractType: z.enum(['ERC20', 'ERC721']).optional(), method: z.string(), - functionAbi: functionAbiSchema.optional(), + functionAbi: abiSchema.optional(), parameters: z.array(paramOrContextParamSchema), }) // Adding this custom logic causes the return type to be ZodEffects instead of ZodObject diff --git a/packages/taco/test/conditions/base/contract.test.ts b/packages/taco/test/conditions/base/contract.test.ts index 67e3ec26..ab414501 100644 --- a/packages/taco/test/conditions/base/contract.test.ts +++ b/packages/taco/test/conditions/base/contract.test.ts @@ -16,6 +16,7 @@ import { CustomContextParam, } from '../../../src/conditions/context'; import { testContractConditionObj, testFunctionAbi } from '../../test-utils'; +import { parseAbiItem } from 'abitype'; describe('validation', () => { it('accepts on a valid schema', () => { @@ -235,10 +236,17 @@ describe('supports custom function abi', async () => { stateMutability: 'pure', }, }, - ])('accepts well-formed functionAbi', ({ method, functionAbi }) => { + { + method: 'balanceOf', + functionAbi: 'function balanceOf(address _owner) view returns (uint256 balance)', + }, + ])('accepts well-formed functionAbi', ({ method, functionAbi}) => { const result = ContractCondition.validate(contractConditionSchema, { ...contractConditionObj, - parameters: functionAbi.inputs.map((input) => `fake_parameter_${input}`), // + parameters: + typeof functionAbi === 'string' + ? ['fake_parameter'] + : functionAbi.inputs.map((input) => `fake_parameter_${input}`), functionAbi: functionAbi as FunctionAbiProps, method, }); @@ -246,7 +254,11 @@ describe('supports custom function abi', async () => { expect(result.error).toBeUndefined(); expect(result.data).toBeDefined(); expect(result.data?.method).toEqual(method); - expect(result.data?.functionAbi).toEqual(functionAbi); + if (typeof functionAbi === "string") { + expect(result.data?.functionAbi).toEqual(parseAbiItem(functionAbi)); + } else { + expect(result.data?.functionAbi).toEqual(functionAbi); + } }); it.each([ @@ -336,6 +348,22 @@ describe('supports custom function abi', async () => { }, ); + it('rejects malformed human-readable functionAbi', () => { + const result = ContractCondition.validate(contractConditionSchema, { + ...contractConditionObj, + method: 'invalidMethod', + functionAbi: 'function balance(_owner) invalid human-readable ABI', + }); + + expect(result.error).toBeDefined(); + expect(result.data).toBeUndefined(); + expect(result.error?.format()).toMatchObject({ + functionAbi: { + _errors: ['Invalid Human-Readable ABI format'], + }, + }); + }); + it.each([ { contractAddress: '0x123',