Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add transfer entity and deposit amount fields in user [SF-962] #73

Merged
merged 3 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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!
}
52 changes: 52 additions & 0 deletions src/helper/initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Transaction,
User,
Liquidation,
Transfer,
} from '../../generated/schema';
import { getDailyVolumeEntityId } from '../utils/id-generation';
import { buildLendingMarketId } from '../utils/string';
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -211,3 +219,47 @@ 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);
} else {
log.error('Invalid currency: ', currencyString.split(','));
}
reuvab marked this conversation as resolved.
Show resolved Hide resolved
user.transferCount = user.transferCount.plus(BigInt.fromI32(1));
user.save();
};
32 changes: 29 additions & 3 deletions src/token-vault.ts
Original file line number Diff line number Diff line change
@@ -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
);
}
2 changes: 2 additions & 0 deletions subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lending-controller';
export * from './lending-market';
export * from './liquidation';
export * from './token-vault';
70 changes: 70 additions & 0 deletions test/mocks/token-vault.ts
Original file line number Diff line number Diff line change
@@ -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;
}
144 changes: 119 additions & 25 deletions test/token-vault.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Address, ethereum } from '@graphprotocol/graph-ts';
import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts';
import {
afterEach,
assert,
Expand All @@ -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<Deposit>(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<Deposit>(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()
);
});
});