Handle subgraph schema entity array type and relation fields (#49)

* Handle relation entities in subgraph

* Modify eden-watcher entities to handle subgraph schema data types

* Modify schema gql to match eden subgraph gql shape

* Handle array type fields in subgraph schema

* Fix store get api for array type fields in subgraph entities

* Handle array type in eden-watcher and format await used in params
This commit is contained in:
nikugogoi 2021-11-17 17:01:09 +05:30 committed by nabarun
parent 158c3928c9
commit 800ad79baf
25 changed files with 527 additions and 221 deletions

View File

@ -48,7 +48,8 @@
"multiformats": "^9.4.8",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.2.32",
"yargs": "^17.0.1"
"yargs": "^17.0.1",
"decimal.js": "^10.3.1"
},
"devDependencies": {
"@ethersproject/abi": "^5.3.0",

View File

@ -2,8 +2,8 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Account } from './Account';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
@Entity()
@ -23,8 +23,8 @@ export class Claim {
@Column('bigint', { transformer: bigintTransformer })
index!: bigint;
@ManyToOne(() => Account)
account!: Account;
@Column('varchar')
account!: string;
@Column('bigint', { transformer: bigintTransformer })
totalEarned!: bigint;

View File

@ -2,8 +2,8 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Distributor } from './Distributor';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
@Entity()
@ -17,8 +17,8 @@ export class Distribution {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Distributor)
distributor!: Distributor;
@Column('varchar')
distributor!: string;
@Column('bigint', { transformer: bigintTransformer })
timestamp!: bigint;

View File

@ -2,8 +2,7 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Distribution } from './Distribution';
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity()
export class Distributor {
@ -16,6 +15,6 @@ export class Distributor {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Distribution, { nullable: true })
currentDistribution!: Distribution;
@Column('varchar', { nullable: true })
currentDistribution!: string;
}

View File

