Skip to content

Commit

Permalink
Merge pull request #13 from subquery/starknet-dictionary
Browse files Browse the repository at this point in the history
update starknet dictionary logic
  • Loading branch information
jiqiang90 authored Jan 19, 2025
2 parents 6cf7f01 + 18ccc8f commit 14410c6
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 55 deletions.
208 changes: 208 additions & 0 deletions packages/node/src/indexer/dictionary/v1/starknetDictionaryV1.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import {
StarknetDatasourceKind,
StarknetHandlerKind,
StarknetRuntimeDatasource,
} from '@subql/types-starknet';
import { StarknetProjectDsTemplate } from '../../../configure/SubqueryProject';
import { buildDictionaryV1QueryEntries } from './starknetDictionaryV1';

const mockTempDs: StarknetProjectDsTemplate[] = [
{
name: 'ZkLend',
kind: StarknetDatasourceKind.Runtime,
assets: new Map(),
options: {
// Must be a key of assets
abi: 'zkLend',
// # this is the contract address for zkLend market https://starkscan.co/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05
address:
'0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
},
mapping: {
file: '',
handlers: [
{
kind: StarknetHandlerKind.Call,
handler: 'handleTransaction',
filter: {
to: '0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
type: 'INVOKE',
/**
* The function can either be the function fragment or signature
* function: 'withdraw'
* function: '0x015511cc3694f64379908437d6d64458dc76d02482052bfb8a5b33a72c054c77'
*/
function: 'withdraw',
},
},
{
kind: StarknetHandlerKind.Event,
handler: 'handleLog',
filter: {
/**
* Follows standard log filters for Starknet
* zkLend address: "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05"
*/
topics: [
'Deposit', //0x9149d2123147c5f43d258257fef0b7b969db78269369ebcf5ebb9eef8592f2
],
},
},
],
},
},
];

describe('buildDictionaryV1QueryEntries', () => {
describe('Log filters', () => {
it('Build filter for logs', () => {
const ds: StarknetRuntimeDatasource = {
kind: StarknetDatasourceKind.Runtime,
assets: new Map(),
options: {
// Must be a key of assets
abi: 'zkLend',
// # this is the contract address for zkLend market https://starkscan.co/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05
address:
'0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
},
mapping: {
file: '',
handlers: [
{
kind: StarknetHandlerKind.Event,
handler: 'handleLog',
filter: {
/**
* Follows standard log filters for Starknet
* zkLend address: "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05"
*/
topics: [
'Deposit', //0x9149d2123147c5f43d258257fef0b7b969db78269369ebcf5ebb9eef8592f2
],
},
},
],
},
};

const result = buildDictionaryV1QueryEntries([ds]);

expect(result).toEqual([
{
conditions: [
{
field: 'address',
matcher: 'equalTo',
value:
'0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
},
{
field: 'topics',
matcher: 'contains',
value: [
'0x9149d2123147c5f43d258257fef0b7b969db78269369ebcf5ebb9eef8592f2',
],
},
],
entity: 'logs',
},
]);
});
});
describe('Transaction filters', () => {
it('Build a filter for contract type', () => {
const ds: StarknetRuntimeDatasource = {
kind: StarknetDatasourceKind.Runtime,
assets: new Map(),
startBlock: 1,
mapping: {
file: '',
handlers: [
{
handler: 'handleTransaction',
kind: StarknetHandlerKind.Call,
filter: {
type: 'L1_HANDLER',
},
},
],
},
};

const result = buildDictionaryV1QueryEntries([ds]);

expect(result).toEqual([
[
{
conditions: [
{
field: 'type',
matcher: 'equalTo',
value: 'L1_HANDLER',
},
],
entity: 'calls',
},
],
]);
});

it('Build a filter with include ds option and contract address', () => {
const ds: StarknetRuntimeDatasource = {
kind: StarknetDatasourceKind.Runtime,
assets: new Map(),
options: {
// Must be a key of assets
abi: 'zkLend',
// # this is the contract address for zkLend market https://starkscan.co/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05
address:
'0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
},
mapping: {
file: '',
handlers: [
{
kind: StarknetHandlerKind.Call,
handler: 'handleTransaction',
filter: {
to: '0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
// type: "INVOKE",
/**
* The function can either be the function fragment or signature
* function: 'withdraw'
* function: '0x015511cc3694f64379908437d6d64458dc76d02482052bfb8a5b33a72c054c77'
*/
function: 'withdraw',
},
},
],
},
};

const result = buildDictionaryV1QueryEntries([ds]);
expect(result).toEqual([
{
conditions: [
{
field: 'to',
matcher: 'equalTo',
value:
'0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05',
},
{
field: 'func',
matcher: 'equalTo',
value:
'0x15511cc3694f64379908437d6d64458dc76d02482052bfb8a5b33a72c054c77',
},
],
entity: 'calls',
},
]);
});
});
});
80 changes: 26 additions & 54 deletions packages/node/src/indexer/dictionary/v1/starknetDictionaryV1.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import { NOT_NULL_FILTER } from '@subql/common-starknet';
import { NodeConfig, DictionaryV1, getLogger } from '@subql/node-core';
import {
DictionaryQueryCondition,
Expand All @@ -13,21 +12,16 @@ import {
StarknetTransactionFilter,
SubqlDatasource,
} from '@subql/types-starknet';
import JSON5 from 'json5';
import { sortBy, uniqBy } from 'lodash';
import fetch from 'node-fetch';
import { num } from 'starknet';
import {
StarknetProjectDs,
StarknetProjectDsTemplate,
SubqueryProject,
} from '../../../configure/SubqueryProject';
import { encodeSelectorToHex } from '../../../starknet/utils.starknet';
import { encodeSelectorToHex, hexEq } from '../../../starknet/utils.starknet';
import { yargsOptions } from '../../../yargs';
import { groupedDataSources, validAddresses } from '../utils';

const CHAIN_ALIASES_URL =
'https://raw.githubusercontent.com/subquery/templates/main/chainAliases.json5';

const logger = getLogger('dictionary-v1');

// Adds the addresses to the query conditions if valid
Expand Down Expand Up @@ -69,31 +63,19 @@ function eventFilterToQueryEntry(
): DictionaryV1QueryEntry {
const conditions: DictionaryQueryCondition[] = [];
applyAddresses(conditions, addresses);
// No null not needed, can use [] instead
if (filter?.topics) {
for (let i = 0; i < Math.min(filter.topics.length, 4); i++) {
const topic = filter.topics[i];
if (!topic) {
continue;
}
const field = `topics${i}`;

if (topic === NOT_NULL_FILTER) {
conditions.push({
field,
value: false,
matcher: 'isNull',
});
} else {
conditions.push({
field,
value: encodeSelectorToHex(topic),
matcher: 'equalTo',
});
}
}
const hexTopics: string[] = filter.topics
.filter((topic) => topic !== null && topic !== undefined)
.map((topic) => (num.isHex(topic) ? topic : encodeSelectorToHex(topic)));
conditions.push({
field: 'topics',
value: hexTopics,
matcher: 'contains',
});
}
return {
entity: 'evmLogs',
entity: 'logs',
conditions,
};
}
Expand All @@ -110,21 +92,27 @@ function callFilterToQueryEntry(
condition.field = 'to';
}
}

