diff --git a/db/migrations/1698043395524-Data.js b/db/migrations/1698043395524-Data.js new file mode 100644 index 000000000..a35a1ce6e --- /dev/null +++ b/db/migrations/1698043395524-Data.js @@ -0,0 +1,11 @@ +module.exports = class Data1698043395524 { + name = 'Data1698043395524' + + async up(db) { + await db.query(`ALTER TABLE "extrinsic" ADD "fuel_tank" jsonb`) + } + + async down(db) { + await db.query(`ALTER TABLE "extrinsic" DROP COLUMN "fuel_tank"`) + } +} diff --git a/schema.graphql b/schema.graphql index a9f1fd42f..ebe6b865c 100644 --- a/schema.graphql +++ b/schema.graphql @@ -24,6 +24,15 @@ type Fee { who: Account } +type FuelTankData { + id: String! + name: String! + ruleSetId: Int + paysRemainingFee: Boolean + useNoneOrigin: Boolean + feePaid: BigInt +} + type Extrinsic @entity { id: ID! hash: String! @index @@ -46,6 +55,7 @@ type Extrinsic @entity { # Extras participants: [String!]! + fuelTank: FuelTankData } type Event @entity { diff --git a/src/mappings/balances/events/withdraw.ts b/src/mappings/balances/events/withdraw.ts index 28c29e908..4376c0abc 100644 --- a/src/mappings/balances/events/withdraw.ts +++ b/src/mappings/balances/events/withdraw.ts @@ -5,12 +5,11 @@ import { BalancesWithdrawEvent } from '../../../types/generated/events' import { Event } from '../../../types/generated/support' import { CommonContext } from '../../types/contexts' -function getEventData(ctx: CommonContext, event: Event): bigint { +function getEventData(ctx: CommonContext, event: Event) { const data = new BalancesWithdrawEvent(ctx, event) if (data.isMatrixEnjinV603) { - const { amount } = data.asMatrixEnjinV603 - return amount + return data.asMatrixEnjinV603 } throw new UnknownVersionError(data.constructor.name) @@ -20,9 +19,6 @@ export async function withdraw( ctx: CommonContext, block: SubstrateBlock, item: EventItem<'Balances.Withdraw', { event: { args: true; extrinsic: true } }> -): Promise { - const fee = getEventData(ctx, item.event) - if (!fee) return undefined - - return fee +) { + return getEventData(ctx, item.event) } diff --git a/src/mappings/fuelTanks/common.ts b/src/mappings/fuelTanks/common.ts index 91fbd198c..7b4929bf3 100644 --- a/src/mappings/fuelTanks/common.ts +++ b/src/mappings/fuelTanks/common.ts @@ -11,6 +11,10 @@ import { PermittedExtrinsics, FuelTankRuleSet, } from '../../model' +import { Call } from '../../types/generated/support' +import { CommonContext } from '../types/contexts' +import { FuelTanksDispatchAndTouchCall, FuelTanksDispatchCall } from '../../types/generated/calls' +import { UnknownVersionError } from '../../common/errors' export function rulesToMap( ruleId: string, @@ -67,3 +71,37 @@ export function rulesToMap( permittedExtrinsics, } } + +export function getTankDataFromCall(ctx: CommonContext, call: Call) { + let data: FuelTanksDispatchCall | FuelTanksDispatchAndTouchCall + if (call.name === 'FuelTanks.dispatch') { + data = new FuelTanksDispatchCall(ctx, call) + } else { + data = new FuelTanksDispatchAndTouchCall(ctx, call) + } + + if (data.isMatrixEnjinV603) { + return data.asMatrixEnjinV603 + } + + if (data.isV604) { + return data.asV604 + } + + if (data.isV602) { + return data.asV602 + } + + if (data.isV601) { + return data.asV601 + } + if (data.isV600) { + return data.asV600 + } + + if (data.isV500) { + return data.asV500 + } + + throw new UnknownVersionError(data.constructor.name) +} diff --git a/src/mappings/multiTokens/events/minted.ts b/src/mappings/multiTokens/events/minted.ts index 3149078a8..33795f05c 100644 --- a/src/mappings/multiTokens/events/minted.ts +++ b/src/mappings/multiTokens/events/minted.ts @@ -99,9 +99,7 @@ export async function minted( where: { id: `${u8aToHex(data.recipient)}-${data.collectionId}-${data.tokenId}` }, }) - if (token.metadata?.attributes) { - computeTraits(data.collectionId.toString()) - } + computeTraits(data.collectionId.toString()) token.supply += data.amount token.nonFungible = isNonFungible(token) diff --git a/src/model/generated/_fuelTankData.ts b/src/model/generated/_fuelTankData.ts new file mode 100644 index 000000000..0b6193703 --- /dev/null +++ b/src/model/generated/_fuelTankData.ts @@ -0,0 +1,84 @@ +import assert from "assert" +import * as marshal from "./marshal" + +export class FuelTankData { + private _id!: string + private _name!: string + private _ruleSetId!: number | undefined | null + private _paysRemainingFee!: boolean | undefined | null + private _useNoneOrigin!: boolean | undefined | null + private _feePaid!: bigint | undefined | null + + constructor(props?: Partial>, json?: any) { + Object.assign(this, props) + if (json != null) { + this._id = marshal.string.fromJSON(json.id) + this._name = marshal.string.fromJSON(json.name) + this._ruleSetId = json.ruleSetId == null ? undefined : marshal.int.fromJSON(json.ruleSetId) + this._paysRemainingFee = json.paysRemainingFee == null ? undefined : marshal.boolean.fromJSON(json.paysRemainingFee) + this._useNoneOrigin = json.useNoneOrigin == null ? undefined : marshal.boolean.fromJSON(json.useNoneOrigin) + this._feePaid = json.feePaid == null ? undefined : marshal.bigint.fromJSON(json.feePaid) + } + } + + get id(): string { + assert(this._id != null, 'uninitialized access') + return this._id + } + + set id(value: string) { + this._id = value + } + + get name(): string { + assert(this._name != null, 'uninitialized access') + return this._name + } + + set name(value: string) { + this._name = value + } + + get ruleSetId(): number | undefined | null { + return this._ruleSetId + } + + set ruleSetId(value: number | undefined | null) { + this._ruleSetId = value + } + + get paysRemainingFee(): boolean | undefined | null { + return this._paysRemainingFee + } + + set paysRemainingFee(value: boolean | undefined | null) { + this._paysRemainingFee = value + } + + get useNoneOrigin(): boolean | undefined | null { + return this._useNoneOrigin + } + + set useNoneOrigin(value: boolean | undefined | null) { + this._useNoneOrigin = value + } + + get feePaid(): bigint | undefined | null { + return this._feePaid + } + + set feePaid(value: bigint | undefined | null) { + this._feePaid = value + } + + toJSON(): object { + return { + id: this.id, + name: this.name, + ruleSetId: this.ruleSetId, + paysRemainingFee: this.paysRemainingFee, + useNoneOrigin: this.useNoneOrigin, + feePaid: this.feePaid == null ? undefined : marshal.bigint.toJSON(this.feePaid), + } + } +} diff --git a/src/model/generated/extrinsic.model.ts b/src/model/generated/extrinsic.model.ts index f5313f128..1c1adf109 100644 --- a/src/model/generated/extrinsic.model.ts +++ b/src/model/generated/extrinsic.model.ts @@ -3,6 +3,7 @@ import * as marshal from "./marshal" import {Account} from "./account.model" import {Fee} from "./_fee" import {Event} from "./event.model" +import {FuelTankData} from "./_fuelTankData" @Entity_() export class Extrinsic { @@ -63,4 +64,7 @@ export class Extrinsic { @Column_("text", {array: true, nullable: false}) participants!: (string)[] + + @Column_("jsonb", {transformer: {to: obj => obj == null ? undefined : obj.toJSON(), from: obj => obj == null ? undefined : new FuelTankData(undefined, obj)}, nullable: true}) + fuelTank!: FuelTankData | undefined | null } diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index cc6274497..dd5eb319f 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -2,6 +2,7 @@ export * from "./chainInfo.model" export * from "./_marketplace" export * from "./extrinsic.model" export * from "./_fee" +export * from "./_fuelTankData" export * from "./event.model" export * from "./_eventData" export * from "./_multiTokensApproved" diff --git a/src/processor.ts b/src/processor.ts index 0e1070126..f29ae59e2 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -1,13 +1,13 @@ /* eslint-disable no-await-in-loop */ import { BatchContext, BatchProcessorItem, SubstrateBatchProcessor, SubstrateBlock } from '@subsquid/substrate-processor' import { FullTypeormDatabase } from '@subsquid/typeorm-store' -import { hexStripPrefix, hexToU8a } from '@polkadot/util' +import { hexStripPrefix, hexToU8a, u8aToHex } from '@polkadot/util' import { EntityManager } from 'typeorm' import _ from 'lodash' import * as Sentry from '@sentry/node' import { RewriteFrames } from '@sentry/integrations' import config from './config' -import { AccountTokenEvent, Event, Extrinsic, Fee, Listing } from './model' +import { AccountTokenEvent, Event, Extrinsic, Fee, FuelTank, FuelTankData, Listing } from './model' import { createEnjToken } from './createEnjToken' import { chainState } from './chainState' import * as map from './mappings' @@ -17,6 +17,7 @@ import { populateBlock } from './populateBlock' import { updateClaimDetails } from './mappings/claims/common' import { syncAllCollections } from './jobs/collection-stats' import { metadataQueue } from './jobs/process-metadata' +import { getTankDataFromCall } from './mappings/fuelTanks/common' Sentry.init({ dsn: config.sentryDsn, @@ -296,6 +297,7 @@ processor.run( let publicKey = '' let extrinsicSignature: any = {} + let fuelTank = null if (!signature) { publicKey = item.call.args.dest @@ -312,9 +314,49 @@ processor.run( extrinsicSignature = signature } + if (call.name === 'FuelTanks.dispatch' || call.name === 'FuelTanks.dispatch_and_touch') { + const tankData = getTankDataFromCall(ctx as unknown as CommonContext, call) + const tank = await ctx.store.findOneByOrFail(FuelTank, { id: u8aToHex(tankData.tankId.value) }) + + fuelTank = new FuelTankData({ + id: tank.id, + name: tank.name, + feePaid: 0n, + ruleSetId: tankData.ruleSetId, + paysRemainingFee: + 'settings' in tankData && tankData.settings !== undefined + ? tankData.settings.paysRemainingFee + : null, + useNoneOrigin: + 'settings' in tankData && tankData.settings !== undefined + ? tankData.settings.useNoneOrigin + : null, + }) + + // eslint-disable-next-line no-restricted-syntax + for (const eventItem of block.items) { + if (eventItem.name !== 'Balances.Withdraw' || eventItem.event.extrinsic?.id !== id) { + // eslint-disable-next-line no-continue + continue + } + + // eslint-disable-next-line no-await-in-loop + const transfer = await map.balances.events.withdraw( + ctx as unknown as CommonContext, + block.header, + eventItem + ) + + if (transfer && u8aToHex(transfer.who) === tank.id) { + fuelTank.feePaid = transfer.amount + } + } + } + // eslint-disable-next-line no-await-in-loop const signer = await getOrCreateAccount(ctx as unknown as CommonContext, hexToU8a(publicKey)) // TODO: Get or create accounts on batches const callName = call.name.split('.') + const txFee = (fee ?? 0n) + (fuelTank?.feePaid ?? 0n) const extrinsic = new Extrinsic({ id, @@ -331,9 +373,10 @@ processor.run( tip, error, fee: new Fee({ - amount: fee, + amount: txFee, who: signer.id, }), + fuelTank, createdAt: new Date(block.header.timestamp), participants: getParticipants(call.args, publicKey), }) @@ -353,14 +396,14 @@ processor.run( } // eslint-disable-next-line no-await-in-loop - const feeAmount = await map.balances.events.withdraw( + const transfer = await map.balances.events.withdraw( ctx as unknown as CommonContext, block.header, eventItem ) - if (extrinsic.fee && feeAmount) { - extrinsic.fee.amount = feeAmount + if (extrinsic.fee && transfer) { + extrinsic.fee.amount = transfer.amount break } } @@ -391,7 +434,6 @@ processor.run( const lastBlock = ctx.blocks[ctx.blocks.length - 1].header if (lastBlock.height > config.lastBlockHeight - 200) { - // import('./handleJobs') await chainState(ctx as unknown as CommonContext, lastBlock) } } catch (error) {