Skip to content

Commit

Permalink
refactor how GraphQL arguments are retrieved to support passing varia…
Browse files Browse the repository at this point in the history
…bles
  • Loading branch information
therealemjy committed Jul 2, 2023
1 parent 74d94ba commit ff143d8
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-dogs-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eth-graphql": patch
---

Fix issue where variables passed as arguments of a contract field would return an error
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ exports[`createSchema/makeCalls/formatGraphQlArgs converts argument nodes to val
"100",
"0.1",
"0",
"fake variable value",
]
`;

exports[`createSchema/makeCalls/formatGraphQlArgs throws an error if one of the argument values is of the kind NullValue 1`] = `[Error: [eth-graphql] Incorrect valueNode kind detected: NullValue. There is likely an issue with an ABI inside your eth-graphql config file]`;

exports[`createSchema/makeCalls/formatGraphQlArgs throws an error if one of the argument values is of the kind Variable 1`] = `[Error: [eth-graphql] Incorrect valueNode kind detected: Variable. There is likely an issue with an ABI inside your eth-graphql config file]`;
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import { ArgumentNode, Kind, ValueNode } from 'graphql';
import { ArgumentNode, Kind } from 'graphql';

import formatGraphQlArgs from '../formatGraphQlArgs';

describe('createSchema/makeCalls/formatGraphQlArgs', () => {
it.each([Kind.NULL, Kind.VARIABLE])(
'throws an error if one of the argument values is of the kind %s',
kind => {
const args: ArgumentNode[] = [
{
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: 'fake argument name',
},
value: {
kind,
value: 'fake value',
} as ValueNode,
},
];

try {
formatGraphQlArgs(args);

throw new Error('formatGraphQlArgs should have thrown an error but did not');
} catch (error) {
expect(error).toMatchSnapshot();
}
},
);

it('converts argument nodes to valid arguments', () => {
const args: ArgumentNode[] = [
const fakeVariableName = 'fakeVariable';
const argumentNodes: ArgumentNode[] = [
{
kind: Kind.ARGUMENT,
name: {
Expand Down Expand Up @@ -143,9 +117,28 @@ describe('createSchema/makeCalls/formatGraphQlArgs', () => {
value: '0',
},
},
{
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: 'fake name 8',
},
value: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: fakeVariableName,
},
},
},
];

const res = formatGraphQlArgs(args);
const res = formatGraphQlArgs({
argumentNodes,
variableValues: {
[fakeVariableName]: 'fake variable value',
},
});

expect(res).toMatchSnapshot();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,63 @@
import { ArgumentNode, Kind, ValueNode } from 'graphql';
import { ArgumentNode, GraphQLResolveInfo, Kind, ValueNode } from 'graphql';

import EthGraphQlError from '../../EthGraphQlError';
import { SolidityValue } from '../../types';

// Format each type of value node to an argument consumable by the contract
const formatValueNode = (valueNode: ValueNode): SolidityValue => {
if (valueNode.kind === Kind.NULL || valueNode.kind === Kind.VARIABLE) {
throw new EthGraphQlError(
`Incorrect valueNode kind detected: ${valueNode.kind}. There is likely an issue with an ABI inside your eth-graphql config file`,
);
// Extract a node's value
const getNodeValue = ({
valueNode,
variableValues,
}: {
valueNode: ValueNode;
variableValues: GraphQLResolveInfo['variableValues'];
}): SolidityValue => {
if (valueNode.kind === Kind.NULL) {
return null;
}

// Get variable node value
if (valueNode.kind === Kind.VARIABLE) {
const variableName = valueNode.name.value;
return variableValues[variableName] as SolidityValue;
}

// Convert list to array
if (valueNode.kind === Kind.LIST) {
return valueNode.values.map(formatValueNode);
return valueNode.values.map(node =>
getNodeValue({
valueNode: node,
variableValues,
}),
);
}

// Convert object to tuple
if (valueNode.kind === Kind.OBJECT) {
return valueNode.fields.map(field => formatValueNode(field.value));
return valueNode.fields.map(field =>
getNodeValue({
valueNode: field.value,
variableValues,
}),
);
}

return valueNode.value;
};

const formatGraphQlArgs = (args: ReadonlyArray<ArgumentNode>) =>
args.reduce<ReadonlyArray<SolidityValue>>(
(accArguments, argument) => [...accArguments, formatValueNode(argument.value)],
const formatGraphQlArgs = ({
argumentNodes,
variableValues,
}: {
argumentNodes: ReadonlyArray<ArgumentNode>;
variableValues: GraphQLResolveInfo['variableValues'];
}) =>
argumentNodes.reduce<ReadonlyArray<SolidityValue>>(
(accArguments, argument) => [
...accArguments,
getNodeValue({
valueNode: argument.value,
variableValues,
}),
],
[],
);

Expand Down
21 changes: 15 additions & 6 deletions packages/eth-graphql/src/createSchema/makeCalls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,28 @@ const makeCalls = async ({ graphqlResolveInfo, fieldMapping, config, chainId }:
// Get contract address from config if it exists
let contractAddresses = field.contract.address && [field.contract.address[chainId]];

// If contract was defined in config without an address property, then
// it means an array of addresses to call was passed as the first
// If contract was defined in config without an address property, then it
// means an array of addresses to call was passed as the first and only
// argument of the contract field
if (!contractAddresses) {
contractAddresses =
(graphqlResolveInfo.variableValues.addresses as string[]) ||
(formatGraphQlArgs(contractSelection.arguments || [])[0] as string[]);
const contractArguments = formatGraphQlArgs({
argumentNodes: contractSelection.arguments || [],
variableValues: graphqlResolveInfo.variableValues,
});

// Since the a contract field only accepts one "addresses" argument,
// then we know it is the first (and only) argument of the array
contractAddresses = contractArguments[0] as string[];
}

const hasDefinedAddress = !!field.contract.address;

// Format arguments
const contractCallArguments = formatGraphQlArgs(callSelection.arguments || []);
const contractCallArguments = formatGraphQlArgs({
argumentNodes: callSelection.arguments || [],
variableValues: graphqlResolveInfo.variableValues,
});

const contractFunctionSignature = formatToSignature(field.abiItem);

// Shape a contract call for each contract address to call
Expand Down

0 comments on commit ff143d8

Please sign in to comment.