Skip to content

Commit

Permalink
Added Human-Readable ABI format
Browse files Browse the repository at this point in the history
  • Loading branch information
andresceballosm committed Jul 29, 2024
1 parent 74770d2 commit 9cc37e6
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 5 deletions.
55 changes: 53 additions & 2 deletions packages/taco/src/conditions/base/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ const functionAbiSchema = z
},
);

function toJsonAbiFormat(humanReadableAbi: string): any {
const abiWithoutFunctionKeyword = humanReadableAbi.replace(
/^function\s+/,
'',
);
const fragment = ethers.utils.FunctionFragment.from(
abiWithoutFunctionKeyword,
);
const jsonAbi = JSON.parse(fragment.format(ethers.utils.FormatTypes.json));

delete jsonAbi.constant;
delete jsonAbi.payable;

jsonAbi.inputs = jsonAbi.inputs.map((input: any) => ({
...input,
internalType: input.type,
}));
jsonAbi.outputs = jsonAbi.outputs.map((output: any) => ({
...output,
internalType: output.type,
}));

return jsonAbi;
}

export type FunctionAbiProps = z.infer<typeof functionAbiSchema>;

export const ContractConditionType = 'contract';
Expand All @@ -82,7 +107,27 @@ 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: z
.union([
functionAbiSchema,
z
.string()
.refine(
(abi) => {
try {
toJsonAbiFormat(abi);
return true;
} catch (e) {
return false;
}
},
{
message: 'Invalid Human-Readable ABI format',
},
)
.transform(toJsonAbiFormat),
])
.optional(),
parameters: z.array(paramOrContextParamSchema),
})
// Adding this custom logic causes the return type to be ZodEffects instead of ZodObject
Expand All @@ -98,9 +143,15 @@ export const contractConditionSchema = rpcConditionSchema
);

export type ContractConditionProps = z.infer<typeof contractConditionSchema>;
interface ContractConditionHumanReadableAbi extends ContractConditionProps {
functionAbi: string;
}

export class ContractCondition extends Condition {
constructor(value: OmitConditionType<ContractConditionProps>) {
constructor(value: OmitConditionType<ContractConditionHumanReadableAbi | ContractConditionProps>) {
if (typeof value.functionAbi === 'string') {
value.functionAbi = toJsonAbiFormat(value.functionAbi);
}
super(contractConditionSchema, {
conditionType: ContractConditionType,
...value,
Expand Down
51 changes: 48 additions & 3 deletions packages/taco/test/conditions/base/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,29 @@ describe('supports custom function abi', () => {
stateMutability: 'pure',
},
},
{
method: 'balanceOf',
functionAbi: '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}`), //
functionAbi: functionAbi as FunctionAbiProps,
parameters:
typeof functionAbi === 'string'
? ['fake_parameter']
: functionAbi.inputs.map((input) => `fake_parameter_${input}`),
functionAbi: functionAbi as FunctionAbiProps | string,
method,
});

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(typeof result.data?.functionAbi).toBe('object');
} else {
expect(result.data?.functionAbi).toEqual(functionAbi);
}
});

it.each([
Expand Down Expand Up @@ -309,6 +320,40 @@ describe('supports custom function abi', () => {
},
);

it('rejects malformed human-readable functionAbi', () => {
const result = ContractCondition.validate(contractConditionSchema, {
...contractConditionObj,
functionAbi: 'invalid human-readable ABI',
method: 'invalidMethod',
});

expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
expect(result.error?.format()).toMatchObject({
functionAbi: {
_errors: ['Invalid Human-Readable ABI format'],
},
});
});

it('converts human-readable ABI to JSON ABI format', () => {
const humanReadableAbi =
'balanceOf(address _owner) view returns (uint256 balance)';
const condition = new ContractCondition({
...contractConditionObj,
functionAbi: humanReadableAbi,
method: 'balanceOf',
});

expect(condition.value.functionAbi).toEqual({
name: 'balanceOf',
type: 'function',
inputs: [{ name: '_owner', type: 'address', internalType: 'address' }],
outputs: [{ name: 'balance', type: 'uint256', internalType: 'uint256' }],
stateMutability: 'view',
});
});

it.each([
{
contractAddress: '0x123',
Expand Down

0 comments on commit 9cc37e6

Please sign in to comment.