Skip to content

Commit

Permalink
Add typecasting for tuple[] and address[] (#1892)
Browse files Browse the repository at this point in the history
* wrap address[] and tuple[] into changetype

* tests

* pass imports array

* cleanup mapping codegen

* changeset

* add issue to changeset
  • Loading branch information
YaroShkvorets authored Jan 8, 2025
1 parent d1fa5e0 commit b4564a7
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-pears-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphprotocol/graph-cli': minor
---

handle tuple[] and address[] for event parameters - #949
2 changes: 1 addition & 1 deletion packages/cli/src/codegen/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const unrollTuple = ({
path: string[];
index: number; // TODO: index is unused, do we really need it?
value: any;
}) =>
}): { path: string[]; type: string }[] =>
value.components.reduce((acc: any[], component: any, index: number) => {
const name = component.name || `value${index}`;
return acc.concat(
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/protocols/ethereum/scaffold/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const generatePlaceholderHandlers = ({
entity.count = entity.count + BigInt.fromI32(1)
// Entity fields can be set based on event parameters
${generateEventFieldAssignments(event, contractName).slice(0, 2).join('\n')}
${generateEventFieldAssignments(event, contractName).assignments.slice(0, 2).join('\n')}
// Entities can be written to the store with \`.save()\`
entity.save()
Expand Down
171 changes: 162 additions & 9 deletions packages/cli/src/scaffold/__snapshots__/ethereum.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dataSources:
entities:
- ExampleEvent
- ExampleEvent1
- TupleArrayEvent
abis:
- name: Contract
file: ./abis/Contract.json
Expand All @@ -29,6 +30,8 @@ dataSources:
handler: handleExampleEvent
- event: ExampleEvent(bytes32)
handler: handleExampleEvent1
- event: TupleArrayEvent((uint256,address)[],address[])
handler: handleTupleArrayEvent
file: ./src/contract.ts
"
`;
Expand All @@ -38,7 +41,8 @@ exports[`Ethereum subgraph scaffolding > Mapping (default) 1`] = `
import {
Contract,
ExampleEvent,
ExampleEvent1
ExampleEvent1,
TupleArrayEvent
} from "../generated/Contract/Contract"
import { ExampleEntity } from "../generated/schema"
Expand Down Expand Up @@ -89,15 +93,23 @@ export function handleExampleEvent(event: ExampleEvent): void {
}
export function handleExampleEvent1(event: ExampleEvent1): void {}
export function handleTupleArrayEvent(event: TupleArrayEvent): void {}
"
`;

exports[`Ethereum subgraph scaffolding > Mapping (for indexing events) 1`] = `
"import {
ExampleEvent as ExampleEventEvent,
ExampleEvent1 as ExampleEvent1Event
ExampleEvent1 as ExampleEvent1Event,
TupleArrayEvent as TupleArrayEventEvent
} from "../generated/Contract/Contract"
import { ExampleEvent, ExampleEvent1 } from "../generated/schema"
import {
ExampleEvent,
ExampleEvent1,
TupleArrayEvent
} from "../generated/schema"
import { Bytes } from "@graphprotocol/graph-ts"
export function handleExampleEvent(event: ExampleEventEvent): void {
let entity = new ExampleEvent(
Expand Down Expand Up @@ -134,6 +146,82 @@ export function handleExampleEvent1(event: ExampleEvent1Event): void {
entity.save()
}
export function handleTupleArrayEvent(event: TupleArrayEventEvent): void {
let entity = new TupleArrayEvent(
event.transaction.hash.concatI32(event.logIndex.toI32())
)
entity.tupleArray = changetype<Bytes[]>(event.params.tupleArray)
entity.addressArray = changetype<Bytes[]>(event.params.addressArray)
entity.blockNumber = event.block.number
entity.blockTimestamp = event.block.timestamp
entity.transactionHash = event.transaction.hash
entity.save()
}
"
`;

exports[`Ethereum subgraph scaffolding > Mapping handles tuple array type conversion 1`] = `
"import { BigInt, Bytes } from "@graphprotocol/graph-ts"
import {
Contract,
ExampleEvent,
ExampleEvent1,
TupleArrayEvent
} from "../generated/Contract/Contract"
import { ExampleEntity } from "../generated/schema"
export function handleExampleEvent(event: ExampleEvent): void {
// Entities can be loaded from the store using an ID; this ID
// needs to be unique across all entities of the same type
const id = event.transaction.hash.concat(
Bytes.fromByteArray(Bytes.fromBigInt(event.logIndex))
)
let entity = ExampleEntity.load(id)
// Entities only exist after they have been saved to the store;
// \`null\` checks allow to create entities on demand
if (!entity) {
entity = new ExampleEntity(id)
// Entity fields can be set using simple assignments
entity.count = BigInt.fromI32(0)
}
// BigInt and BigDecimal math are supported
entity.count = entity.count + BigInt.fromI32(1)
// Entity fields can be set based on event parameters
entity.a = event.params.a
entity.b = event.params.b
// Entities can be written to the store with \`.save()\`
entity.save()
// Note: If a handler doesn't require existing field values, it is faster
// _not_ to load the entity from the store. Instead, create it fresh with
// \`new Entity(...)\`, set the fields that should be updated and save the
// entity back to the store. Fields that were not set or unset remain
// unchanged, allowing for partial updates to be applied.
// It is also possible to access smart contracts from mappings. For
// example, the contract that has emitted the event can be connected to
// with:
//
// let contract = Contract.bind(event.address)
//
// The following functions can then be called on this contract to access
// state variables and other data:
//
// - contract.someVariable(...)
// - contract.getSomeValue(...)
}
export function handleExampleEvent1(event: ExampleEvent1): void {}
export function handleTupleArrayEvent(event: TupleArrayEvent): void {}
"
`;

Expand Down Expand Up @@ -173,6 +261,15 @@ type ExampleEvent1 @entity(immutable: true) {
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type TupleArrayEvent @entity(immutable: true) {
id: Bytes!
tupleArray: [Bytes!]! # tuple[]
addressArray: [Bytes!]! # address[]
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
"
`;

Expand All @@ -185,7 +282,7 @@ exports[`Ethereum subgraph scaffolding > Test Files (default) 1`] = `
beforeAll,
afterAll
} from "matchstick-as/assembly/index"
import { BigInt, Bytes } from "@graphprotocol/graph-ts"
import { BigInt, Bytes, Address } from "@graphprotocol/graph-ts"
import { ExampleEvent } from "../generated/schema"
import { ExampleEvent as ExampleEventEvent } from "../generated/Contract/Contract"
import { handleExampleEvent } from "../src/contract"
Expand Down Expand Up @@ -257,8 +354,12 @@ describe("Describe entity assertions", () => {

exports[`Ethereum subgraph scaffolding > Test Files (default) 2`] = `
"import { newMockEvent } from "matchstick-as"
import { ethereum, BigInt, Bytes } from "@graphprotocol/graph-ts"
import { ExampleEvent, ExampleEvent1 } from "../generated/Contract/Contract"
import { ethereum, BigInt, Bytes, Address } from "@graphprotocol/graph-ts"
import {
ExampleEvent,
ExampleEvent1,
TupleArrayEvent
} from "../generated/Contract/Contract"
export function createExampleEventEvent(
a: BigInt,
Expand Down Expand Up @@ -305,6 +406,30 @@ export function createExampleEvent1Event(a: Bytes): ExampleEvent1 {
return exampleEvent1Event
}
export function createTupleArrayEventEvent(
tupleArray: Array<ethereum.Tuple>,
addressArray: Array<Address>
): TupleArrayEvent {
let tupleArrayEventEvent = changetype<TupleArrayEvent>(newMockEvent())
tupleArrayEventEvent.parameters = new Array()
tupleArrayEventEvent.parameters.push(
new ethereum.EventParam(
"tupleArray",
ethereum.Value.fromTupleArray(tupleArray)
)
)
tupleArrayEventEvent.parameters.push(
new ethereum.EventParam(
"addressArray",
ethereum.Value.fromAddressArray(addressArray)
)
)
return tupleArrayEventEvent
}
"
`;
Expand All @@ -317,7 +442,7 @@ exports[`Ethereum subgraph scaffolding > Test Files (for indexing events) 1`] =
beforeAll,
afterAll
} from "matchstick-as/assembly/index"
import { BigInt, Bytes } from "@graphprotocol/graph-ts"
import { BigInt, Bytes, Address } from "@graphprotocol/graph-ts"
import { ExampleEvent } from "../generated/schema"
import { ExampleEvent as ExampleEventEvent } from "../generated/Contract/Contract"
import { handleExampleEvent } from "../src/contract"
Expand Down Expand Up @@ -389,8 +514,12 @@ describe("Describe entity assertions", () => {
exports[`Ethereum subgraph scaffolding > Test Files (for indexing events) 2`] = `
"import { newMockEvent } from "matchstick-as"
import { ethereum, BigInt, Bytes } from "@graphprotocol/graph-ts"
import { ExampleEvent, ExampleEvent1 } from "../generated/Contract/Contract"
import { ethereum, BigInt, Bytes, Address } from "@graphprotocol/graph-ts"
import {
ExampleEvent,
ExampleEvent1,
TupleArrayEvent
} from "../generated/Contract/Contract"
export function createExampleEventEvent(
a: BigInt,
Expand Down Expand Up @@ -437,5 +566,29 @@ export function createExampleEvent1Event(a: Bytes): ExampleEvent1 {
return exampleEvent1Event
}
export function createTupleArrayEventEvent(
tupleArray: Array<ethereum.Tuple>,
addressArray: Array<Address>
): TupleArrayEvent {
let tupleArrayEventEvent = changetype<TupleArrayEvent>(newMockEvent())
tupleArrayEventEvent.parameters = new Array()
tupleArrayEventEvent.parameters.push(
new ethereum.EventParam(
"tupleArray",
ethereum.Value.fromTupleArray(tupleArray)
)
)
tupleArrayEventEvent.parameters.push(
new ethereum.EventParam(
"addressArray",
ethereum.Value.fromAddressArray(addressArray)
)
)
return tupleArrayEventEvent
}
"
`;
28 changes: 27 additions & 1 deletion packages/cli/src/scaffold/ethereum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,32 @@ const TEST_CALLABLE_FUNCTIONS = [
},
];

const TEST_TUPLE_ARRAY_EVENT = {
name: 'TupleArrayEvent',
type: 'event',
inputs: [
{
name: 'tupleArray',
type: 'tuple[]',
components: [
{ name: 'field1', type: 'uint256' },
{ name: 'field2', type: 'address' },
],
},
{ name: 'addressArray', type: 'address[]' },
],
};

const TEST_ABI = new ABI(
'Contract',
undefined,
immutable.fromJS([TEST_EVENT, OVERLOADED_EVENT, TEST_CONTRACT, ...TEST_CALLABLE_FUNCTIONS]),
immutable.fromJS([
TEST_EVENT,
OVERLOADED_EVENT,
TEST_TUPLE_ARRAY_EVENT,
TEST_CONTRACT,
...TEST_CALLABLE_FUNCTIONS,
]),
);

const protocol = new Protocol('ethereum');
Expand Down Expand Up @@ -101,6 +123,10 @@ describe('Ethereum subgraph scaffolding', () => {
expect(await scaffoldWithIndexEvents.generateMapping()).toMatchSnapshot();
});

test('Mapping handles tuple array type conversion', async () => {
expect(await scaffold.generateMapping()).toMatchSnapshot();
});

test('Test Files (default)', async () => {
const files = await scaffoldWithIndexEvents.generateTests();
const testFile = files?.['contract.test.ts'];
Expand Down
Loading

0 comments on commit b4564a7

Please sign in to comment.