@ -3,9 +3,10 @@
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Block } from './Block';
import Decimal from 'decimal.js';
import { ProducerEpoch } from './ProducerEpoch';
import { bigintTransformer } from '@vulcanize/util';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
@Entity()
export class Epoch {
@ -24,11 +25,11 @@ export class Epoch {
@Column('bigint', { transformer: bigintTransformer })
epochNumber!: bigint;
@ManyToOne(() => Block, { nullable: true })
startBlock!: Block;
@Column('varchar', { nullable: true })
startBlock!: string;
@ManyToOne(() => Block, { nullable: true })
endBlock!: Block;
@Column('varchar', { nullable: true })
endBlock!: string;
@Column('bigint', { transformer: bigintTransformer })
producerBlocks!: bigint;
@ -36,8 +37,8 @@ export class Epoch {
@Column('bigint', { transformer: bigintTransformer })
allBlocks!: bigint;
@Column('varchar')
producerBlocksRatio!: string;
@Column('numeric', { default: 0, transformer: decimalTransformer })
producerBlocksRatio!: Decimal;
@ManyToOne(() => ProducerEpoch)
producerRewards!: ProducerEpoch;

View File

@ -2,10 +2,9 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Slot } from './Slot';
import { Staker } from './Staker';
import { bigintTransformer } from '@vulcanize/util';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintArrayTransformer, bigintTransformer } from '@vulcanize/util';
@Entity()
export class Network {
@ -18,17 +17,17 @@ export class Network {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Slot, { nullable: true })
slot0!: Slot;
@Column('varchar', { nullable: true })
slot0!: string;
@ManyToOne(() => Slot, { nullable: true })
slot1!: Slot;
@Column('varchar', { nullable: true })
slot1!: string;
@ManyToOne(() => Slot, { nullable: true })
slot2!: Slot;
@Column('varchar', { nullable: true })
slot2!: string;
@ManyToOne(() => Staker)
stakers!: Staker;
@Column('varchar', { array: true })
stakers!: string[];
@Column('bigint', { nullable: true, transformer: bigintTransformer })
numStakers!: bigint;
@ -36,6 +35,6 @@ export class Network {
@Column('bigint', { transformer: bigintTransformer })
totalStaked!: bigint;
@Column('bigint', { array: true })
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
stakedPercentiles!: bigint[];
}

View File

@ -2,9 +2,10 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Epoch } from './Epoch';
import { bigintTransformer } from '@vulcanize/util';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
@Entity()
export class ProducerEpoch {
@ -20,8 +21,8 @@ export class ProducerEpoch {
@Column('varchar')
address!: string;
@ManyToOne(() => Epoch)
epoch!: Epoch;
@Column('varchar')
epoch!: string;
@Column('bigint', { transformer: bigintTransformer })
totalRewards!: bigint;
@ -29,6 +30,6 @@ export class ProducerEpoch {
@Column('bigint', { transformer: bigintTransformer })
blocksProduced!: bigint;
@Column('varchar')
blocksProducedRatio!: string;
@Column('numeric', { default: 0, transformer: decimalTransformer })
blocksProducedRatio!: Decimal;
}

View File

@ -2,8 +2,7 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Producer } from './Producer';
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity()
export class ProducerSet {
@ -16,6 +15,6 @@ export class ProducerSet {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Producer)
producers!: Producer;
@Column('varchar', { array: true })
producers!: string[];
}

View File

@ -27,6 +27,9 @@ export class ProducerSetChange {
@Column('varchar')
producer!: string;
@Column('integer')
@Column({
type: 'enum',
enum: ProducerSetChangeType
})
changeType!: ProducerSetChangeType;
}

View File

@ -2,9 +2,7 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { RewardScheduleEntry } from './RewardScheduleEntry';
import { Epoch } from './Epoch';
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity()
export class RewardSchedule {
@ -17,15 +15,15 @@ export class RewardSchedule {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => RewardScheduleEntry)
rewardScheduleEntries!: RewardScheduleEntry;
@Column('varchar', { array: true })
rewardScheduleEntries!: string[];
@ManyToOne(() => Epoch, { nullable: true })
lastEpoch!: Epoch;
@Column('varchar', { nullable: true })
lastEpoch!: string;
@ManyToOne(() => Epoch, { nullable: true })
pendingEpoch!: Epoch;
@Column('varchar', { nullable: true })
pendingEpoch!: string;
@ManyToOne(() => RewardScheduleEntry, { nullable: true })
activeRewardScheduleEntry!: RewardScheduleEntry;
@Column('varchar', { nullable: true })
activeRewardScheduleEntry!: string;
}

View File

@ -2,8 +2,8 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Account } from './Account';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
@Entity()
@ -20,8 +20,8 @@ export class Slash {
@Column('bigint', { transformer: bigintTransformer })
timestamp!: bigint;
@ManyToOne(() => Account)
account!: Account;
@Column('varchar')
account!: string;
@Column('bigint', { transformer: bigintTransformer })
slashed!: bigint;

View File

@ -3,8 +3,11 @@
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import Decimal from 'decimal.js';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
import { SlotClaim } from './SlotClaim';
import { bigintTransformer } from '@vulcanize/util';
@Entity()
export class Slot {
@ -35,8 +38,8 @@ export class Slot {
@Column('bigint', { transformer: bigintTransformer })
expirationTime!: bigint;
@Column('varchar')
taxRatePerDay!: string;
@Column('numeric', { default: 0, transformer: decimalTransformer })
taxRatePerDay!: Decimal;
@ManyToOne(() => SlotClaim)
claims!: SlotClaim;

View File

@ -2,9 +2,10 @@
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { Slot } from './Slot';
import { bigintTransformer } from '@vulcanize/util';
import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
@Entity()
export class SlotClaim {
@ -17,8 +18,8 @@ export class SlotClaim {
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Slot)
slot!: Slot;
@Column('varchar')
slot!: string;
@Column('varchar')
owner!: string;
@ -35,6 +36,6 @@ export class SlotClaim {
@Column('bigint', { transformer: bigintTransformer })
expirationTime!: bigint;
@Column('varchar')
taxRatePerDay!: string;
@Column('numeric', { default: 0, transformer: decimalTransformer })
taxRatePerDay!: Decimal;
}

View File

@ -1,5 +1,9 @@
scalar BigInt
scalar Bytes
scalar BigDecimal
type Proof {
data: String!
}
@ -239,7 +243,7 @@ type Query {
type Producer {
id: ID!
active: Boolean!
rewardCollector: String
rewardCollector: Bytes
rewards: BigInt!
confirmedBlocks: BigInt!
pendingEpochBlocks: BigInt!
@ -253,7 +257,7 @@ type ProducerSet {
type ProducerSetChange {
id: ID!
blockNumber: BigInt!
producer: String!
producer: Bytes!
changeType: ProducerSetChangeType!
}
@ -265,8 +269,8 @@ enum ProducerSetChangeType {
type ProducerRewardCollectorChange {
id: ID!
blockNumber: BigInt!
producer: String!
rewardCollector: String!
producer: Bytes!
rewardCollector: Bytes!
}
type RewardScheduleEntry {
@ -311,39 +315,39 @@ type Epoch {
endBlock: Block
producerBlocks: BigInt!
allBlocks: BigInt!
producerBlocksRatio: String!
producerBlocksRatio: BigDecimal!
producerRewards: [ProducerEpoch!]!
}
type ProducerEpoch {
id: ID!
address: String!
address: Bytes!
epoch: Epoch!
totalRewards: BigInt!
blocksProduced: BigInt!
blocksProducedRatio: String!
blocksProducedRatio: BigDecimal!
}
type SlotClaim {
id: ID!
slot: Slot!
owner: String!
owner: Bytes!
winningBid: BigInt!
oldBid: BigInt!
startTime: BigInt!
expirationTime: BigInt!
taxRatePerDay: String!
taxRatePerDay: BigDecimal!
}
type Slot {
id: ID!
owner: String!
delegate: String!
owner: Bytes!
delegate: Bytes!
winningBid: BigInt!
oldBid: BigInt!
startTime: BigInt!
expirationTime: BigInt!
taxRatePerDay: String!
taxRatePerDay: BigDecimal!
claims: [SlotClaim!]!
}
@ -374,7 +378,7 @@ type Distribution {
distributor: Distributor!
timestamp: BigInt!
distributionNumber: BigInt!
merkleRoot: String!
merkleRoot: Bytes!
metadataURI: String!
}

View File

@ -90,14 +90,14 @@ export class Database {
return true;
}).map(async (field) => {
const { type, propertyName } = field;
// Fill _blockNumber as blockNumber and _blockHash as blockHash in the entityInstance (wasm).
if (['_blockNumber', '_blockHash'].includes(propertyName)) {
return toEntityValue(instanceExports, entityInstance, data, type.toString(), propertyName.slice(1));
if (['_blockNumber', '_blockHash'].includes(field.propertyName)) {
field.propertyName = field.propertyName.slice(1);
return toEntityValue(instanceExports, entityInstance, data, field);
}
return toEntityValue(instanceExports, entityInstance, data, type.toString(), propertyName);
return toEntityValue(instanceExports, entityInstance, data, field);
}, {});
await Promise.all(entityValuePromises);
@ -115,7 +115,7 @@ export class Database {
async getEntityValues (instanceExports: any, block: Block, entityInstance: any, entityFields: any): Promise<{ [key: string]: any } > {
const entityValuePromises = entityFields.map(async (field: any) => {
const { type, propertyName } = field;
const { propertyName } = field;
// Get blockHash property for db entry from block instance.
if (propertyName === 'blockHash') {
@ -129,10 +129,10 @@ export class Database {
// Get blockNumber as _blockNumber and blockHash as _blockHash from the entityInstance (wasm).
if (['_blockNumber', '_blockHash'].includes(propertyName)) {
return fromEntityValue(instanceExports, entityInstance, type.toString(), propertyName.slice(1));
return fromEntityValue(instanceExports, entityInstance, propertyName);
}
return fromEntityValue(instanceExports, entityInstance, type.toString(), propertyName);
return fromEntityValue(instanceExports, entityInstance, propertyName);
}, {});
const entityValues = await Promise.all(entityValuePromises);

View File

@ -105,17 +105,27 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
'ethereum.call': async (call: number) => {
const smartContractCall = await ethereum.SmartContractCall.wrap(call);
const contractAddress = await Address.wrap(await smartContractCall.contractAddress);
const contractName = __getString(await smartContractCall.contractName);
const functionName = __getString(await smartContractCall.functionName);
const functionSignature = __getString(await smartContractCall.functionSignature);
let functionParams = __getArray(await smartContractCall.functionParams);
const contractAddressPtr = await smartContractCall.contractAddress;
const contractAddress = await Address.wrap(contractAddressPtr);
const contractNamePtr = await smartContractCall.contractName;
const contractName = __getString(contractNamePtr);
const functionNamePtr = await smartContractCall.functionName;
const functionName = __getString(functionNamePtr);
const functionSignaturePtr = await smartContractCall.functionSignature;
const functionSignature = __getString(functionSignaturePtr);
const functionParamsPtr = await smartContractCall.functionParams;
let functionParams = __getArray(functionParamsPtr);
console.log('ethereum.call params');
console.log('functionSignature:', functionSignature);
const abi = abis[contractName];
const contract = new Contract(__getString(await contractAddress.toHexString()), abi, provider);
const contractAddressStringPtr = await contractAddress.toHexString();
const contract = new Contract(__getString(contractAddressStringPtr), abi, provider);
try {
const functionParamsPromise = functionParams.map(async param => {
@ -142,7 +152,8 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
});
const resultPtrArray: any[] = await Promise.all(resultPtrArrayPromise);
const res = await __newArray(await getIdOfType(TypeId.ArrayEthereumValue), resultPtrArray);
const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue);
const res = await __newArray(arrayEthereumValueId, resultPtrArray);
return res;
} catch (err) {
@ -194,10 +205,12 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
const bigDecimaly = BigDecimal.wrap(y);
const yDigitsBigIntArray = __getArray(await bigDecimaly.digits);
const digitsPtr = await bigDecimaly.digits;
const yDigitsBigIntArray = __getArray(digitsPtr);
const yDigits = BigNumber.from(yDigitsBigIntArray);
const yExpBigIntArray = __getArray(await bigDecimaly.exp);
const expPtr = await bigDecimaly.exp;
const yExpBigIntArray = __getArray(expPtr);
const yExp = BigNumber.from(yExpBigIntArray);
console.log('y digits and exp', yDigits, yExp);
@ -205,11 +218,17 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
'bigDecimal.toString': async (bigDecimal: number) => {
const bigDecimalInstance = BigDecimal.wrap(bigDecimal);
const digitsBigInt = BigInt.wrap(await bigDecimalInstance.digits);
const expBigInt = BigInt.wrap(await bigDecimalInstance.exp);
const digitsPtr = await bigDecimalInstance.digits;
const digitsBigInt = BigInt.wrap(digitsPtr);
const digits = __getString(await digitsBigInt.toString());
const exp = __getString(await expBigInt.toString());
const expPtr = await bigDecimalInstance.exp;
const expBigInt = BigInt.wrap(expPtr);
const digitsStringPtr = await digitsBigInt.toString();
const digits = __getString(digitsStringPtr);
const expStringPtr = await expBigInt.toString();
const exp = __getString(expStringPtr);
const decimal = new Decimal(`${digits}e${exp}`);
const ptr = __newString(decimal.toFixed());
@ -224,11 +243,13 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
const digits = decimal.d.join('');
const digitsBigNumber = BigNumber.from(digits);
const signBigNumber = BigNumber.from(decimal.s);
const digitsBigInt = await BigInt.fromString(await __newString(digitsBigNumber.mul(signBigNumber).toString()));
const digitsStringPtr = await __newString(digitsBigNumber.mul(signBigNumber).toString());
const digitsBigInt = await BigInt.fromString(digitsStringPtr);
// Calculate exp after converting digits to BigInt above.
const exp = decimal.e - digits.length + 1;
const expBigInt = await BigInt.fromString(await __newString(exp.toString()));
const expStringPtr = await __newString(exp.toString());
const expBigInt = await BigInt.fromString(expStringPtr);
const bigDecimal = await BigDecimal.__new(digitsBigInt);
bigDecimal.exp = expBigInt;
@ -259,10 +280,12 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
},
'bigInt.plus': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigNumber = BigNumber.from(__getString(await xBigInt.toString()));
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigNumber = BigNumber.from(__getString(await yBigInt.toString()));
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const sum = xBigNumber.add(yBigNumber);
const ptr = await __newString(sum.toString());
@ -272,10 +295,12 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
},
'bigInt.minus': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigNumber = BigNumber.from(__getString(await xBigInt.toString()));
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigNumber = BigNumber.from(__getString(await yBigInt.toString()));
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const diff = xBigNumber.sub(yBigNumber);
const ptr = await __newString(diff.toString());
@ -285,10 +310,12 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
},
'bigInt.times': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigNumber = BigNumber.from(__getString(await xBigInt.toString()));
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigNumber = BigNumber.from(__getString(await yBigInt.toString()));
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const product = xBigNumber.mul(yBigNumber);
const ptr = await __newString(product.toString());
@ -298,10 +325,12 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
},
'bigInt.dividedBy': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigNumber = BigNumber.from(__getString(await xBigInt.toString()));
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigNumber = BigNumber.from(__getString(await yBigInt.toString()));
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const quotient = xBigNumber.div(yBigNumber);
const ptr = await __newString(quotient.toString());
@ -334,7 +363,8 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
datasource: {
'dataSource.address': async () => {
assert(dataSource);
return Address.fromString(await __newString(dataSource.address));
const addressStringPtr = await __newString(dataSource.address);
return Address.fromString(addressStringPtr);
}
}
};

View File

@ -3,9 +3,11 @@ import path from 'path';
import fs from 'fs-extra';
import debug from 'debug';
import yaml from 'js-yaml';
import Decimal from 'decimal.js';
import { ColumnType } from 'typeorm';
import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
import { TypeId, EthereumValueKind, ValueKind } from './types';
import Decimal from 'decimal.js';
const log = debug('vulcanize:utils');
@ -57,7 +59,8 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
switch (kind) {
case EthereumValueKind.ADDRESS: {
const address = Address.wrap(await value.toAddress());
const addressPtr = await value.toAddress();
const address = Address.wrap(addressPtr);
const addressStringPtr = await address.toHexString();
return __getString(addressStringPtr);
}
@ -76,7 +79,8 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
case EthereumValueKind.INT:
case EthereumValueKind.UINT: {
const bigInt = BigInt.wrap(await value.toBigInt());
const bigIntPtr = await value.toBigInt();
const bigInt = BigInt.wrap(bigIntPtr);
const bigIntStringPtr = await bigInt.toString();
const bigIntString = __getString(bigIntStringPtr);
return BigNumber.from(bigIntString);
@ -113,8 +117,8 @@ export const toEthereumValue = async (instanceExports: any, value: any, type: st
// For uint/int type or enum type.
if (isIntegerOrEnum) {
const valueString = await __newString(value.toString());
const bigInt = await BigInt.fromString(valueString);
const valueStringPtr = await __newString(value.toString());
const bigInt = await BigInt.fromString(valueStringPtr);
let ethereumValue = await ethereum.Value.fromUnsignedBigInt(bigInt);
if (Boolean(isInteger) && !isUnsigned) {
@ -125,18 +129,23 @@ export const toEthereumValue = async (instanceExports: any, value: any, type: st
}
if (type.startsWith('address')) {
return ethereum.Value.fromAddress(await Address.fromString(await __newString(value)));
const valueStringPtr = await __newString(value);
const addressPtr = await Address.fromString(valueStringPtr);
return ethereum.Value.fromAddress(addressPtr);
}
// TODO: Check between fixed bytes and dynamic bytes.
if (type.startsWith('bytes')) {
const byteArray = await ByteArray.fromHexString(await __newString(value));
const valueStringPtr = await __newString(value);
const byteArray = await ByteArray.fromHexString(valueStringPtr);
const bytes = await Bytes.fromByteArray(byteArray);
return ethereum.Value.fromBytes(bytes);
}
// For string type.
return ethereum.Value.fromString(await __newString(value));
const valueStringPtr = await __newString(value);
return ethereum.Value.fromString(valueStringPtr);
};
/**
@ -168,14 +177,22 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
const block = await createBlock(instanceExports, blockData);
// Fill transaction data.
const txHashByteArray = await ByteArray.fromHexString(await __newString(tx.hash));
const txHashStringPtr = await __newString(tx.hash);
const txHashByteArray = await ByteArray.fromHexString(txHashStringPtr);
const txHash = await Bytes.fromByteArray(txHashByteArray);
const txIndex = await BigInt.fromI32(tx.index);
const txFrom = await Address.fromString(await __newString(tx.from));
const txFromStringPtr = await __newString(tx.from);
const txFrom = await Address.fromString(txFromStringPtr);
const txTo = tx.to && await Address.fromString(await __newString(tx.to));
const txToStringPtr = await __newString(tx.to);
const txTo = tx.to && await Address.fromString(txToStringPtr);
const txValuePtr = await BigInt.fromI32(0);
const txGasLimitPtr = await BigInt.fromI32(0);
const txGasPricePtr = await BigInt.fromI32(0);
const txinputPtr = await Bytes.empty();
// Missing fields from watcher in transaction data:
// value
@ -187,33 +204,39 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
txIndex,
txFrom,
txTo,
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await Bytes.empty()
txValuePtr,
txGasLimitPtr,
txGasPricePtr,
txinputPtr
);
const eventParamArrayPromise = eventParamsData.map(async data => {
const { name, value, kind } = data;
const ethValue = await toEthereumValue(instanceExports, value, kind);
const namePtr = await __newString(name);
return ethereum.EventParam.__new(
await __newString(name),
namePtr,
ethValue
);
});
const eventParamArray = await Promise.all(eventParamArrayPromise);
const eventParams = await __newArray(await idOfType(TypeId.ArrayEventParam), eventParamArray);
const arrayEventParamId = await idOfType(TypeId.ArrayEventParam);
const eventParams = await __newArray(arrayEventParamId, eventParamArray);
const addStrPtr = await __newString(contractAddress);
const eventAddressPtr = await Address.fromString(addStrPtr);
const eventIndexPtr = await BigInt.fromI32(eventIndex);
const transactionLogIndexPtr = await BigInt.fromI32(0);
// Create event to be passed to handler.
return ethereum.Event.__new(
await Address.fromString(addStrPtr),
await BigInt.fromI32(eventIndex),
await BigInt.fromI32(0),
eventAddressPtr,
eventIndexPtr,
transactionLogIndexPtr,
null,
block,
transaction,
@ -232,26 +255,40 @@ export const createBlock = async (instanceExports: any, blockData: Block): Promi
} = instanceExports;
// Fill block data.
const blockHashByteArray = await ByteArray.fromHexString(await __newString(blockData.blockHash));
const blockHashStringPtr = await __newString(blockData.blockHash);
const blockHashByteArray = await ByteArray.fromHexString(blockHashStringPtr);
const blockHash = await Bytes.fromByteArray(blockHashByteArray);
const parentHashByteArray = await ByteArray.fromHexString(await __newString(blockData.parentHash));
const parentHashStringPtr = await __newString(blockData.parentHash);
const parentHashByteArray = await ByteArray.fromHexString(parentHashStringPtr);
const parentHash = await Bytes.fromByteArray(parentHashByteArray);
const blockNumber = await BigInt.fromString(await __newString(blockData.blockNumber));
const blockNumberStringPtr = await __newString(blockData.blockNumber);
const blockNumber = await BigInt.fromString(blockNumberStringPtr);
const blockTimestamp = await BigInt.fromString(await __newString(blockData.timestamp));
const timestampStringPtr = await __newString(blockData.timestamp);
const blockTimestamp = await BigInt.fromString(timestampStringPtr);
const stateRootByteArray = await ByteArray.fromHexString(await __newString(blockData.stateRoot));
const stateRootStringPtr = await __newString(blockData.stateRoot);
const stateRootByteArray = await ByteArray.fromHexString(stateRootStringPtr);
const stateRoot = await Bytes.fromByteArray(stateRootByteArray);
const transactionsRootByteArray = await ByteArray.fromHexString(await __newString(blockData.txRoot));
const txRootStringPtr = await __newString(blockData.txRoot);
const transactionsRootByteArray = await ByteArray.fromHexString(txRootStringPtr);
const transactionsRoot = await Bytes.fromByteArray(transactionsRootByteArray);
const receiptsRootByteArray = await ByteArray.fromHexString(await __newString(blockData.receiptRoot));
const receiptRootStringPtr = await __newString(blockData.receiptRoot);
const receiptsRootByteArray = await ByteArray.fromHexString(receiptRootStringPtr);
const receiptsRoot = await Bytes.fromByteArray(receiptsRootByteArray);
const totalDifficulty = await BigInt.fromString(await __newString(blockData.td));
const tdStringPtr = await __newString(blockData.td);
const totalDifficulty = await BigInt.fromString(tdStringPtr);
const unclesHashPtr = await Bytes.empty();
const authorPtr = await Address.zero();
const gasUsedPtr = await BigInt.fromI32(0);
const gasLimitPtr = await BigInt.fromI32(0);
const difficultyPtr = await BigInt.fromI32(0);
// Missing fields from watcher in block data:
// unclesHash
@ -263,16 +300,16 @@ export const createBlock = async (instanceExports: any, blockData: Block): Promi
return await ethereum.Block.__new(
blockHash,
parentHash,
await Bytes.empty(),
await Address.zero(),
unclesHashPtr,
authorPtr,
stateRoot,
transactionsRoot,
receiptsRoot,
blockNumber,
await BigInt.fromI32(0),
await BigInt.fromI32(0),
gasUsedPtr,
gasLimitPtr,
blockTimestamp,
await BigInt.fromI32(0),
difficultyPtr,
totalDifficulty,
null
);
@ -286,109 +323,163 @@ export const getSubgraphConfig = async (subgraphPath: string): Promise<any> => {
throw new Error(`Config file not found: ${configFilePath}`);
}
const config = yaml.load(await fs.readFile(configFilePath, 'utf8'));
const configFile = await fs.readFile(configFilePath, 'utf8');
const config = yaml.load(configFile);
log('config', JSON.stringify(config, null, 2));
return config;
};
export const toEntityValue = async (instanceExports: any, entityInstance: any, data: any, type: string, key: string) => {
const { __newString, BigInt: ExportBigInt, Value, ByteArray, Bytes, BigDecimal } = instanceExports;
export const toEntityValue = async (instanceExports: any, entityInstance: any, data: any, field: ColumnMetadata) => {
const { __newString, Value } = instanceExports;
const { type, isArray, propertyName } = field;
const entityKey = await __newString(propertyName);
const entityValuePtr = await entityInstance.get(entityKey);
const subgraphValue = Value.wrap(entityValuePtr);
const value = data[propertyName];
const entityValue = await formatEntityValue(instanceExports, subgraphValue, type, value, isArray);
return entityInstance.set(entityKey, entityValue);
};
export const fromEntityValue = async (instanceExports: any, entityInstance: any, key: string): Promise<any> => {
const { __newString } = instanceExports;
const entityKey = await __newString(key);
const value = data[key];
const entityValuePtr = await entityInstance.get(entityKey);
return parseEntityValue(instanceExports, entityValuePtr);
};
const parseEntityValue = async (instanceExports: any, valuePtr: number) => {
const {
__getString,
__getArray,
BigInt: ExportBigInt,
Bytes,
BigDecimal,
Value
} = instanceExports;
const value = Value.wrap(valuePtr);
const kind = await value.kind;
switch (kind) {
case ValueKind.STRING: {
const stringValue = await value.toString();
return __getString(stringValue);
}
case ValueKind.BYTES: {
const bytesPtr = await value.toBytes();
const bytes = await Bytes.wrap(bytesPtr);
const bytesStringPtr = await bytes.toHexString();
return __getString(bytesStringPtr);
}
case ValueKind.BOOL: {
const bool = await value.toBoolean();
return Boolean(bool);
}
case ValueKind.INT: {
return value.toI32();
}
case ValueKind.BIGINT: {
const bigIntPtr = await value.toBigInt();
const bigInt = ExportBigInt.wrap(bigIntPtr);
const bigIntStringPtr = await bigInt.toString();
const bigIntString = __getString(bigIntStringPtr);
return BigInt(bigIntString);
}
case ValueKind.BIGDECIMAL: {
const bigDecimalPtr = await value.toBigDecimal();
const bigDecimal = BigDecimal.wrap(bigDecimalPtr);
const bigDecimalStringPtr = await bigDecimal.toString();
return new Decimal(__getString(bigDecimalStringPtr));
}
case ValueKind.ARRAY: {
const arrayPtr = await value.toArray();
const arr = await __getArray(arrayPtr);
const arrDataPromises = arr.map((arrValuePtr: any) => parseEntityValue(instanceExports, arrValuePtr));
return Promise.all(arrDataPromises);
}
case ValueKind.NULL: {
return null;
}
default:
throw new Error(`Unsupported value kind: ${kind}`);
}
};
const formatEntityValue = async (instanceExports: any, subgraphValue: any, type: ColumnType, value: any, isArray: boolean): Promise<any> => {
const { __newString, __newArray, BigInt: ExportBigInt, Value, ByteArray, Bytes, BigDecimal, id_of_type: getIdOfType } = instanceExports;
if (isArray) {
// TODO: Implement handling array of Bytes type field.
const dataArrayPromises = value.map((el: any) => formatEntityValue(instanceExports, subgraphValue, type, el, false));
const dataArray = await Promise.all(dataArrayPromises);
const arrayStoreValueId = await getIdOfType(TypeId.ArrayStoreValue);
const valueArray = await __newArray(arrayStoreValueId, dataArray);
return Value.fromArray(valueArray);
}
switch (type) {
case 'varchar': {
const entityValue = await __newString(value);
const graphValue = Value.wrap(await entityInstance.get(entityKey));
const kind = await graphValue.kind;
const kind = await subgraphValue.kind;
switch (kind) {
case ValueKind.BYTES: {
const byteArray = await ByteArray.fromHexString(entityValue);
const bytes = await Bytes.fromByteArray(byteArray);
return entityInstance.setBytes(entityKey, bytes);
return Value.fromBytes(bytes);
}
default:
return entityInstance.setString(entityKey, entityValue);
return Value.fromString(entityValue);
}
}
case 'integer': {
return entityInstance.setI32(entityKey, value);
return Value.fromI32(value);
}
case 'bigint': {
const bigInt = await ExportBigInt.fromString(await __newString(value.toString()));
const valueStringPtr = await __newString(value.toString());
const bigInt = await ExportBigInt.fromString(valueStringPtr);
return entityInstance.setBigInt(entityKey, bigInt);
return Value.fromBigInt(bigInt);
}
case 'boolean': {
return entityInstance.setBoolean(entityKey, value ? 1 : 0);
return Value.fromBoolean(value ? 1 : 0);
}
case 'enum': {
const entityValue = await __newString(value);
return entityInstance.setString(entityKey, entityValue);
return Value.fromString(entityValue);
}
case 'numeric': {
const bigDecimal = await BigDecimal.fromString(await __newString(value.toString()));
return entityInstance.setBigDecimal(entityKey, bigDecimal);
}
const valueStringPtr = await __newString(value.toString());
const bigDecimal = await BigDecimal.fromString(valueStringPtr);
// TODO: Support more types.
default:
throw new Error(`Unsupported type: ${type}`);
}
};
export const fromEntityValue = async (instanceExports: any, entityInstance: any, type: string, key: string): Promise<any> => {
const { __newString, __getString, BigInt: ExportBigInt, Value, BigDecimal, Bytes } = instanceExports;
const entityKey = await __newString(key);
switch (type) {
case 'varchar': {
const value = Value.wrap(await entityInstance.get(entityKey));
const kind = await value.kind;
switch (kind) {
case ValueKind.BYTES: {
const bytes = await Bytes.wrap(await value.toBytes());
const bytesStringPtr = await bytes.toHexString();
return __getString(bytesStringPtr);
}
default:
return __getString(await entityInstance.getString(entityKey));
}
}
case 'integer': {
return entityInstance.getI32(entityKey);
}
case 'bigint': {
const bigInt = ExportBigInt.wrap(await entityInstance.getBigInt(entityKey));
return BigInt(__getString(await bigInt.toString()));
}
case 'boolean': {
return Boolean(await entityInstance.getBoolean(entityKey));
}
case 'enum': {
return __getString(await entityInstance.getString(entityKey));
}
case 'numeric': {
const bigDecimal = BigDecimal.wrap(await entityInstance.getBigDecimal(entityKey));
return new Decimal(__getString(await bigDecimal.toString()));
return Value.fromBigDecimal(bigDecimal);
}
// TODO: Support more types.

View File

@ -12,6 +12,70 @@ import {
BigDecimal
} from "@graphprotocol/graph-ts";
export class RelatedEntity extends Entity {
constructor(id: string) {
super();
this.set("id", Value.fromString(id));
this.set("paramBigInt", Value.fromBigInt(BigInt.zero()));
this.set("examples", Value.fromStringArray(new Array(0)));
this.set("bigIntArray", Value.fromBigIntArray(new Array(0)));
}
save(): void {
let id = this.get("id");
assert(id != null, "Cannot save RelatedEntity entity without an ID");
if (id) {
assert(
id.kind == ValueKind.STRING,
"Cannot save RelatedEntity entity with non-string ID. " +
'Considering using .toHex() to convert the "id" to a string.'
);
store.set("RelatedEntity", id.toString(), this);
}
}
static load(id: string): RelatedEntity | null {
return changetype<RelatedEntity | null>(store.get("RelatedEntity", id));
}
get id(): string {
let value = this.get("id");
return value!.toString();
}
set id(value: string) {
this.set("id", Value.fromString(value));
}
get paramBigInt(): BigInt {
let value = this.get("paramBigInt");
return value!.toBigInt();
}
set paramBigInt(value: BigInt) {
this.set("paramBigInt", Value.fromBigInt(value));
}
get examples(): Array<string> {
let value = this.get("examples");
return value!.toStringArray();
}
set examples(value: Array<string>) {
this.set("examples", Value.fromStringArray(value));
}
get bigIntArray(): Array<BigInt> {
let value = this.get("bigIntArray");
return value!.toBigIntArray();
}
set bigIntArray(value: Array<BigInt>) {
this.set("bigIntArray", Value.fromBigIntArray(value));
}
}
export class ExampleEntity extends Entity {
constructor(id: string) {
super();
@ -24,6 +88,7 @@ export class ExampleEntity extends Entity {
this.set("paramBytes", Value.fromBytes(Bytes.empty()));
this.set("paramEnum", Value.fromString(""));
this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero()));
this.set("related", Value.fromString(""));
}
save(): void {
@ -114,4 +179,13 @@ export class ExampleEntity extends Entity {
set paramBigDecimal(value: BigDecimal) {
this.set("paramBigDecimal", Value.fromBigDecimal(value));
}
get related(): string {
let value = this.get("related");
return value!.toString();
}
set related(value: string) {
this.set("related", Value.fromString(value));
}
}

View File

@ -3,6 +3,13 @@ enum EnumType {
choice2
}
type RelatedEntity @entity {
id: ID!
paramBigInt: BigInt!
examples: [ExampleEntity!]!
bigIntArray: [BigInt!]!
}
type ExampleEntity @entity {
id: ID!
count: BigInt!
@ -12,4 +19,5 @@ type ExampleEntity @entity {
paramBytes: Bytes!
paramEnum: EnumType!
paramBigDecimal: BigDecimal!
related: RelatedEntity!
}

View File

@ -4,7 +4,7 @@ import {
Example1,
Test
} from '../generated/Example1/Example1';
import { ExampleEntity } from '../generated/schema';
import { ExampleEntity, RelatedEntity } from '../generated/schema';
export function handleTest (event: Test): void {
log.debug('event.address: {}', [event.address.toHexString()]);
@ -15,12 +15,12 @@ export function handleTest (event: Test): void {
// Entities can be loaded from the store using a string ID; this ID
// needs to be unique across all entities of the same type
let entity = ExampleEntity.load(event.transaction.from.toHex());
let entity = ExampleEntity.load(event.transaction.hash.toHexString());
// Entities only exist after they have been saved to the store;
// `null` checks allow to create entities on demand
if (!entity) {
entity = new ExampleEntity(event.transaction.from.toHex());
entity = new ExampleEntity(event.transaction.hash.toHexString());
// Entity fields can be set using simple assignments
entity.count = BigInt.fromString('0');
@ -37,6 +37,25 @@ export function handleTest (event: Test): void {
entity.paramEnum = 'choice1';
entity.paramBigDecimal = BigDecimal.fromString('123');
let relatedEntity = RelatedEntity.load(event.transaction.from.toHex());
if (!relatedEntity) {
relatedEntity = new RelatedEntity(event.transaction.from.toHex());
relatedEntity.paramBigInt = BigInt.fromString('123');
}
const bigIntArray = relatedEntity.bigIntArray;
bigIntArray.push(entity.count);
relatedEntity.bigIntArray = bigIntArray;
const examples = relatedEntity.examples;
examples.push(entity.id);
relatedEntity.examples = examples;
relatedEntity.save();
entity.related = relatedEntity.id;
// Entities can be written to the store with `.save()`
entity.save();

View File

@ -11,7 +11,7 @@
# IPFS API address (can be taken from the output on running the IPFS daemon).
ipfsApiAddr = "/ip4/127.0.0.1/tcp/5001"
subgraphPath = "../graph-node/test/subgraph/example1/build"
[database]

View File

@ -47,4 +47,7 @@ export class ExampleEntity {
@Column('numeric', { default: 0, transformer: decimalTransformer })
paramBigDecimal!: Decimal
@Column('varchar')
related!: string;
}

View File

@ -0,0 +1,28 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer, bigintArrayTransformer } from '@vulcanize/util';
@Entity()
export class RelatedEntity {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string;
@Column('integer')
blockNumber!: number;
@Column('bigint', { transformer: bigintTransformer })
paramBigInt!: bigint;
@Column('varchar', { array: true })
examples!: string[];
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
bigIntArray!: bigint[];
}

View File

@ -77,11 +77,21 @@ type Query {
getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock
}
enum EnumType {
choice1
choice2
}
type ExampleEntity {
id: ID!
count: BigInt!
param1: String!
param2: Int!
paramString: String!
paramInt: Int!
paramBoolean: Boolean!
paramBytes: Bytes!
paramEnum: EnumType!
paramBigDecimal: BigDecimal!
related: String!
}
type Mutation {

View File

@ -80,6 +80,40 @@ export const bigintTransformer: ValueTransformer = {
}
};
export const bigintArrayTransformer: ValueTransformer = {
to: (valueArray?: bigint[]) => {
if (valueArray) {
return valueArray.map(value => bigintTransformer.to(value));
}
return valueArray;
},
from: (valueArray?: string[]) => {
if (valueArray) {
return valueArray.map(value => bigintTransformer.from(value));
}
return valueArray;
}
};
export const decimalArrayTransformer: ValueTransformer = {
to: (valueArray?: Decimal[]) => {
if (valueArray) {
return valueArray.map(value => decimalTransformer.to(value));
}
return valueArray;
},
from: (valueArray?: string[]) => {
if (valueArray) {
return valueArray.map(value => decimalTransformer.from(value));
}
return valueArray;
}
};
export const resetJobs = async (config: Config): Promise<void> => {
const { jobQueue: jobQueueConfig } = config;