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

Fix/erc20 add total supply #855

Closed
wants to merge 5 commits into from
Closed
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
59 changes: 47 additions & 12 deletions src/services/evm/erc20.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import {
import { AccountBalance } from '../../models/account_balance';
import { Erc20Activity } from '../../models/erc20_activity';
import { Erc20Contract } from '../../models/erc20_contract';
import { BULL_JOB_NAME, SERVICE as EVM_SERVICE, SERVICE } from './constant';
import {
BULL_JOB_NAME,
SERVICE as EVM_SERVICE,
SERVICE,
ZERO_ADDRESS,
} from './constant';
import { ERC20_EVENT_TOPIC0, Erc20Handler } from './erc20_handler';
import { convertEthAddressToBech32Address } from './utils';

Expand Down Expand Up @@ -117,16 +122,6 @@ export default class Erc20Service extends BullableService {
);
let erc20CosmosEvents: Event[] = [];
if (config.evmOnly === false) {
if (!this.erc20ModuleAccount) {
const lcdClient = await getLcdClient();
const erc20Account: QueryModuleAccountByNameResponseSDKType =
await lcdClient.provider.cosmos.auth.v1beta1.moduleAccountByName({
name: 'erc20',
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.erc20ModuleAccount = erc20Account.account.base_account.address;
}
erc20CosmosEvents = await Event.query()
.where('block_height', '>', startBlock)
.andWhere('block_height', '<=', endBlock)
Expand Down Expand Up @@ -253,8 +248,26 @@ export default class Erc20Service extends BullableService {
),
(o) => `${o.account_id}_${o.denom}`
);
const erc20Contracts = _.keyBy(
await Erc20Contract.query()
.transacting(trx)
.whereIn(
'address',
erc20Activities.map((e) => e.erc20_contract_address)
),
'address'
);
const factoryAccounts = [ZERO_ADDRESS];
if (this.erc20ModuleAccount) {
factoryAccounts.push(this.erc20ModuleAccount);
}
// construct cw721 handler object
const erc20Handler = new Erc20Handler(accountBalances, erc20Activities);
const erc20Handler = new Erc20Handler(
accountBalances,
erc20Contracts,
erc20Activities,
factoryAccounts
);
erc20Handler.process();
const updatedAccountBalances = Object.values(
erc20Handler.accountBalances
Expand All @@ -266,6 +279,16 @@ export default class Erc20Service extends BullableService {
.onConflict(['account_id', 'denom'])
.merge();
}
const updatedErc20Contracts = Object.values(
erc20Handler.erc20Contracts
);
if (updatedErc20Contracts.length > 0) {
await Erc20Contract.query()
.transacting(trx)
.insert(updatedErc20Contracts)
.onConflict(['id'])
.merge();
}
}
updateBlockCheckpoint.height = endBlock;
await BlockCheckpoint.query()
Expand Down Expand Up @@ -442,6 +465,18 @@ export default class Erc20Service extends BullableService {
public async _start(): Promise<void> {
this.viemClient = getViemClient();
if (NODE_ENV !== 'test') {
if (config.evmOnly === false) {
if (!this.erc20ModuleAccount) {
const lcdClient = await getLcdClient();
const erc20Account: QueryModuleAccountByNameResponseSDKType =
await lcdClient.provider.cosmos.auth.v1beta1.moduleAccountByName({
name: 'erc20',
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.erc20ModuleAccount = erc20Account.account.base_account.address;
}
}
await this.createJob(
BULL_JOB_NAME.HANDLE_ERC20_CONTRACT,
BULL_JOB_NAME.HANDLE_ERC20_CONTRACT,
Expand Down
93 changes: 69 additions & 24 deletions src/services/evm/erc20_handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Dictionary } from 'lodash';
import Moleculer from 'moleculer';
import { decodeAbiParameters, keccak256, toHex } from 'viem';
import { Erc20Activity, Event, EventAttribute, EvmEvent } from '../../models';
import {
Erc20Activity,
Erc20Contract,
Event,
EventAttribute,
EvmEvent,
} from '../../models';
import { AccountBalance } from '../../models/account_balance';
import { ZERO_ADDRESS } from './constant';
import { convertBech32AddressToEthAddress } from './utils';
Expand Down Expand Up @@ -54,12 +60,20 @@ export class Erc20Handler {

erc20Activities: Erc20Activity[];

erc20Contracts: Dictionary<Erc20Contract>;

factoryAccounts: string[];

constructor(
accountBalances: Dictionary<AccountBalance>,
erc20Activities: Erc20Activity[]
erc20Contracts: Dictionary<Erc20Contract>,
erc20Activities: Erc20Activity[],
factoryAccounts: string[]
) {
this.accountBalances = accountBalances;
this.erc20Activities = erc20Activities;
this.erc20Contracts = erc20Contracts;
this.factoryAccounts = factoryAccounts;
}

process() {
Expand All @@ -77,8 +91,8 @@ export class Erc20Handler {
}

handlerErc20Transfer(erc20Activity: Erc20Activity) {
// update from account balance if from != ZERO_ADDRESS
if (erc20Activity.from !== ZERO_ADDRESS) {
// update from account balance if from != factory account
if (!this.factoryAccounts.includes(erc20Activity.from)) {
const fromAccountId = erc20Activity.from_account_id;
const key = `${fromAccountId}_${erc20Activity.erc20_contract_address}`;
const fromAccountBalance = this.accountBalances[key];
Expand All @@ -99,27 +113,58 @@ export class Erc20Handler {
type: AccountBalance.TYPE.ERC20_TOKEN,
});
}
} else {
const erc20Contract: Erc20Contract =
this.erc20Contracts[erc20Activity.erc20_contract_address];
if (
erc20Contract &&
erc20Contract.last_updated_height <= erc20Activity.height
) {
// update total supply
erc20Contract.total_supply = (
BigInt(erc20Contract.total_supply || 0) + BigInt(erc20Activity.amount)
).toString();
// update last updated height
erc20Contract.last_updated_height = erc20Activity.height;
}
}
// update to account balance
const toAccountId = erc20Activity.to_account_id;
const key = `${toAccountId}_${erc20Activity.erc20_contract_address}`;
const toAccountBalance = this.accountBalances[key];
if (
!toAccountBalance ||
toAccountBalance.last_updated_height <= erc20Activity.height
) {
// calculate new balance: increase balance of to account
const amount = (
BigInt(toAccountBalance?.amount || 0) + BigInt(erc20Activity.amount)
).toString();
// update object accountBalance
this.accountBalances[key] = AccountBalance.fromJson({
denom: erc20Activity.erc20_contract_address,
amount,
last_updated_height: erc20Activity.height,
account_id: toAccountId,
type: AccountBalance.TYPE.ERC20_TOKEN,
});
// update from account balance if to != factory account
if (!this.factoryAccounts.includes(erc20Activity.to)) {
// update to account balance
const toAccountId = erc20Activity.to_account_id;
const key = `${toAccountId}_${erc20Activity.erc20_contract_address}`;
const toAccountBalance = this.accountBalances[key];
if (
!toAccountBalance ||
toAccountBalance.last_updated_height <= erc20Activity.height
) {
// calculate new balance: increase balance of to account
const amount = (
BigInt(toAccountBalance?.amount || 0) + BigInt(erc20Activity.amount)
).toString();
// update object accountBalance
this.accountBalances[key] = AccountBalance.fromJson({
denom: erc20Activity.erc20_contract_address,
amount,
last_updated_height: erc20Activity.height,
account_id: toAccountId,
type: AccountBalance.TYPE.ERC20_TOKEN,
});
}
} else {
const erc20Contract: Erc20Contract =
this.erc20Contracts[erc20Activity.erc20_contract_address];
if (
erc20Contract &&
erc20Contract.last_updated_height <= erc20Activity.height
) {
// update total supply
erc20Contract.total_supply = (
BigInt(erc20Contract.total_supply || 0) - BigInt(erc20Activity.amount)
).toString();
// update last updated height
erc20Contract.last_updated_height = erc20Activity.height;
}
}
}

Expand Down
130 changes: 126 additions & 4 deletions test/unit/services/evm/erc20_handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core';
import { ServiceBroker } from 'moleculer';
import { decodeAbiParameters, toHex } from 'viem';
import { Dictionary } from 'lodash';
import { Erc20Activity, EvmEvent } from '../../../../src/models';
import { Erc20Activity, Erc20Contract, EvmEvent } from '../../../../src/models';
import {
ABI_APPROVAL_PARAMS,
ABI_TRANSFER_PARAMS,
Expand Down Expand Up @@ -176,7 +176,25 @@ export default class Erc20HandlerTest {
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(accountBalances, []);
const totalSupply = '123654';
const erc20Contracts = {
[erc20Activity.erc20_contract_address]: Erc20Contract.fromJson({
evm_smart_contract_id: 1,
total_supply: totalSupply,
symbol: 'ALPHA',
address: erc20Activity.erc20_contract_address,
decimal: '20',
name: 'Alpha Grand Wolf',
track: true,
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(
accountBalances,
erc20Contracts,
[],
[ZERO_ADDRESS]
);
erc20Handler.handlerErc20Transfer(erc20Activity);
expect(erc20Handler.accountBalances[fromKey]).toMatchObject({
denom: erc20Activity.erc20_contract_address,
Expand All @@ -186,6 +204,9 @@ export default class Erc20HandlerTest {
denom: erc20Activity.erc20_contract_address,
amount: (BigInt(erc20Activity.amount) + BigInt(toAmount)).toString(),
});
expect(
erc20Contracts[erc20Activity.erc20_contract_address].total_supply
).toEqual(totalSupply);
}

@Test('test handlerErc20Transfer when from is zero')
Expand Down Expand Up @@ -217,13 +238,93 @@ export default class Erc20HandlerTest {
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(accountBalances, []);
const totalSupply = '123654';
const erc20Contracts = {
[erc20Activity.erc20_contract_address]: Erc20Contract.fromJson({
evm_smart_contract_id: 1,
total_supply: totalSupply,
symbol: 'ALPHA',
address: erc20Activity.erc20_contract_address,
decimal: '20',
name: 'Alpha Grand Wolf',
track: true,
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(
accountBalances,
erc20Contracts,
[],
[ZERO_ADDRESS]
);
erc20Handler.handlerErc20Transfer(erc20Activity);
expect(erc20Handler.accountBalances[fromKey]).toBeUndefined();
expect(erc20Handler.accountBalances[toKey]).toMatchObject({
denom: erc20Activity.erc20_contract_address,
amount: (BigInt(erc20Activity.amount) + BigInt(toAmount)).toString(),
});
expect(
erc20Contracts[erc20Activity.erc20_contract_address].total_supply
).toEqual((BigInt(totalSupply) + BigInt(erc20Activity.amount)).toString());
}

@Test('test handlerErc20Transfer when to is zero')
async testHandlerErc20TransferWhenToIsZero() {
const balance = '242423234';
const erc20Activity = Erc20Activity.fromJson({
evm_event_id: 1,
sender: '0x7c756Cba10Ff2C65016494E8BA37C12a108572b5',
action: ERC20_ACTION.TRANSFER,
erc20_contract_address: '0x98605ae21dd3be686337a6d7a8f156d0d8baee92',
amount: '12345222',
from: '0xD83E708D7FE0E769Af80d990f9241458734808Ac',
to: ZERO_ADDRESS,
height: 10000,
tx_hash:
'0xb97228e533e3af1323d873c9c3e4c0a9b85d95ecd8e98110c8890c9453d2f077',
evm_tx_id: 1,
from_account_id: 123,
to_account_id: 234,
});
const [fromKey, toKey] = [
`${erc20Activity.from_account_id}_${erc20Activity.erc20_contract_address}`,
`${erc20Activity.to_account_id}_${erc20Activity.erc20_contract_address}`,
];
const accountBalances: Dictionary<AccountBalance> = {
[fromKey]: AccountBalance.fromJson({
denom: erc20Activity.erc20_contract_address,
amount: balance,
last_updated_height: 1,
}),
};
const totalSupply = '123654';
const erc20Contracts = {
[erc20Activity.erc20_contract_address]: Erc20Contract.fromJson({
evm_smart_contract_id: 1,
total_supply: totalSupply,
symbol: 'ALPHA',
address: erc20Activity.erc20_contract_address,
decimal: '20',
name: 'Alpha Grand Wolf',
track: true,
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(
accountBalances,
erc20Contracts,
[],
[ZERO_ADDRESS]
);
erc20Handler.handlerErc20Transfer(erc20Activity);
expect(erc20Handler.accountBalances[fromKey]).toMatchObject({
denom: erc20Activity.erc20_contract_address,
amount: (BigInt(balance) - BigInt(erc20Activity.amount)).toString(),
});
expect(erc20Handler.accountBalances[toKey]).toBeUndefined();
expect(
erc20Contracts[erc20Activity.erc20_contract_address].total_supply
).toEqual((BigInt(totalSupply) - BigInt(erc20Activity.amount)).toString());
}

@Test('test handlerErc20Transfer when last_updated_height not suitable')
Expand Down Expand Up @@ -261,7 +362,25 @@ export default class Erc20HandlerTest {
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(accountBalances, []);
const totalSupply = '123654';
const erc20Contracts = {
[erc20Activity.erc20_contract_address]: Erc20Contract.fromJson({
evm_smart_contract_id: 1,
total_supply: totalSupply,
symbol: 'ALPHA',
address: erc20Activity.erc20_contract_address,
decimal: '20',
name: 'Alpha Grand Wolf',
track: true,
last_updated_height: 1,
}),
};
const erc20Handler = new Erc20Handler(
accountBalances,
erc20Contracts,
[],
[ZERO_ADDRESS]
);
erc20Handler.handlerErc20Transfer(erc20Activity);
expect(erc20Handler.accountBalances[fromKey]).toMatchObject({
denom: erc20Activity.erc20_contract_address,
Expand All @@ -271,5 +390,8 @@ export default class Erc20HandlerTest {
denom: erc20Activity.erc20_contract_address,
amount: (BigInt(erc20Activity.amount) + BigInt(toAmount)).toString(),
});
expect(
erc20Contracts[erc20Activity.erc20_contract_address].total_supply
).toEqual(totalSupply);
}
}
Loading