if (!filter) {
return {
entity: 'evmTransactions',
entity: 'calls',
conditions,
};
}

if (filter.from) {
conditions.push({
field: 'from',
value: filter.from.toLowerCase(),
matcher: 'equalTo',
});
}
if (filter.type) {
conditions.push({
field: 'type',
value: filter.type,
matcher: 'equalTo',
});
}

const optionsAddresses = conditions.find((c) => c.field === 'to');
if (!optionsAddresses) {
if (filter.to) {
Expand All @@ -145,7 +133,6 @@ function callFilterToQueryEntry(
`TransactionFilter 'to' conflict with 'address' in data source options`,
);
}

if (filter.function === null || filter.function === '0x') {
conditions.push({
field: 'func',
Expand All @@ -155,12 +142,14 @@ function callFilterToQueryEntry(
} else if (filter.function) {
conditions.push({
field: 'func',
value: filter.function,
value: num.isHex(filter.function)
? filter.function
: encodeSelectorToHex(filter.function),
matcher: 'equalTo',
});
}
return {
entity: 'evmTransactions',
entity: 'calls',
conditions,
};
}
Expand Down Expand Up @@ -221,41 +210,24 @@ export class StarknetDictionaryV1 extends DictionaryV1<SubqlDatasource> {
project: SubqueryProject,
nodeConfig: NodeConfig,
dictionaryUrl: string,
chainId?: string,
) {
super(dictionaryUrl, chainId ?? project.network.chainId, nodeConfig);
super(dictionaryUrl, project.network.chainId, nodeConfig);
}

static async create(
project: SubqueryProject,
nodeConfig: NodeConfig,
dictionaryUrl: string,
): Promise<StarknetDictionaryV1> {
/*Some dictionarys for EVM are built with other SDKs as they are chains with an EVM runtime
* we maintain a list of aliases so we can map the evmChainId to the genesis hash of the other SDKs
* e.g moonbeam is built with Substrate SDK but can be used as an EVM dictionary
*/
const chainAliases = await this.getEvmChainId();
const chainAlias = chainAliases[project.network.chainId];

const dictionary = new StarknetDictionaryV1(
project,
nodeConfig,
dictionaryUrl,
chainAlias,
);
await dictionary.init();
return dictionary;
}

private static async getEvmChainId(): Promise<Record<string, string>> {
const response = await fetch(CHAIN_ALIASES_URL);

const raw = await response.text();
// We use JSON5 here because the file has comments in it
return JSON5.parse(raw);
}

buildDictionaryQueryEntries(
// Add name to datasource as templates have this set
dataSources: (StarknetProjectDs | StarknetProjectDsTemplate)[],
Expand Down
Loading

0 comments on commit 14410c6

Please sign in to comment.