From 94d6846b7a30d45abdbc9a38bdcb3d7d33c6b574 Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Tue, 12 Dec 2023 16:21:43 +1300 Subject: [PATCH] test: Add test cases for ERC-721 deposit. --- tests/handlers/InputAdded.test.ts | 141 ++++++++++++++++++++++++++++-- tests/stubs/params.ts | 49 ++++++++++- 2 files changed, 183 insertions(+), 7 deletions(-) diff --git a/tests/handlers/InputAdded.test.ts b/tests/handlers/InputAdded.test.ts index d9a1a9e..775e304 100644 --- a/tests/handlers/InputAdded.test.ts +++ b/tests/handlers/InputAdded.test.ts @@ -1,9 +1,18 @@ import { dataSlice, getUint } from 'ethers'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterEach } from 'node:test'; +import { MockedObject, beforeEach, describe, expect, test, vi } from 'vitest'; import { Contract } from '../../src/abi/ERC20'; +import { Contract as ERC721 } from '../../src/abi/ERC721'; import InputAdded from '../../src/handlers/InputAdded'; -import { Application, Erc20Deposit, Token } from '../../src/model'; -import { block, ctx, input, logs } from '../stubs/params'; +import { + Application, + Erc20Deposit, + Erc721Deposit, + Input, + NFT, + Token, +} from '../../src/model'; +import { block, ctx, input, logErc721Transfer, logs } from '../stubs/params'; vi.mock('../../src/abi/ERC20', async (importOriginal) => { const actualMods = await importOriginal; @@ -16,22 +25,39 @@ vi.mock('../../src/abi/ERC20', async (importOriginal) => { Contract, }; }); -vi.mock('../../src/model/', async (importOriginal) => { - const actualMods = await importOriginal; + +vi.mock('../../src/abi/ERC721', async (importOriginal) => { + const Contract = vi.fn(); + + Contract.prototype.name = vi.fn(); + Contract.prototype.symbol = vi.fn(); + + return { + Contract, + }; +}); + +vi.mock('../../src/model/', async () => { const Token = vi.fn(); const Erc20Deposit = vi.fn(); const Application = vi.fn(); const Input = vi.fn(); + const Erc721Deposit = vi.fn(); + const NFT = vi.fn(); return { - ...actualMods!, Application, Token, Erc20Deposit, + Erc721Deposit, Input, + NFT, }; }); const ApplicationMock = vi.mocked(Application); +const InputMock = vi.mocked(Input); +const NFTStub = vi.mocked(NFT); +const ERC721DepositStub = vi.mocked(Erc721Deposit); const tokenAddress = dataSlice(input.payload, 1, 21).toLowerCase(); // 20 bytes for address const from = dataSlice(input.payload, 21, 41).toLowerCase(); // 20 bytes for address @@ -40,10 +66,12 @@ const amount = getUint(dataSlice(input.payload, 41, 73)); // 32 bytes for uint25 describe('InputAdded', () => { let inputAdded: InputAdded; let erc20; + let erc721: MockedObject; const mockTokenStorage = new Map(); const mockDepositStorage = new Map(); const mockInputStorage = new Map(); const mockApplicationStorage = new Map(); + beforeEach(() => { inputAdded = new InputAdded( mockTokenStorage, @@ -52,6 +80,7 @@ describe('InputAdded', () => { mockInputStorage, ); erc20 = new Contract(ctx, block.header, tokenAddress); + erc721 = vi.mocked(new ERC721(ctx, block.header, tokenAddress)); mockTokenStorage.clear(); mockDepositStorage.clear(); mockApplicationStorage.clear(); @@ -110,23 +139,27 @@ describe('InputAdded', () => { expect(handlePayload).toBe(undefined); }); }); + describe('handle', async () => { test('call with the correct params', async () => { vi.spyOn(inputAdded, 'handle'); inputAdded.handle(logs[0], block, ctx); expect(inputAdded.handle).toBeCalledWith(logs[0], block, ctx); }); + test('wrong contract address', async () => { await inputAdded.handle(logs[1], block, ctx); expect(mockInputStorage.size).toBe(0); expect(mockApplicationStorage.size).toBe(0); expect(mockDepositStorage.size).toBe(0); }); + test('correct contract address', async () => { await inputAdded.handle(logs[0], block, ctx); expect(mockApplicationStorage.size).toBe(1); expect(mockInputStorage.size).toBe(1); }); + test('Erc20Deposit Stored', async () => { const name = 'SimpleERC20'; const symbol = 'SIM20'; @@ -174,5 +207,101 @@ describe('InputAdded', () => { timestamp, }); }); + + describe('ERC-721 deposits', () => { + const name = 'BrotherNFT'; + const symbol = 'BRUH'; + + beforeEach(() => { + erc721.name.mockResolvedValue(name); + erc721.symbol.mockResolvedValue(symbol); + + // Returning simple object as the Class type for assertion + InputMock.mockImplementationOnce((args) => { + return { ...args } as Input; + }); + + NFTStub.mockImplementationOnce((args) => { + return { ...args } as NFT; + }); + + ERC721DepositStub.mockImplementationOnce((args) => { + return { ...args } as Erc721Deposit; + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test('should store the token information', async () => { + await inputAdded.handle(logErc721Transfer, block, ctx); + + expect(mockTokenStorage.size).toBe(1); + const token = mockTokenStorage.values().next().value; + expect(token.name).toEqual(name); + expect(token.symbol).toEqual(symbol); + }); + + test('should store the deposit information', async () => { + await inputAdded.handle(logErc721Transfer, block, ctx); + + expect(mockDepositStorage.size).toBe(1); + const deposit = mockDepositStorage.values().next().value; + expect(deposit.id).toEqual( + '0x0be010fa7e70d74fa8b6729fe1ae268787298f54-1', + ); + expect(deposit.from).toEqual( + logErc721Transfer.transaction.from, + ); + expect(deposit.tokenIdx).toEqual(1n); + expect(deposit.token).toEqual({ + id: '0x7a3cc9c0408887a030a0354330c36a9cd681aa7e', + name, + symbol, + }); + }); + + test('should assign the erc721 deposit information correctly into the input', async () => { + await inputAdded.handle(logErc721Transfer, block, ctx); + + expect(mockInputStorage.size).toBe(1); + const input = mockInputStorage.values().next().value; + expect(input.erc721Deposit).toEqual({ + from: '0xa074683b5be015f053b5dceb064c41fc9d11b6e5', + id: '0x0be010fa7e70d74fa8b6729fe1ae268787298f54-1', + token: { + id: '0x7a3cc9c0408887a030a0354330c36a9cd681aa7e', + name, + symbol, + }, + tokenIdx: 1n, + }); + }); + + test('should handle the absence of name and symbol methods in the ERC-721 contract', async () => { + erc721.name.mockRejectedValue( + new Error('No name method implemented on contract'), + ); + erc721.symbol.mockRejectedValue( + new Error('No symbol method implemented on contract'), + ); + + await inputAdded.handle(logErc721Transfer, block, ctx); + + expect(mockInputStorage.size).toBe(1); + const input = mockInputStorage.values().next().value; + expect(input.erc721Deposit).toEqual({ + from: '0xa074683b5be015f053b5dceb064c41fc9d11b6e5', + id: '0x0be010fa7e70d74fa8b6729fe1ae268787298f54-1', + token: { + id: '0x7a3cc9c0408887a030a0354330c36a9cd681aa7e', + name: null, + symbol: null, + }, + tokenIdx: 1n, + }); + }); + }); }); }); diff --git a/tests/stubs/params.ts b/tests/stubs/params.ts index 4be3f29..710c44a 100644 --- a/tests/stubs/params.ts +++ b/tests/stubs/params.ts @@ -5,18 +5,22 @@ import { vi } from 'vitest'; import { CartesiDAppFactoryAddress, ERC20PortalAddress, + InputBoxAddress, } from '../../src/config'; import { Input } from '../../src/model'; + vi.mock('@subsquid/logger', async (importOriginal) => { const actualMods = await importOriginal; const Logger = vi.fn(); Logger.prototype.warn = vi.fn(); Logger.prototype.info = vi.fn(); + Logger.prototype.error = vi.fn(); return { ...actualMods!, Logger, }; }); + vi.mock('@subsquid/typeorm-store', async (importOriginal) => { const actualMods = await importOriginal; const Store = vi.fn(); @@ -28,6 +32,7 @@ vi.mock('@subsquid/typeorm-store', async (importOriginal) => { }); const payload = '0x494e5345525420494e544f20636572746966696572202056414c554553202827307866434432423566316346353562353643306632323464614439394331346234454530393237346433272c3130202c273078664344324235663163463535623536433066323234646144393943313462344545303932373464332729'; + export const input = { id: '0x60a7048c3136293071605a4eaffef49923e981cc-0', application: { @@ -44,17 +49,55 @@ export const input = { blockNumber: 4040941n, blockHash: '0xce6a0d404b4201b3bd4fb8309df0b6a64f6a5d7b71fa89bf2737d4574c58b32f', + erc721Deposit: null, erc20Deposit: null, transactionHash: '0x6a3d76983453c0f74188bd89e01576c35f9d9b02daecdd49f7171aeb2bd3dc78', } satisfies Input; +export const logErc721Transfer = { + id: '0004867730-000035-2c78f', + address: InputBoxAddress, + logIndex: 35, + transactionIndex: 24, + topics: [ + '0x6aaa400068bf4ca337265e2a1e1e841f66b8597fd5b452fdc52a44bed28a0784', + '0x0000000000000000000000000be010fa7e70d74fa8b6729fe1ae268787298f54', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ], + data: '0x000000000000000000000000237f8dd094c0e47f4236f12b4fa01d6dae89fb87000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c87a3cc9c0408887a030a0354330c36a9cd681aa7ea074683b5be015f053b5dceb064c41fc9d11b6e500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + block: { + id: '0004867730-2c78f', + height: 4867730, + hash: '0x2c78fb73f84f2755f65533652983578bcf89a68ad173e756bc631b4d0d242b53', + parentHash: + '0x1bb7d54bde1c3dda41c6cc5ab40ad04b855d1ce5dec4175571dd158d3134ec3e', + timestamp: 1702321200000, + }, + transaction: { + id: '0004867730-000024-2c78f', + transactionIndex: 24, + from: '0xa074683b5be015f053b5dceb064c41fc9d11b6e5', + to: '0x237f8dd094c0e47f4236f12b4fa01d6dae89fb87', + hash: '0x47c53eeddc2f927ef2a7a3dd9a95bfd70ecfda2c4efdf10a16c48ca98c86b881', + value: 0, + block: { + id: '0004867730-2c78f', + height: 4867730, + hash: '0x2c78fb73f84f2755f65533652983578bcf89a68ad173e756bc631b4d0d242b53', + parentHash: + '0x1bb7d54bde1c3dda41c6cc5ab40ad04b855d1ce5dec4175571dd158d3134ec3e', + timestamp: 1702321200000, + }, + }, +}; + export const logs = [ { id: '0004411683-000001-cae3a', logIndex: 1, transactionIndex: 1, - address: '0x59b22d57d4f067708ab0c00552767405926dc768', + address: InputBoxAddress, topics: [ '0x6aaa400068bf4ca337265e2a1e1e841f66b8597fd5b452fdc52a44bed28a0784', '0x0000000000000000000000000be010fa7e70d74fa8b6729fe1ae268787298f54', @@ -161,6 +204,7 @@ export const logs = [ }, }, ]; + export const block = { header: { id: '1234567890', @@ -174,15 +218,18 @@ export const block = { traces: [], stateDiffs: [], }; + export const token = { decimals: 18, id: '0x059c7507b973d1512768c06f32a813bc93d83eb2', name: 'SimpleERC20', symbol: 'SIM20', }; + const consoleSink = vi.fn(); const em = vi.fn(); const logger = new Logger(consoleSink, 'app'); + const store = new Store(em); export const ctx = { _chain: {} as unknown as Chain,