From d6e6a3adba41f064d29b13ad87ce10872aec2dfc Mon Sep 17 00:00:00 2001 From: NetFreak26 Date: Sat, 16 Dec 2023 22:55:40 +0530 Subject: [PATCH 1/3] feat: add transfer entity and deposit amount fields in user [SF-962] --- schema.graphql | 25 +++++++ src/helper/initializer.ts | 50 +++++++++++++ src/token-vault.ts | 32 ++++++++- subgraph.yaml | 2 + test/mocks/index.ts | 1 + test/mocks/token-vault.ts | 70 ++++++++++++++++++ test/token-vault.test.ts | 144 +++++++++++++++++++++++++++++++------- 7 files changed, 296 insertions(+), 28 deletions(-) create mode 100644 test/mocks/token-vault.ts diff --git a/schema.graphql b/schema.graphql index 122e604..4edbbcd 100644 --- a/schema.graphql +++ b/schema.graphql @@ -86,6 +86,14 @@ type User @entity { orders: [Order!]! @derivedFrom(field: "maker") liquidationCount: BigInt! liquidations: [Liquidation!]! @derivedFrom(field: "user") + transferCount: BigInt! + transfers: [Transfer!]! @derivedFrom(field: "user") + depositETH: BigInt! + depositUSDC: BigInt! + depositWBTC: BigInt! + depositWFIL: BigInt! + depositAUSDC: BigInt! + depositAXLFIL: BigInt! } type DailyVolume @entity { @@ -116,3 +124,20 @@ type Liquidation @entity { blockNumber: BigInt! txHash: Bytes! } + +enum TransferType { + Deposit + Withdraw +} + +type Transfer @entity { + id: ID! + user: User! + currency: Bytes! + amount: BigInt! + transferType: TransferType! + + timestamp: BigInt! + blockNumber: BigInt! + txHash: Bytes! +} diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index b5f4d83..7f8d1d8 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -13,6 +13,7 @@ import { Transaction, User, Liquidation, + Transfer, } from '../../generated/schema'; import { getDailyVolumeEntityId } from '../utils/id-generation'; import { buildLendingMarketId } from '../utils/string'; @@ -61,6 +62,13 @@ export const getOrInitUser = (address: Bytes, createdAt: BigInt): User => { user.transactionCount = BigInt.fromI32(0); user.orderCount = BigInt.fromI32(0); user.liquidationCount = BigInt.fromI32(0); + user.transferCount = BigInt.fromI32(0); + user.depositETH = BigInt.fromI32(0); + user.depositUSDC = BigInt.fromI32(0); + user.depositWBTC = BigInt.fromI32(0); + user.depositWFIL = BigInt.fromI32(0); + user.depositAUSDC = BigInt.fromI32(0); + user.depositAXLFIL = BigInt.fromI32(0); user.createdAt = createdAt; user.save(); @@ -211,3 +219,45 @@ export const initLiquidation = ( user.liquidationCount = user.liquidationCount.plus(BigInt.fromI32(1)); user.save(); }; + +export const initTransfer = ( + id: string, + userAddress: Address, + currency: Bytes, + amount: BigInt, + transferType: string, + timestamp: BigInt, + blockNumber: BigInt, + txHash: Bytes +): void => { + const transfer = new Transfer(id); + + const user = getOrInitUser(userAddress, timestamp); + + transfer.user = user.id; + transfer.currency = currency; + transfer.amount = amount; + transfer.transferType = transferType; + transfer.timestamp = timestamp; + transfer.blockNumber = blockNumber; + transfer.txHash = txHash; + transfer.save(); + + const currencyString = currency.toString(); + + if (currencyString == 'ETH') { + user.depositETH = user.depositETH.plus(amount); + } else if (currencyString == 'USDC') { + user.depositUSDC = user.depositUSDC.plus(amount); + } else if (currencyString == 'WBTC') { + user.depositWBTC = user.depositWBTC.plus(amount); + } else if (currencyString == 'WFIL') { + user.depositWFIL = user.depositWFIL.plus(amount); + } else if (currencyString == 'aUSDC') { + user.depositAUSDC = user.depositAUSDC.plus(amount); + } else if (currencyString == 'axlFIL') { + user.depositAXLFIL = user.depositAXLFIL.plus(amount); + } + user.transferCount = user.transferCount.plus(BigInt.fromI32(1)); + user.save(); +}; diff --git a/src/token-vault.ts b/src/token-vault.ts index b0eda7d..2222f0e 100644 --- a/src/token-vault.ts +++ b/src/token-vault.ts @@ -1,6 +1,32 @@ -import { Deposit } from '../generated/TokenVault/TokenVault'; -import { getOrInitUser } from './helper/initializer'; +import { Deposit, Withdraw } from '../generated/TokenVault/TokenVault'; +import { initTransfer } from './helper/initializer'; export function handleDeposit(event: Deposit): void { - getOrInitUser(event.params.user, event.block.timestamp); + const id = + event.transaction.hash.toHexString() + ':' + event.logIndex.toString(); + initTransfer( + id, + event.params.user, + event.params.ccy, + event.params.amount, + 'Deposit', + event.block.timestamp, + event.block.number, + event.transaction.hash + ); +} + +export function handleWithdraw(event: Withdraw): void { + const id = + event.transaction.hash.toHexString() + ':' + event.logIndex.toString(); + initTransfer( + id, + event.params.user, + event.params.ccy, + event.params.amount, + 'Withdraw', + event.block.timestamp, + event.block.number, + event.transaction.hash + ); } diff --git a/subgraph.yaml b/subgraph.yaml index bd331be..3902a55 100644 --- a/subgraph.yaml +++ b/subgraph.yaml @@ -43,6 +43,8 @@ dataSources: eventHandlers: - event: Deposit(indexed address,bytes32,uint256) handler: handleDeposit + - event: Withdraw(indexed address,bytes32,uint256) + handler: handleWithdraw file: ./src/token-vault.ts - kind: ethereum name: FundManagementLogic diff --git a/test/mocks/index.ts b/test/mocks/index.ts index fed7ddf..3eb496c 100644 --- a/test/mocks/index.ts +++ b/test/mocks/index.ts @@ -1,3 +1,4 @@ export * from './lending-controller'; export * from './lending-market'; export * from './liquidation'; +export * from './token-vault'; diff --git a/test/mocks/token-vault.ts b/test/mocks/token-vault.ts new file mode 100644 index 0000000..85608e5 --- /dev/null +++ b/test/mocks/token-vault.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts'; +import { newMockEvent } from 'matchstick-as/assembly/index'; +import { Deposit, Withdraw } from '../../generated/TokenVault/TokenVault'; + +export function createDepositEvent( + user: Address, + ccy: Bytes, + amount: BigInt +): Deposit { + const mockEvent = newMockEvent(); + const event = new Deposit( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + mockEvent.parameters, + mockEvent.receipt + ); + + event.parameters = new Array(); + event.parameters.push( + new ethereum.EventParam('user', ethereum.Value.fromAddress(user)) + ); + event.parameters.push( + new ethereum.EventParam('ccy', ethereum.Value.fromBytes(ccy)) + ); + event.parameters.push( + new ethereum.EventParam( + 'amount', + ethereum.Value.fromUnsignedBigInt(amount) + ) + ); + return event; +} + +export function createWithdrawEvent( + user: Address, + ccy: Bytes, + amount: BigInt +): Withdraw { + const mockEvent = newMockEvent(); + const event = new Withdraw( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + mockEvent.parameters, + mockEvent.receipt + ); + + event.parameters = new Array(); + event.parameters.push( + new ethereum.EventParam('user', ethereum.Value.fromAddress(user)) + ); + event.parameters.push( + new ethereum.EventParam('ccy', ethereum.Value.fromBytes(ccy)) + ); + event.parameters.push( + new ethereum.EventParam( + 'amount', + ethereum.Value.fromUnsignedBigInt(amount) + ) + ); + return event; +} diff --git a/test/token-vault.test.ts b/test/token-vault.test.ts index 9256538..706a9b6 100644 --- a/test/token-vault.test.ts +++ b/test/token-vault.test.ts @@ -1,4 +1,4 @@ -import { Address, ethereum } from '@graphprotocol/graph-ts'; +import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'; import { afterEach, assert, @@ -8,42 +8,136 @@ import { test, } from 'matchstick-as/assembly/index'; import { Deposit } from '../generated/TokenVault/TokenVault'; -import { handleDeposit } from '../src/token-vault'; -import { toBytes20 } from '../src/utils/string'; +import { handleDeposit, handleWithdraw } from '../src/token-vault'; +import { toBytes32, toBytes20 } from '../src/utils/string'; +import { ALICE, BOB } from './utils/createEntities'; +import { createDepositEvent, createWithdrawEvent } from './mocks'; -describe('User Entity', () => { +const ccy = toBytes32('ETH'); + +describe('Deposit & Withdraw', () => { afterEach(() => { clearStore(); }); - test('Should create an user when the Deposit Event is raised', () => { - const userWallet = '0x0000000000000000000000000000000000000000'; - const mockEvent = changetype(newMockEvent()); - mockEvent.parameters.push( - new ethereum.EventParam( - 'user', - ethereum.Value.fromAddress(Address.fromString(userWallet)) - ) - ); - handleDeposit(mockEvent); - assert.fieldEquals('User', userWallet, 'id', userWallet); + test('Should create an user and a transfer when the Deposit Event is raised', () => { + const amount = BigInt.fromI32(10000000); + const event = createDepositEvent(ALICE, ccy, amount); + + const id = + event.transaction.hash.toHexString() + + ':' + + event.logIndex.toString(); + + handleDeposit(event); + + assert.entityCount('User', 1); assert.fieldEquals('Protocol', 'ethereum', 'totalUsers', '1'); + assert.fieldEquals('User', ALICE.toHexString(), 'transferCount', '1'); + assert.fieldEquals( + 'User', + ALICE.toHexString(), + 'depositETH', + amount.toString() + ); + + assert.entityCount('Transfer', 1); + assert.fieldEquals('Transfer', id, 'user', ALICE.toHexString()); + assert.fieldEquals('Transfer', id, 'currency', ccy.toHexString()); + assert.fieldEquals('Transfer', id, 'amount', amount.toString()); + assert.fieldEquals('Transfer', id, 'transferType', 'Deposit'); + + assert.stringEquals(ccy.toString(), 'ETH'); }); - test('Should increment the totalUsers when a new user is created', () => { + test('Should increment the totalUsers when a new user deposits', () => { + const amount = BigInt.fromI32(10000000); for (let i = 0; i < 100; i++) { - const mockEvent = changetype(newMockEvent()); - mockEvent.parameters.push( - new ethereum.EventParam( - 'user', - ethereum.Value.fromAddress( - Address.fromBytes(toBytes20(i.toString())) - ) - ) + const event = createDepositEvent( + Address.fromBytes(toBytes20(i.toString())), + ccy, + amount ); - handleDeposit(mockEvent); + event.logIndex = BigInt.fromI32(i); + handleDeposit(event); } assert.entityCount('User', 100); assert.fieldEquals('Protocol', 'ethereum', 'totalUsers', '100'); + assert.entityCount('Transfer', 100); + }); + + test('Should increment the transferCount when same user deposits', () => { + const amount = BigInt.fromI32(10000000); + for (let i = 0; i < 10; i++) { + const event = createDepositEvent(ALICE, ccy, amount); + event.logIndex = BigInt.fromI32(i); + handleDeposit(event); + } + assert.entityCount('User', 1); + assert.fieldEquals('Protocol', 'ethereum', 'totalUsers', '1'); + assert.fieldEquals('User', ALICE.toHexString(), 'transferCount', '10'); + assert.entityCount('Transfer', 10); + }); + + test('Should increment the transferCount and add a transfer when user withdraws', () => { + const amount = BigInt.fromI32(10000000); + const depositEvent = createDepositEvent(ALICE, ccy, amount); + handleDeposit(depositEvent); + + const withdrawEvent = createWithdrawEvent(ALICE, ccy, amount); + withdrawEvent.logIndex = BigInt.fromI32(2); + handleWithdraw(withdrawEvent); + + const id = + withdrawEvent.transaction.hash.toHexString() + + ':' + + withdrawEvent.logIndex.toString(); + + assert.entityCount('User', 1); + assert.fieldEquals('Protocol', 'ethereum', 'totalUsers', '1'); + assert.fieldEquals('User', ALICE.toHexString(), 'transferCount', '2'); + + assert.entityCount('Transfer', 2); + assert.fieldEquals('Transfer', id, 'user', ALICE.toHexString()); + assert.fieldEquals('Transfer', id, 'currency', ccy.toHexString()); + assert.fieldEquals('Transfer', id, 'amount', amount.toString()); + assert.fieldEquals('Transfer', id, 'transferType', 'Withdraw'); + }); + + test('Should increment the deposit amount for each currency', () => { + const amount1 = BigInt.fromI32(10000000); + const amount2 = BigInt.fromI32(20000000); + const amount3 = BigInt.fromI32(50000000); + const wfil = toBytes32('WFIL'); + const usdc = toBytes32('USDC'); + const axlFIL = toBytes32('axlFIL'); + + const event1 = createDepositEvent(ALICE, wfil, amount1); + const event2 = createDepositEvent(ALICE, usdc, amount2); + const event3 = createDepositEvent(ALICE, axlFIL, amount3); + + handleDeposit(event1); + handleDeposit(event2); + handleDeposit(event3); + + assert.fieldEquals('User', ALICE.toHexString(), 'depositETH', '0'); + assert.fieldEquals( + 'User', + ALICE.toHexString(), + 'depositWFIL', + amount1.toString() + ); + assert.fieldEquals( + 'User', + ALICE.toHexString(), + 'depositUSDC', + amount2.toString() + ); + assert.fieldEquals( + 'User', + ALICE.toHexString(), + 'depositAXLFIL', + amount3.toString() + ); }); }); From d4f863afd9b449c134982aabbe368ff29ba2d156 Mon Sep 17 00:00:00 2001 From: NetFreak26 Date: Sun, 17 Dec 2023 01:17:33 +0530 Subject: [PATCH 2/3] fix: add an error if currency is not recognized [SF-962] --- src/helper/initializer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index 7f8d1d8..b108af2 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -257,6 +257,8 @@ export const initTransfer = ( user.depositAUSDC = user.depositAUSDC.plus(amount); } else if (currencyString == 'axlFIL') { user.depositAXLFIL = user.depositAXLFIL.plus(amount); + } else { + console.error('Invalid currency: ' + currencyString); } user.transferCount = user.transferCount.plus(BigInt.fromI32(1)); user.save(); From 3dba124a37ce5802ec7afe3ebd26442dddf09405 Mon Sep 17 00:00:00 2001 From: NetFreak26 Date: Sun, 17 Dec 2023 01:35:10 +0530 Subject: [PATCH 3/3] fix: fix test error [SF-962] --- src/helper/initializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index b108af2..831a9df 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -258,7 +258,7 @@ export const initTransfer = ( } else if (currencyString == 'axlFIL') { user.depositAXLFIL = user.depositAXLFIL.plus(amount); } else { - console.error('Invalid currency: ' + currencyString); + log.error('Invalid currency: ', currencyString.split(',')); } user.transferCount = user.transferCount.plus(BigInt.fromI32(1)); user.save();