Skip to content

Commit

Permalink
Feature: Add timestamp field to the Application domain (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunomenezes authored Nov 27, 2023
1 parent 722d051 commit 2b82064
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 8 deletions.
11 changes: 11 additions & 0 deletions db/migrations/1700532563386-Data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = class Data1700532563386 {
name = 'Data1700532563386'

async up(db) {
await db.query(`ALTER TABLE "application" ADD "timestamp" numeric NOT NULL`)
}

async down(db) {
await db.query(`ALTER TABLE "application" DROP COLUMN "timestamp"`)
}
}
1 change: 1 addition & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type ApplicationFactory @entity @cardinality(value: 5) {
type Application @entity @cardinality(value: 100) {
id: ID!
owner: String
timestamp: BigInt!
factory: ApplicationFactory
inputs: [Input!] @derivedFrom(field: "application")
}
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/ApplicationCreated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export default class ApplicationCreated implements Handler {
id,
factory,
owner: dappOwner.toLowerCase(),
timestamp: timestamp / 1000n,
});

this.applicationStorage.set(id, app);
ctx.log.info(`${id} (Application) stored`);
}
Expand Down
8 changes: 6 additions & 2 deletions src/handlers/InputAdded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ export default class InputAdded implements Handler {
const timestamp = BigInt(log.block.timestamp);
const event = events.InputAdded.decode(log);
const dappId = event.dapp.toLowerCase();
const timestampInSeconds = timestamp / 1000n;

let application =
this.applicationStorage.get(dappId) ??
(await ctx.store.get(Application, dappId));
if (!application) {
ctx.log.warn(`${dappId} (Application) not found`);
application = new Application({ id: dappId });
application = new Application({
id: dappId,
timestamp: timestampInSeconds,
});
this.applicationStorage.set(dappId, application);
ctx.log.info(`${dappId} (Application) stored`);
}
Expand All @@ -73,7 +77,7 @@ export default class InputAdded implements Handler {
index: Number(event.inboxInputIndex),
msgSender: event.sender.toLowerCase(),
payload: event.input,
timestamp: timestamp / 1000n,
timestamp: timestampInSeconds,
blockNumber: BigInt(log.block.height),
blockHash: log.block.hash,
transactionHash: log.transaction?.hash,
Expand Down
26 changes: 26 additions & 0 deletions tests/handlers/ApplicationCreated.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import ApplicationCreated from '../../src/handlers/ApplicationCreated';
import { Application } from '../../src/model';
import { block, ctx, logs } from '../stubs/params';

vi.mock('../../src/model/', async (importOriginal) => {
Expand All @@ -13,10 +14,13 @@ vi.mock('../../src/model/', async (importOriginal) => {
};
});

const ApplicationMock = vi.mocked(Application);

describe('ApplicationCreated', () => {
let applicationCreated: ApplicationCreated;
const mockFactoryStorage = new Map();
const mockApplicationStorage = new Map();

beforeEach(() => {
applicationCreated = new ApplicationCreated(
mockFactoryStorage,
Expand All @@ -26,6 +30,7 @@ describe('ApplicationCreated', () => {
mockApplicationStorage.clear();
vi.clearAllMocks();
});

describe('handle', async () => {
test('call with correct params', async () => {
vi.spyOn(applicationCreated, 'handle');
Expand All @@ -49,5 +54,26 @@ describe('ApplicationCreated', () => {
expect(mockFactoryStorage.has(logs[1].address)).toBe(true);
expect(mockApplicationStorage.has(applicationId)).toBe(true);
});

test('set the timestamp in seconds from the block timestamp', async () => {
const expectedParams = vi.fn();

ApplicationMock.mockImplementationOnce((args) => {
expectedParams(args);
return new Application(args);
});

await applicationCreated.handle(logs[1], block, ctx);
const applicationId = '0x0be010fa7e70d74fa8b6729fe1ae268787298f54';
const timestampInSeconds = BigInt(logs[1].block.timestamp) / 1000n;

expect(expectedParams).toHaveBeenCalledOnce();
expect(expectedParams).toHaveBeenCalledWith({
factory: expect.any(Object),
id: applicationId,
owner: '0x74d093f6911ac080897c3145441103dabb869307',
timestamp: timestampInSeconds,
});
});
});
});
24 changes: 23 additions & 1 deletion tests/handlers/InputAdded.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { dataSlice, getUint } from 'ethers';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { Contract } from '../../src/abi/ERC20';
import InputAdded from '../../src/handlers/InputAdded';
import { Erc20Deposit, Token } from '../../src/model';
import { Application, Erc20Deposit, Token } from '../../src/model';
import { block, ctx, input, logs } from '../stubs/params';

vi.mock('../../src/abi/ERC20', async (importOriginal) => {
Expand Down Expand Up @@ -30,9 +30,13 @@ vi.mock('../../src/model/', async (importOriginal) => {
Input,
};
});

const ApplicationMock = vi.mocked(Application);

const tokenAddress = dataSlice(input.payload, 1, 21).toLowerCase(); // 20 bytes for address
const from = dataSlice(input.payload, 21, 41).toLowerCase(); // 20 bytes for address
const amount = getUint(dataSlice(input.payload, 41, 73)); // 32 bytes for uint256

describe('InputAdded', () => {
let inputAdded: InputAdded;
let erc20;
Expand Down Expand Up @@ -152,5 +156,23 @@ describe('InputAdded', () => {
await inputAdded.handle(logs[0], block, ctx);
expect(mockDepositStorage.size).toBe(1);
});

test('when creating a non-existing app it should also set the timestamp in seconds', async () => {
const expectedParams = vi.fn();

ApplicationMock.mockImplementationOnce((args) => {
expectedParams(args);
return new Application(args);
});

await inputAdded.handle(logs[0], block, ctx);

const timestamp = BigInt(logs[0].block.timestamp) / 1000n;

expect(expectedParams).toHaveBeenCalledWith({
id: '0x0be010fa7e70d74fa8b6729fe1ae268787298f54',
timestamp,
});
});
});
});
8 changes: 5 additions & 3 deletions tests/stubs/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CartesiDAppFactoryAddress,
ERC20PortalAddress,
} from '../../src/config';
import { Input } from '../../src/model';
vi.mock('@subsquid/logger', async (importOriginal) => {
const actualMods = await importOriginal;
const Logger = vi.fn();
Expand All @@ -31,21 +32,22 @@ export const input = {
id: '0x60a7048c3136293071605a4eaffef49923e981cc-0',
application: {
id: '0x60a7048c3136293071605a4eaffef49923e981cc',
timestamp: 1696281168n,
owner: null,
factory: null,
inputs: [],
},
index: 1,
msgSender: ERC20PortalAddress,
payload: payload,
timestamp: 1691384268 as unknown as bigint,
blockNumber: 4040941 as unknown as bigint,
timestamp: 1691384268n,
blockNumber: 4040941n,
blockHash:
'0xce6a0d404b4201b3bd4fb8309df0b6a64f6a5d7b71fa89bf2737d4574c58b32f',
erc20Deposit: null,
transactionHash:
'0x6a3d76983453c0f74188bd89e01576c35f9d9b02daecdd49f7171aeb2bd3dc78',
};
} satisfies Input;

export const logs = [
{
Expand Down
5 changes: 3 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { defineConfig } from 'vitest/config'
import { UserConfig } from 'vitest';
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
coverage: {
reporter: ['text', 'lcov'],
exclude: ['src/abi', 'src/model', 'tests/'],
},
} as UserConfig,
})
});

0 comments on commit 2b82064

Please sign in to comment.