From 80622e4f529af3ea94a70412e75bc31a15decf14 Mon Sep 17 00:00:00 2001 From: Sotatek-ThinhNguyen2 Date: Wed, 11 Sep 2024 09:53:22 +0700 Subject: [PATCH 1/6] feat: multi signature table --- ...1725867149224-add-multi-signature-table.ts | 63 +++++++++++++++++++ .../repositories/event-log.repository.ts | 36 +++++++++-- .../multi-signature.repository.ts | 14 +++++ .../crawler/entities/event-logs.entity.ts | 7 ++- .../entities/multi-signature.entity.ts | 34 ++++++++++ 5 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/database/migrations/1725867149224-add-multi-signature-table.ts create mode 100644 src/database/repositories/multi-signature.repository.ts create mode 100644 src/modules/crawler/entities/multi-signature.entity.ts diff --git a/src/database/migrations/1725867149224-add-multi-signature-table.ts b/src/database/migrations/1725867149224-add-multi-signature-table.ts new file mode 100644 index 0000000..84058cb --- /dev/null +++ b/src/database/migrations/1725867149224-add-multi-signature-table.ts @@ -0,0 +1,63 @@ +import { MigrationInterface, QueryRunner, Table } from 'typeorm'; + +import { ENetworkName } from '@constants/blockchain.constant'; + +export class AddMultiSignatureTable1725867149224 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + return queryRunner.createTable( + new Table({ + name: 'multi_signature', + columns: [ + { + name: 'id', + type: 'int', + isPrimary: true, + isGenerated: true, + }, + { + name: 'validator', + type: 'varchar', + length: '255', + isNullable: true, + }, + { + name: 'tx_id', + type: 'bigint', + isNullable: true, + }, + { + name: 'signature', + type: 'text', + isNullable: true, + }, + { + name: 'chain', + type: 'varchar', + length: '255', + enum: Object.values(ENetworkName), + isNullable: true, + }, + { + name: 'created_at', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + }, + { + name: 'updated_at', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + }, + { + name: 'deleted_at', + type: 'timestamp', + isNullable: true, + }, + ], + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + return queryRunner.dropTable('multi_signature'); + } +} diff --git a/src/database/repositories/event-log.repository.ts b/src/database/repositories/event-log.repository.ts index cce9a50..7959fe8 100644 --- a/src/database/repositories/event-log.repository.ts +++ b/src/database/repositories/event-log.repository.ts @@ -14,15 +14,27 @@ import { endOfDayUnix, startOfDayUnix } from '@shared/utils/time'; export class EventLogRepository extends BaseRepository { protected alias: ETableName = ETableName.EVENT_LOGS; - public async getEventLockWithNetwork(network: ENetworkName): Promise { - return this.createQueryBuilder(`${this.alias}`) - .where(`${this.alias}.networkReceived = :network`, { network }) - .andWhere(`${this.alias}.status IN (:...status)`, { status: [EEventStatus.WAITING, EEventStatus.FAILED] }) + public async getEventLockWithNetwork(network: ENetworkName, threshold?: number): Promise { + const qb = this.createQueryBuilder(`${this.alias}`); + qb.innerJoinAndSelect(`${this.alias}.validator`, 'signature'); + + qb.where(`${this.alias}.networkReceived = :network`, { network }); + + if (threshold) { + qb.andWhere( + `(SELECT COUNT(${ETableName.MULTI_SIGNATURE}.id) FROM ${ETableName.MULTI_SIGNATURE} WHERE ${ETableName.MULTI_SIGNATURE}.tx_id = ${this.alias}.id) >= :threshold`, + { + threshold, + }, + ); + } + qb.andWhere(`${this.alias}.status IN (:...status)`, { status: [EEventStatus.WAITING, EEventStatus.FAILED] }) .andWhere(`${this.alias}.retry < :retryNumber`, { retryNumber: 3 }) .orderBy(`${this.alias}.status`, EDirection.DESC) .addOrderBy(`${this.alias}.id`, EDirection.ASC) - .addOrderBy(`${this.alias}.retry`, EDirection.ASC) - .getOne(); + .addOrderBy(`${this.alias}.retry`, EDirection.ASC); + + return qb.getOne(); } public async updateStatusAndRetryEvenLog( @@ -112,4 +124,16 @@ export class EventLogRepository extends BaseRepository { .groupBy(`${this.alias}.sender_address`); return qb.getRawOne(); } + + async getValidatorPendingSignature(validator: string, network: ENetworkName) { + const qb = this.createQueryBuilder(`${this.alias}`); + qb.leftJoinAndSelect(`${this.alias}.validator`, 'signature'); + qb.where(`${this.alias}.networkReceived = :network`, { network }); + qb.andWhere(`(signature.validator IS NULL OR signature.validator != :validator)`, { validator }) + .andWhere(`${this.alias}.status = :status`, { status: EEventStatus.WAITING }) + .addOrderBy(`${this.alias}.id`, EDirection.ASC) + .addOrderBy(`${this.alias}.retry`, EDirection.ASC); + + return qb.getOne(); + } } diff --git a/src/database/repositories/multi-signature.repository.ts b/src/database/repositories/multi-signature.repository.ts new file mode 100644 index 0000000..ec75805 --- /dev/null +++ b/src/database/repositories/multi-signature.repository.ts @@ -0,0 +1,14 @@ +import { EntityRepository } from 'nestjs-typeorm-custom-repository'; + +import { EDirection } from '@constants/api.constant'; +import { EEventStatus, ENetworkName } from '@constants/blockchain.constant'; +import { ETableName } from '@constants/entity.constant'; + +import { BaseRepository } from '@core/base-repository'; + +import { MultiSignature } from '@modules/crawler/entities/multi-signature.entity'; + +@EntityRepository(MultiSignature) +export class MultiSignatureRepository extends BaseRepository { + protected alias: ETableName = ETableName.MULTI_SIGNATURE; +} diff --git a/src/modules/crawler/entities/event-logs.entity.ts b/src/modules/crawler/entities/event-logs.entity.ts index 5fc3058..b7fd3c1 100644 --- a/src/modules/crawler/entities/event-logs.entity.ts +++ b/src/modules/crawler/entities/event-logs.entity.ts @@ -1,4 +1,4 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, JoinColumn, OneToMany } from 'typeorm'; import { EEventName, ENetworkName } from '@constants/blockchain.constant'; import { ETableName } from '@constants/entity.constant'; @@ -6,6 +6,7 @@ import { ETableName } from '@constants/entity.constant'; import { BaseEntityIncludeTime } from '@core/base.entity'; import { EEventStatus } from '../../../constants/blockchain.constant'; +import { MultiSignature } from './multi-signature.entity'; @Entity(ETableName.EVENT_LOGS) export class EventLog extends BaseEntityIncludeTime { @@ -75,6 +76,10 @@ export class EventLog extends BaseEntityIncludeTime { @Column({ name: 'retry', type: 'int', nullable: false, default: 0 }) retry: number; + @OneToMany(() => MultiSignature, multiSignature => multiSignature.transaction) + @JoinColumn({ name: 'id' }) + validator: MultiSignature[]; + constructor(value: Partial) { super(); Object.assign(this, value); diff --git a/src/modules/crawler/entities/multi-signature.entity.ts b/src/modules/crawler/entities/multi-signature.entity.ts new file mode 100644 index 0000000..2b069b7 --- /dev/null +++ b/src/modules/crawler/entities/multi-signature.entity.ts @@ -0,0 +1,34 @@ +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; + +import { ENetworkName } from '@constants/blockchain.constant'; +import { ETableName } from '@constants/entity.constant'; + +import { BaseEntityIncludeTime } from '@core/base.entity'; + +import { EventLog } from './event-logs.entity'; + +@Entity(ETableName.MULTI_SIGNATURE) +export class MultiSignature extends BaseEntityIncludeTime { + @Column({ name: 'validator', type: 'varchar', nullable: true }) + validator: string; + + @Column({ name: 'tx_id', type: 'bigint', nullable: true }) + txId: number; + + @Column({ name: 'signature', type: 'text', nullable: true }) + signature: string; + + @Column({ name: 'chain', type: 'varchar', enum: ENetworkName, nullable: true }) + chain: ENetworkName; + + @ManyToOne(() => EventLog, eventLog => eventLog.validator, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'tx_id' }) + transaction: EventLog; + + constructor(value: Partial) { + super(); + Object.assign(this, value); + } +} From 67069ede7b4a3d3c581b51b5e1c51018e08aaac2 Mon Sep 17 00:00:00 2001 From: Sotatek-ThinhNguyen2 Date: Wed, 11 Sep 2024 09:56:08 +0700 Subject: [PATCH 2/6] feat: add table enum --- src/constants/entity.constant.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/entity.constant.ts b/src/constants/entity.constant.ts index 3a97135..2d9d544 100644 --- a/src/constants/entity.constant.ts +++ b/src/constants/entity.constant.ts @@ -5,4 +5,5 @@ export enum ETableName { TOKEN_PAIRS = 'token_pairs', COMMON_CONFIGURATION = 'common_configuration', TOKEN_PRICES = 'token_prices', + MULTI_SIGNATURE = 'multi_signature', } From f62038d0dda414b00ca2df363066bf669b133a35 Mon Sep 17 00:00:00 2001 From: Sotatek-ThinhNguyen2 Date: Thu, 12 Sep 2024 11:08:27 +0700 Subject: [PATCH 3/6] feat: evm multi signature validator --- .env.example | 3 + package.json | 1 + src/config/config.module.ts | 3 +- src/constants/blockchain.constant.ts | 1 + src/constants/env.constant.ts | 2 + ...1-add-error-retry-multi-signature-table.ts | 13 + .../repositories/event-log.repository.ts | 20 +- src/modules/crawler/crawler.console.ts | 15 + src/modules/crawler/crawler.minabridge.ts | 5 +- src/modules/crawler/crawler.module.ts | 2 + .../entities/multi-signature.entity.ts | 6 + src/modules/crawler/sender.evmbridge.ts | 167 ++++- .../web3/abis/eth-bridge-contract.json | 581 +++++++++++++++++- src/shared/modules/web3/web3.module.ts | 4 +- src/shared/modules/web3/web3.service.ts | 30 +- 15 files changed, 822 insertions(+), 31 deletions(-) create mode 100644 src/database/migrations/1726107274721-add-error-retry-multi-signature-table.ts diff --git a/.env.example b/.env.example index 53df2e1..1286d1b 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,9 @@ ETH_BRIDGE_START_BLOCK=4863742 ETH_BRIDGE_CONTRACT_ADDRESS=0x83e21AccD43Bb7C23C51e68fFa345fab3983FfeC ETH_TOKEN_BRIDGE_ADDRESS=0x0000000000000000000000000000000000000000 ETH_BRIDGE_RPC_OPTIONS=https://ethereum-sepolia.publicnode.com +ETH_BRIDGE_DOMAIN_NAME=MinaBridge +ETH_BRIDGE_DOMAIN_VERSION=1.0.0 + MINA_BRIDGE_START_BLOCK=0 MINA_BRIDGE_CONTRACT_ADDRESS=B62qkttesK1uAJU5iL8vqcfyoHX8KqVVAiK6VUWvro9EafSACYdZHbg diff --git a/package.json b/package.json index 04b9531..1662f4d 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "class-validator": "^0.14.0", "crypto-js": "^4.2.0", "dayjs": "^1.11.10", + "ethers": "^5.7.2", "ioredis": "^5.3.2", "joi": "^17.11.0", "log4js": "^6.9.1", diff --git a/src/config/config.module.ts b/src/config/config.module.ts index c1ba58d..6aef147 100644 --- a/src/config/config.module.ts +++ b/src/config/config.module.ts @@ -47,6 +47,8 @@ import redisConfig from './redis.config'; [EEnvKey.MINA_TOKEN_BRIDGE_ADDRESS]: Joi.string().required(), [EEnvKey.ADMIN_MESSAGE_FOR_SIGN]: Joi.string().required(), [EEnvKey.MINA_BRIDGE_SC_PRIVATE_KEY]: Joi.string().required(), + [EEnvKey.ETH_BRIDGE_DOMAIN_NAME]: Joi.string().required(), + [EEnvKey.ETH_BRIDGE_DOMAIN_VERSION]: Joi.string().required(), // coinmarketcap [EEnvKey.COINMARKET_KEY]: Joi.string().required(), [EEnvKey.COINMARKET_URL]: Joi.string().required(), @@ -60,7 +62,6 @@ import redisConfig from './redis.config'; value[EEnvKey.MINA_BRIDGE_START_BLOCK] = Number(value[EEnvKey.MINA_BRIDGE_START_BLOCK]).valueOf(); value[EEnvKey.MINA_BRIDGE_RPC_OPTIONS] = value[EEnvKey.MINA_BRIDGE_RPC_OPTIONS].split(','); value[EEnvKey.ETH_BRIDGE_RPC_OPTIONS] = value[EEnvKey.ETH_BRIDGE_RPC_OPTIONS].split(','); - value[EEnvKey.SIGNER_PRIVATE_KEY] = value[EEnvKey.SIGNER_PRIVATE_KEY].split(','); return value; }), diff --git a/src/constants/blockchain.constant.ts b/src/constants/blockchain.constant.ts index ff49b9c..45bc517 100644 --- a/src/constants/blockchain.constant.ts +++ b/src/constants/blockchain.constant.ts @@ -1,6 +1,7 @@ export const DEFAULT_DECIMAL_PLACES = 6; export const DEFAULT_ADDRESS_PREFIX = '0x'; export const DECIMAL_BASE = 10; +export const FIXED_ESTIMATE_GAS = 100000; export enum ENetworkName { ETH = 'eth', MINA = 'mina', diff --git a/src/constants/env.constant.ts b/src/constants/env.constant.ts index 150e6af..428362d 100644 --- a/src/constants/env.constant.ts +++ b/src/constants/env.constant.ts @@ -39,6 +39,8 @@ export enum EEnvKey { COINMARKET_KEY = 'COINMARKET_KEY', COINMARKET_URL = 'COINMARKET_URL', BASE_MINA_BRIDGE_FEE = 'BASE_MINA_BRIDGE_FEE', + ETH_BRIDGE_DOMAIN_NAME = 'ETH_BRIDGE_DOMAIN_NAME', + ETH_BRIDGE_DOMAIN_VERSION = 'ETH_BRIDGE_DOMAIN_VERSION', } export enum EEnvironments { diff --git a/src/database/migrations/1726107274721-add-error-retry-multi-signature-table.ts b/src/database/migrations/1726107274721-add-error-retry-multi-signature-table.ts new file mode 100644 index 0000000..c6c42eb --- /dev/null +++ b/src/database/migrations/1726107274721-add-error-retry-multi-signature-table.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddErrorRetryMultiSignatureTable1726107274721 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE multi_signature ADD COLUMN retry BIGINT NULL`); + await queryRunner.query(`ALTER TABLE multi_signature ADD COLUMN error_code TEXT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('multi_signature', 'retry'); + await queryRunner.dropColumn('multi_signature', 'error_code'); + } +} diff --git a/src/database/repositories/event-log.repository.ts b/src/database/repositories/event-log.repository.ts index 7959fe8..4e2bba9 100644 --- a/src/database/repositories/event-log.repository.ts +++ b/src/database/repositories/event-log.repository.ts @@ -1,4 +1,5 @@ import { EntityRepository } from 'nestjs-typeorm-custom-repository'; +import { Brackets } from 'typeorm'; import { EDirection } from '@constants/api.constant'; import { EEventStatus, ENetworkName } from '@constants/blockchain.constant'; @@ -22,7 +23,10 @@ export class EventLogRepository extends BaseRepository { if (threshold) { qb.andWhere( - `(SELECT COUNT(${ETableName.MULTI_SIGNATURE}.id) FROM ${ETableName.MULTI_SIGNATURE} WHERE ${ETableName.MULTI_SIGNATURE}.tx_id = ${this.alias}.id) >= :threshold`, + `(SELECT COUNT(${ETableName.MULTI_SIGNATURE}.id) + FROM ${ETableName.MULTI_SIGNATURE} + WHERE ${ETableName.MULTI_SIGNATURE}.tx_id = ${this.alias}.id + AND ${ETableName.MULTI_SIGNATURE}.signature IS NOT NULL) >= :threshold `, { threshold, }, @@ -129,8 +133,18 @@ export class EventLogRepository extends BaseRepository { const qb = this.createQueryBuilder(`${this.alias}`); qb.leftJoinAndSelect(`${this.alias}.validator`, 'signature'); qb.where(`${this.alias}.networkReceived = :network`, { network }); - qb.andWhere(`(signature.validator IS NULL OR signature.validator != :validator)`, { validator }) - .andWhere(`${this.alias}.status = :status`, { status: EEventStatus.WAITING }) + qb.andWhere( + new Brackets(qb => { + qb.where(`signature.validator IS NULL`) + .orWhere(`signature.validator != :validator`, { validator }) + .orWhere( + new Brackets(qb => { + qb.where(`signature.signature IS NULL`).andWhere(`signature.retry < 3`); + }), + ); + }), + ); + qb.andWhere(`${this.alias}.status = :status`, { status: EEventStatus.WAITING }) .addOrderBy(`${this.alias}.id`, EDirection.ASC) .addOrderBy(`${this.alias}.retry`, EDirection.ASC); diff --git a/src/modules/crawler/crawler.console.ts b/src/modules/crawler/crawler.console.ts index cd46611..cb2b5b2 100644 --- a/src/modules/crawler/crawler.console.ts +++ b/src/modules/crawler/crawler.console.ts @@ -45,6 +45,21 @@ export class CrawlerConsole { } } + @Command({ + command: 'validate-eth-bridge-unlock', + description: 'validate ETH Bridge unlock', + }) + async handleValidateMinaLockTx() { + try { + while (true) { + this.senderEVMBridge.handleValidateUnlockTxEVM(); + await sleep(15); + } + } catch (error) { + this.logger.error(error); + } + } + @Command({ command: 'sender-eth-bridge-unlock', description: 'sender ETH Bridge unlock', diff --git a/src/modules/crawler/crawler.minabridge.ts b/src/modules/crawler/crawler.minabridge.ts index 3772b75..6ef24ce 100644 --- a/src/modules/crawler/crawler.minabridge.ts +++ b/src/modules/crawler/crawler.minabridge.ts @@ -95,12 +95,11 @@ export class SCBridgeMinaCrawler { }; } - public async handlerLockEvent(event, queryRunner: QueryRunner) { + public async handlerLockEvent(event: any, queryRunner: QueryRunner) { const field = Field.from(event.event.data.receipt.toString()); const receiveAddress = DEFAULT_ADDRESS_PREFIX + field.toBigInt().toString(16); - const eventUnlock = { - senderAddress: event.event.data.locker, + senderAddress: JSON.parse(JSON.stringify(event.event.data.locker)), amountFrom: event.event.data.amount.toString(), tokenFromAddress: this.configService.get(EEnvKey.MINA_TOKEN_BRIDGE_ADDRESS), networkFrom: ENetworkName.MINA, diff --git a/src/modules/crawler/crawler.module.ts b/src/modules/crawler/crawler.module.ts index f890539..a664fbe 100644 --- a/src/modules/crawler/crawler.module.ts +++ b/src/modules/crawler/crawler.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { CommonConfigRepository } from 'database/repositories/common-configuration.repository'; import { CrawlContractRepository } from 'database/repositories/crawl-contract.repository'; import { EventLogRepository } from 'database/repositories/event-log.repository'; +import { MultiSignatureRepository } from 'database/repositories/multi-signature.repository'; import { TokenPairRepository } from 'database/repositories/token-pair.repository'; import { TokenPriceRepository } from 'database/repositories/token-price.repository'; import { CustomRepositoryModule } from 'nestjs-typeorm-custom-repository'; @@ -22,6 +23,7 @@ import { SenderMinaBridge } from './sender.minabridge'; CommonConfigRepository, TokenPairRepository, TokenPriceRepository, + MultiSignatureRepository, ]), ], providers: [ diff --git a/src/modules/crawler/entities/multi-signature.entity.ts b/src/modules/crawler/entities/multi-signature.entity.ts index 2b069b7..3107902 100644 --- a/src/modules/crawler/entities/multi-signature.entity.ts +++ b/src/modules/crawler/entities/multi-signature.entity.ts @@ -15,9 +15,15 @@ export class MultiSignature extends BaseEntityIncludeTime { @Column({ name: 'tx_id', type: 'bigint', nullable: true }) txId: number; + @Column({ name: 'retry', type: 'bigint', nullable: true }) + retry: number; + @Column({ name: 'signature', type: 'text', nullable: true }) signature: string; + @Column({ name: 'error_code', type: 'text', nullable: true }) + errorCode: string | unknown; + @Column({ name: 'chain', type: 'varchar', enum: ENetworkName, nullable: true }) chain: ENetworkName; diff --git a/src/modules/crawler/sender.evmbridge.ts b/src/modules/crawler/sender.evmbridge.ts index db2413e..385d9b8 100644 --- a/src/modules/crawler/sender.evmbridge.ts +++ b/src/modules/crawler/sender.evmbridge.ts @@ -1,29 +1,49 @@ import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import BigNumber from 'bignumber.js'; import { CommonConfigRepository } from 'database/repositories/common-configuration.repository'; import { EventLogRepository } from 'database/repositories/event-log.repository'; +import { MultiSignatureRepository } from 'database/repositories/multi-signature.repository'; import { TokenPairRepository } from 'database/repositories/token-pair.repository'; +import { ethers } from 'ethers'; +import { Logger } from 'log4js'; -import { DECIMAL_BASE, EEventStatus, ENetworkName } from '@constants/blockchain.constant'; +import { getEthBridgeAddress } from '@config/common.config'; + +import { DECIMAL_BASE, EEventStatus, ENetworkName, FIXED_ESTIMATE_GAS } from '@constants/blockchain.constant'; +import { EEnvKey } from '@constants/env.constant'; import { EError } from '@constants/error.constant'; +import { LoggerService } from '@shared/modules/logger/logger.service'; import { ETHBridgeContract } from '@shared/modules/web3/web3.service'; import { addDecimal, calculateFee } from '@shared/utils/bignumber'; +import { EventLog } from './entities'; +import { CommonConfig } from './entities/common-config.entity'; +import { MultiSignature } from './entities/multi-signature.entity'; + @Injectable() export class SenderEVMBridge { + private readonly logger: Logger; constructor( private readonly eventLogRepository: EventLogRepository, private readonly commonConfigRepository: CommonConfigRepository, private readonly tokenPairRepository: TokenPairRepository, + private readonly multiSignatureRepository: MultiSignatureRepository, private readonly ethBridgeContract: ETHBridgeContract, - ) {} + private readonly configService: ConfigService, + private readonly loggerService: LoggerService, + ) { + this.logger = loggerService.getLogger('SENDER_EVM_CONSOLE'); + } async handleUnlockEVM() { - let dataLock, configTip; + let dataLock: EventLog, configTip: CommonConfig; try { + const threshold = await this.ethBridgeContract.getValidatorThreshold(); + [dataLock, configTip] = await Promise.all([ - this.eventLogRepository.getEventLockWithNetwork(ENetworkName.ETH), + this.eventLogRepository.getEventLockWithNetwork(ENetworkName.ETH, threshold), this.commonConfigRepository.getCommonConfig(), ]); @@ -60,26 +80,28 @@ export class SenderEVMBridge { ); return; } - const gasFee = await this.ethBridgeContract.getEstimateGas( - tokenReceivedAddress, - BigNumber(amountReceive), - txHashLock, - receiveAddress, - 0, - ); - const protocolFee = calculateFee(amountReceive, gasFee, configTip.tip); + // const gasFee = await this.ethBridgeContract.getEstimateGas( + // tokenReceivedAddress, + // BigNumber(amountReceive), + // txHashLock, + // receiveAddress, + // 0, + // ); + const protocolFee = calculateFee(amountReceive, FIXED_ESTIMATE_GAS, configTip.tip); const result = await this.ethBridgeContract.unlock( tokenReceivedAddress, BigNumber(amountReceive), txHashLock, receiveAddress, BigNumber(protocolFee), + dataLock.validator.map(e => e.signature), ); // Update status eventLog when call function unlock if (result.success) { await this.eventLogRepository.updateStatusAndRetryEvenLog(dataLock.id, dataLock.retry, EEventStatus.PROCESSING); } else { + this.logger.log(EError.INVALID_SIGNATURE, result.error); await this.eventLogRepository.updateStatusAndRetryEvenLog( dataLock.id, Number(dataLock.retry + 1), @@ -89,6 +111,7 @@ export class SenderEVMBridge { } return result; } catch (error) { + this.logger.log(EError.INVALID_SIGNATURE, error); await this.eventLogRepository.updateStatusAndRetryEvenLog( dataLock.id, Number(dataLock.retry + 1), @@ -98,6 +121,97 @@ export class SenderEVMBridge { } } + async handleValidateUnlockTxEVM() { + let dataLock, configTip, wallet; + const privateKey = this.configService.get(EEnvKey.SIGNER_PRIVATE_KEY); + wallet = new ethers.Wallet(privateKey); + try { + [dataLock, configTip] = await Promise.all([ + this.eventLogRepository.getValidatorPendingSignature(wallet.address, ENetworkName.ETH), + this.commonConfigRepository.getCommonConfig(), + ]); + + if (!dataLock) { + return; + } + + const { tokenReceivedAddress, tokenFromAddress, txHashLock, receiveAddress, senderAddress, amountFrom } = + dataLock; + + const tokenPair = await this.tokenPairRepository.getTokenPair(tokenFromAddress, tokenReceivedAddress); + + if (!tokenPair) { + await this.eventLogRepository.updateStatusAndRetryEvenLog( + dataLock.id, + dataLock.retry, + EEventStatus.NOTOKENPAIR, + ); + return; + } + + const amountReceive = BigNumber(amountFrom) + .dividedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.fromDecimal)) + .multipliedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.toDecimal)) + .toString(); + + // const gasFee = await this.ethBridgeContract.getEstimateGas( + // tokenReceivedAddress, + // BigNumber(amountReceive), + // txHashLock, + // receiveAddress, + // 0, + // ); + const protocolFee = calculateFee(amountReceive, FIXED_ESTIMATE_GAS, configTip.tip); + + const signTx = await this.getSignature( + wallet, + { + name: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_NAME), + version: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_VERSION), + chainId: await this.ethBridgeContract.getChainId(), + verifyingContract: getEthBridgeAddress(this.configService), + }, + + { + UNLOCK: [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'user', type: 'address' }, + { name: 'hash', type: 'string' }, + { name: 'fee', type: 'uint256' }, + ], + }, + { + token: tokenReceivedAddress, + amount: amountReceive, + user: receiveAddress, + hash: txHashLock, + fee: protocolFee.toString(), + }, + ); + + // const result = await this.ethBridgeContract.unlock( + // tokenReceivedAddress, + // BigNumber(amountReceive), + // txHashLock, + // '0x5321e004589a3f7cafcF8E2802c3c569A74DEac2', + // BigNumber(protocolFee), + // [signTx.signature], + // ); + await this.multiSignatureRepository.save( + new MultiSignature({ + chain: ENetworkName.ETH, + signature: signTx.signature, + validator: wallet.address, + txId: dataLock.id, + }), + ); + } catch (error) { + this.logger.log(EError.INVALID_SIGNATURE, error); + await this.upsertErrorAndRetryMultiSignature(wallet.address, dataLock.id, error); + } + } + private async isPassDailyQuota(address: string, fromDecimal: number): Promise { const [dailyQuota, totalamount] = await Promise.all([ await this.commonConfigRepository.getCommonConfig(), @@ -112,4 +226,33 @@ export class SenderEVMBridge { } return true; } + + private async getSignature( + wallet: ethers.Wallet, + domain: ethers.TypedDataDomain, + types: Record, + value: Record, + ) { + const signature = await wallet._signTypedData(domain, types, value); + + return { signature, payload: { domain, type: types, data: value } }; + } + + public async upsertErrorAndRetryMultiSignature(validator: string, txId: number, errorCode: unknown) { + const validatorSignature = await this.multiSignatureRepository.findOne({ + where: { txId, validator }, + }); + if (!validatorSignature) { + await this.multiSignatureRepository.save( + new MultiSignature({ + txId, + validator, + retry: 1, + errorCode, + }), + ); + } else { + await this.multiSignatureRepository.update({ txId, validator }, { retry: ++validatorSignature.retry, errorCode }); + } + } } diff --git a/src/shared/modules/web3/abis/eth-bridge-contract.json b/src/shared/modules/web3/abis/eth-bridge-contract.json index d4cee42..2c826fa 100644 --- a/src/shared/modules/web3/abis/eth-bridge-contract.json +++ b/src/shared/modules/web3/abis/eth-bridge-contract.json @@ -1 +1,580 @@ -[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxAmount","type":"uint256"}],"name":"ChangeMaxAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"ChangeMinAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"minter","type":"address"}],"name":"ChangeMinter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"locker","type":"address"},{"indexed":false,"internalType":"string","name":"receipt","type":"string"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"tokenName","type":"string"}],"name":"Lock","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"bool","name":"whitelist","type":"bool"}],"name":"SetWhitelistToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"hash","type":"string"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Unlock","type":"event"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"}],"name":"changeMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"},{"internalType":"uint256","name":"_minAmount","type":"uint256"},{"internalType":"uint256","name":"_maxAmount","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"string","name":"receipt","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lock","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"maxAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"max","type":"uint256"}],"name":"setMaxAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"min","type":"uint256"}],"name":"setMinAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"whitelist","type":"bool"}],"name":"setWhitelistToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"string","name":"hash","type":"string"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"unlockHash","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maxAmount", + "type": "uint256" + } + ], + "name": "ChangeMaxAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "minAmount", + "type": "uint256" + } + ], + "name": "ChangeMinAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "ChangeMinter", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "ChangeThreshold", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "locker", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "receipt", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "tokenName", + "type": "string" + } + ], + "name": "Lock", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelist", + "type": "bool" + } + ], + "name": "SetWhitelistToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "hash", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "Unlock", + "type": "event" + }, + { + "inputs": [], + "name": "UNLOCK_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_digest", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + } + ], + "name": "_getSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minter", + "type": "address" + } + ], + "name": "changeMinter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newThreshold", + "type": "uint256" + } + ], + "name": "changeThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "string", + "name": "hash", + "type": "string" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "getUnlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "_unlockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxAmount", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "_validators", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "totalSig", + "type": "uint256" + } + ], + "name": "isValidThreshold", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "string", + "name": "receipt", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "lock", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "maxAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "max", + "type": "uint256" + } + ], + "name": "setMaxAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "min", + "type": "uint256" + } + ], + "name": "setMinAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bool", + "name": "whitelist", + "type": "bool" + } + ], + "name": "setWhitelistToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "threshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "string", + "name": "hash", + "type": "string" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "_signatures", + "type": "bytes[]" + } + ], + "name": "unlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "unlockHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "validators", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistTokens", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/shared/modules/web3/web3.module.ts b/src/shared/modules/web3/web3.module.ts index 1f5e80c..8872024 100644 --- a/src/shared/modules/web3/web3.module.ts +++ b/src/shared/modules/web3/web3.module.ts @@ -40,14 +40,14 @@ export interface IRpcService { web3: Web3; resetApi: () => Promise; maxTries: number; - privateKeys: string[]; + privateKeys: string; getNonce: (walletAddress: string) => Promise; } export const RpcFactory = async (configService: ConfigService, network?: ENetworkName): Promise => { let rpcRound = 0; const rpc = configService.get(EEnvKey.ETH_BRIDGE_RPC_OPTIONS); - const privateKeys = configService.get(EEnvKey.SIGNER_PRIVATE_KEY); + const privateKeys = configService.get(EEnvKey.SIGNER_PRIVATE_KEY); const getNextRPcRound = (): Web3 => { return new Web3(rpc[rpcRound++ % rpc.length]); diff --git a/src/shared/modules/web3/web3.service.ts b/src/shared/modules/web3/web3.service.ts index b9cbb2d..0ff73a6 100644 --- a/src/shared/modules/web3/web3.service.ts +++ b/src/shared/modules/web3/web3.service.ts @@ -75,10 +75,7 @@ export class DefaultContract { } public async estimateGas(method: string, param: Array, specifySignerIndex?: number): Promise { - const signer = this.rpcService.web3.eth.accounts.privateKeyToAccount( - this.rpcService.privateKeys[specifySignerIndex ?? 0], - ); - + const signer = this.rpcService.web3.eth.accounts.privateKeyToAccount(this.rpcService.privateKeys); const data = this.contract.methods[method](...param).encodeABI(); const gasPrice = await this.rpcService.web3.eth.getGasPrice(); const nonce = await this.rpcService.getNonce(signer.address); @@ -102,9 +99,7 @@ export class DefaultContract { specifySignerIndex?: number, ): Promise<{ success: boolean; error: Error; data: TransactionReceipt }> { try { - const signer = this.rpcService.web3.eth.accounts.privateKeyToAccount( - this.rpcService.privateKeys[specifySignerIndex ?? 0], - ); + const signer = this.rpcService.web3.eth.accounts.privateKeyToAccount(this.rpcService.privateKeys); const data = this.contract.methods[method](...param).encodeABI(); const gasPrice = await this.rpcService.web3.eth.getGasPrice(); @@ -181,6 +176,10 @@ export class DefaultContract { return this.rpcService.web3.eth.getBlock(blockNumber); } + public async getChainId() { + return this.rpcService.web3.eth.getChainId(); + } + public async convertGasPriceToEther(amount: number) { try { const gasPrice = await this.rpcService.web3.eth.getGasPrice(); @@ -204,11 +203,23 @@ export class ETHBridgeContract extends DefaultContract { public async latestIndex() { return this.call('latestIndex', []); } + public async getValidatorThreshold() { + return this.call('threshold', []); + } public async mintNFT(toAddress: string) { return this.write('mint', [toAddress]); } - public async unlock(tokenFromAddress, amount, txHashLock, receiveAddress, fee?) { - return this.write('unlock', [tokenFromAddress, amount, receiveAddress, txHashLock, fee]); + public async unlock(tokenFromAddress, amount, txHashLock, receiveAddress, fee?, _signatures?) { + console.log( + '🚀 ~ ETHBridgeContract ~ unlock ~ tokenFromAddress, amount, txHashLock, receiveAddress, fee?, _signatures?:', + tokenFromAddress, + amount, + txHashLock, + receiveAddress, + fee, + _signatures, + ); + return this.write('unlock', [tokenFromAddress, amount, receiveAddress, txHashLock, fee, _signatures]); } public async getEstimateGas(tokenFromAddress, amount, txHashLock, receiveAddress, fee?) { const estimateGas = await this.estimateGas('unlock', [ @@ -217,6 +228,7 @@ export class ETHBridgeContract extends DefaultContract { receiveAddress, txHashLock, Number(fee), + [], ]); const getGasPrice = await this.convertGasPriceToEther(estimateGas); return getGasPrice; From b106d34e42d829c6bfce5bae3ef4c42bce433c9e Mon Sep 17 00:00:00 2001 From: Tan Hoang Date: Fri, 13 Sep 2024 09:49:30 +0700 Subject: [PATCH 4/6] feat: evm validator docker compose' --- docker-compose.dev.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 337f7e4..dff0cd7 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -82,6 +82,17 @@ services: networks: - myNetwork user: node + validate-evm-signature-1: + image: mina-bridge:1.0.0 + command: > + sh -c "npm run console validate-eth-bridge-unlock" + tty: true + restart: always + depends_on: + - postgres + networks: + - myNetwork + user: node postgres: container_name: mina-bridge-${NODE_ENV}-postgres image: postgres:15.3-alpine3.18 From 190d464d4ab139b2b94fd0901f6a244fc8682d6c Mon Sep 17 00:00:00 2001 From: Sotatek-ThinhNguyen2 Date: Tue, 17 Sep 2024 14:33:16 +0700 Subject: [PATCH 5/6] chore: refactor evm validator and test cases --- src/modules/crawler/crawler.console.ts | 2 +- src/modules/crawler/sender.evmbridge.ts | 233 ++++++++---------- src/modules/crawler/tests/evm-sender.spec.ts | 246 ++++++++++++++----- src/modules/users/users.service.ts | 17 +- 4 files changed, 289 insertions(+), 209 deletions(-) diff --git a/src/modules/crawler/crawler.console.ts b/src/modules/crawler/crawler.console.ts index cb2b5b2..8602510 100644 --- a/src/modules/crawler/crawler.console.ts +++ b/src/modules/crawler/crawler.console.ts @@ -52,7 +52,7 @@ export class CrawlerConsole { async handleValidateMinaLockTx() { try { while (true) { - this.senderEVMBridge.handleValidateUnlockTxEVM(); + this.senderEVMBridge.unlockEVMTransaction(); await sleep(15); } } catch (error) { diff --git a/src/modules/crawler/sender.evmbridge.ts b/src/modules/crawler/sender.evmbridge.ts index 385d9b8..749d7fa 100644 --- a/src/modules/crawler/sender.evmbridge.ts +++ b/src/modules/crawler/sender.evmbridge.ts @@ -19,7 +19,6 @@ import { ETHBridgeContract } from '@shared/modules/web3/web3.service'; import { addDecimal, calculateFee } from '@shared/utils/bignumber'; import { EventLog } from './entities'; -import { CommonConfig } from './entities/common-config.entity'; import { MultiSignature } from './entities/multi-signature.entity'; @Injectable() @@ -38,46 +37,28 @@ export class SenderEVMBridge { } async handleUnlockEVM() { - let dataLock: EventLog, configTip: CommonConfig; + let dataLock: EventLog; try { - const threshold = await this.ethBridgeContract.getValidatorThreshold(); - - [dataLock, configTip] = await Promise.all([ - this.eventLogRepository.getEventLockWithNetwork(ENetworkName.ETH, threshold), + const [threshold, configTip] = await Promise.all([ + this.ethBridgeContract.getValidatorThreshold(), this.commonConfigRepository.getCommonConfig(), ]); - if (!dataLock) { - return; - } + dataLock = await this.eventLogRepository.getEventLockWithNetwork(ENetworkName.ETH, threshold); + if (!dataLock) return; await this.eventLogRepository.updateLockEvenLog(dataLock.id, EEventStatus.PROCESSING); - const { tokenReceivedAddress, tokenFromAddress, txHashLock, receiveAddress, senderAddress, amountFrom } = - dataLock; + const { tokenReceivedAddress, txHashLock, receiveAddress } = dataLock; - const tokenPair = await this.tokenPairRepository.getTokenPair(tokenFromAddress, tokenReceivedAddress); + const { tokenPair, amountReceive } = await this.getTokenPairAndAmount(dataLock); if (!tokenPair) { - await this.eventLogRepository.updateStatusAndRetryEvenLog( - dataLock.id, - dataLock.retry, - EEventStatus.NOTOKENPAIR, - ); + await this.updateLogStatusWithRetry(dataLock, EEventStatus.NOTOKENPAIR); return; } - const amountReceive = BigNumber(amountFrom) - .dividedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.fromDecimal)) - .multipliedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.toDecimal)) - .toString(); - - const isPassDailyQuota = await this.isPassDailyQuota(senderAddress, tokenPair.fromDecimal); - if (!isPassDailyQuota) { - await this.eventLogRepository.updateStatusAndRetryEvenLog( - dataLock.id, - dataLock.retry, - EEventStatus.FAILED, - EError.OVER_DAILY_QUOTA, - ); + const isPassQuota = await this.isPassDailyQuota(dataLock.senderAddress, tokenPair.fromDecimal); + if (!isPassQuota) { + await this.updateLogStatusWithRetry(dataLock, EEventStatus.FAILED, EError.OVER_DAILY_QUOTA); return; } // const gasFee = await this.ethBridgeContract.getEstimateGas( @@ -96,64 +77,37 @@ export class SenderEVMBridge { BigNumber(protocolFee), dataLock.validator.map(e => e.signature), ); - // Update status eventLog when call function unlock if (result.success) { - await this.eventLogRepository.updateStatusAndRetryEvenLog(dataLock.id, dataLock.retry, EEventStatus.PROCESSING); + await this.updateLogStatusWithRetry(dataLock, EEventStatus.PROCESSING); } else { - this.logger.log(EError.INVALID_SIGNATURE, result.error); - await this.eventLogRepository.updateStatusAndRetryEvenLog( - dataLock.id, - Number(dataLock.retry + 1), - EEventStatus.FAILED, - result.error, - ); + await this.handleError(result.error, dataLock); } return result; } catch (error) { - this.logger.log(EError.INVALID_SIGNATURE, error); - await this.eventLogRepository.updateStatusAndRetryEvenLog( - dataLock.id, - Number(dataLock.retry + 1), - EEventStatus.FAILED, - error, - ); + await this.handleError(error, dataLock); } } - async handleValidateUnlockTxEVM() { - let dataLock, configTip, wallet; - const privateKey = this.configService.get(EEnvKey.SIGNER_PRIVATE_KEY); - wallet = new ethers.Wallet(privateKey); + async unlockEVMTransaction() { + let dataLock: EventLog; try { - [dataLock, configTip] = await Promise.all([ + const wallet = this.getWallet(); + const [validatorData, configTip] = await Promise.all([ this.eventLogRepository.getValidatorPendingSignature(wallet.address, ENetworkName.ETH), this.commonConfigRepository.getCommonConfig(), ]); - + dataLock = validatorData; if (!dataLock) { return; } - - const { tokenReceivedAddress, tokenFromAddress, txHashLock, receiveAddress, senderAddress, amountFrom } = - dataLock; - - const tokenPair = await this.tokenPairRepository.getTokenPair(tokenFromAddress, tokenReceivedAddress); - + const { tokenReceivedAddress, txHashLock, receiveAddress } = dataLock; + const { tokenPair, amountReceive } = await this.getTokenPairAndAmount(dataLock); if (!tokenPair) { - await this.eventLogRepository.updateStatusAndRetryEvenLog( - dataLock.id, - dataLock.retry, - EEventStatus.NOTOKENPAIR, - ); + await this.updateLogStatusWithRetry(dataLock, EEventStatus.NOTOKENPAIR); return; } - const amountReceive = BigNumber(amountFrom) - .dividedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.fromDecimal)) - .multipliedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.toDecimal)) - .toString(); - // const gasFee = await this.ethBridgeContract.getEstimateGas( // tokenReceivedAddress, // BigNumber(amountReceive), @@ -163,52 +117,17 @@ export class SenderEVMBridge { // ); const protocolFee = calculateFee(amountReceive, FIXED_ESTIMATE_GAS, configTip.tip); - const signTx = await this.getSignature( - wallet, - { - name: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_NAME), - version: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_VERSION), - chainId: await this.ethBridgeContract.getChainId(), - verifyingContract: getEthBridgeAddress(this.configService), - }, - - { - UNLOCK: [ - { name: 'token', type: 'address' }, - { name: 'amount', type: 'uint256' }, - { name: 'user', type: 'address' }, - { name: 'hash', type: 'string' }, - { name: 'fee', type: 'uint256' }, - ], - }, - { - token: tokenReceivedAddress, - amount: amountReceive, - user: receiveAddress, - hash: txHashLock, - fee: protocolFee.toString(), - }, - ); + const signTx = await this.getSignature(wallet, { + token: tokenReceivedAddress, + amount: amountReceive, + user: receiveAddress, + hash: txHashLock, + fee: protocolFee.toString(), + }); - // const result = await this.ethBridgeContract.unlock( - // tokenReceivedAddress, - // BigNumber(amountReceive), - // txHashLock, - // '0x5321e004589a3f7cafcF8E2802c3c569A74DEac2', - // BigNumber(protocolFee), - // [signTx.signature], - // ); - await this.multiSignatureRepository.save( - new MultiSignature({ - chain: ENetworkName.ETH, - signature: signTx.signature, - validator: wallet.address, - txId: dataLock.id, - }), - ); + if (signTx.success) await this.saveSignature(wallet.address, signTx.signature, dataLock.id); } catch (error) { - this.logger.log(EError.INVALID_SIGNATURE, error); - await this.upsertErrorAndRetryMultiSignature(wallet.address, dataLock.id, error); + await this.handleError(error, dataLock, true, this.getWallet().address); } } @@ -218,24 +137,44 @@ export class SenderEVMBridge { await this.eventLogRepository.sumAmountBridgeOfUserInDay(address), ]); - if ( - totalamount && + return totalamount && BigNumber(totalamount.totalamount).isGreaterThan(addDecimal(dailyQuota.dailyQuota, fromDecimal)) - ) { - return false; - } - return true; + ? false + : true; } - private async getSignature( - wallet: ethers.Wallet, - domain: ethers.TypedDataDomain, - types: Record, - value: Record, - ) { - const signature = await wallet._signTypedData(domain, types, value); + private async handleError(error: unknown, dataLock: EventLog, isMultiSignature = false, wallet?: string) { + this.logger.log(EError.INVALID_SIGNATURE, error); + const retryCount = dataLock ? Number(dataLock.retry + 1) : 1; + + if (isMultiSignature) { + await this.upsertErrorAndRetryMultiSignature(wallet, dataLock.id, error); + } else { + await this.eventLogRepository.updateStatusAndRetryEvenLog(dataLock.id, retryCount, EEventStatus.FAILED, error); + } + } - return { signature, payload: { domain, type: types, data: value } }; + public async getSignature(wallet: ethers.Wallet, value: Record) { + const signature = await wallet._signTypedData( + { + name: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_NAME), + version: this.configService.get(EEnvKey.ETH_BRIDGE_DOMAIN_VERSION), + chainId: await this.ethBridgeContract.getChainId(), + verifyingContract: getEthBridgeAddress(this.configService), + }, + { + UNLOCK: [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'user', type: 'address' }, + { name: 'hash', type: 'string' }, + { name: 'fee', type: 'uint256' }, + ], + }, + value, + ); + + return { success: true, signature, payload: { data: value } }; } public async upsertErrorAndRetryMultiSignature(validator: string, txId: number, errorCode: unknown) { @@ -244,15 +183,45 @@ export class SenderEVMBridge { }); if (!validatorSignature) { await this.multiSignatureRepository.save( - new MultiSignature({ - txId, - validator, - retry: 1, - errorCode, - }), + new MultiSignature({ txId, validator, retry: 1, errorCode, chain: ENetworkName.ETH }), ); } else { await this.multiSignatureRepository.update({ txId, validator }, { retry: ++validatorSignature.retry, errorCode }); } } + + public async saveSignature(validatorAddress: string, signature: string, txId: number) { + const validatorRecord = await this.multiSignatureRepository.findOneBy({ validator: validatorAddress, txId }); + + if (validatorRecord) { + await this.multiSignatureRepository.update({ validator: validatorAddress, txId }, { signature }); + } else { + await this.multiSignatureRepository.save( + new MultiSignature({ chain: ENetworkName.ETH, signature, validator: validatorAddress, txId }), + ); + } + } + + public async getTokenPairAndAmount(dataLock: EventLog) { + const { tokenReceivedAddress, tokenFromAddress, amountFrom } = dataLock; + + const tokenPair = await this.tokenPairRepository.getTokenPair(tokenFromAddress, tokenReceivedAddress); + if (!tokenPair) return { tokenPair: null, amountReceive: null }; + + const amountReceive = BigNumber(amountFrom) + .dividedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.fromDecimal)) + .multipliedBy(BigNumber(DECIMAL_BASE).pow(tokenPair.toDecimal)) + .toString(); + + return { tokenPair, amountReceive }; + } + + private async updateLogStatusWithRetry(dataLock: EventLog, status: EEventStatus, errorCode?: EError) { + await this.eventLogRepository.updateStatusAndRetryEvenLog(dataLock.id, dataLock.retry, status, errorCode); + } + + public getWallet(): ethers.Wallet { + const privateKey = this.configService.get(EEnvKey.SIGNER_PRIVATE_KEY); + return new ethers.Wallet(privateKey); + } } diff --git a/src/modules/crawler/tests/evm-sender.spec.ts b/src/modules/crawler/tests/evm-sender.spec.ts index 1655dcd..2bcd8f0 100644 --- a/src/modules/crawler/tests/evm-sender.spec.ts +++ b/src/modules/crawler/tests/evm-sender.spec.ts @@ -1,90 +1,200 @@ +import { ConfigModule, ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; import { CommonConfigRepository } from 'database/repositories/common-configuration.repository'; +import { CrawlContractRepository } from 'database/repositories/crawl-contract.repository'; import { EventLogRepository } from 'database/repositories/event-log.repository'; +import { MultiSignatureRepository } from 'database/repositories/multi-signature.repository'; import { TokenPairRepository } from 'database/repositories/token-pair.repository'; -import { TokenPriceRepository } from 'database/repositories/token-price.repository'; +import { DataSource, QueryRunner } from 'typeorm'; import { ConfigurationModule } from '@config/config.module'; +import { EAsset } from '@constants/api.constant'; +import { EEventName, EEventStatus, ENetworkName, ETokenPairStatus } from '@constants/blockchain.constant'; + +import { TokenPair } from '@modules/users/entities/tokenpair.entity'; + +import { LoggerService } from '@shared/modules/logger/logger.service'; import { Web3Module } from '@shared/modules/web3/web3.module'; +import { ETHBridgeContract } from '@shared/modules/web3/web3.service'; +import { EventLog } from '../entities'; +import { CommonConfig } from '../entities/common-config.entity'; +import { MultiSignature } from '../entities/multi-signature.entity'; import { SenderEVMBridge } from '../sender.evmbridge'; +let senderEVMBridge: SenderEVMBridge; +let dataSource: DataSource; +let queryRunner: QueryRunner; +let eventLogRepository: EventLogRepository; +let commonConfigRepository: CommonConfigRepository; +let tokenPairRepository: TokenPairRepository; +let multiSignatureRepository: MultiSignatureRepository; +let loggerService: LoggerService; +let newEthBridgeContract: ETHBridgeContract; // Mock objects const mockJwtService = { // Mock methods if needed sign: jest.fn(), }; -const mockEventLogRepository = { - getEventLockWithNetwork: jest.fn(), - updateStatusAndRetryEvenLog: jest.fn(), - updateLockEvenLog: jest.fn(), - sumAmountBridgeOfUserInDay: jest.fn(), -}; -const mockCommonConfigRepository = { - getCommonConfig: jest.fn(), -}; -const mockTokenPairRepository = { - getTokenPair: jest.fn(), -}; -const mockTokenPriceRepository = { - getRateETHToMina: jest.fn(), -}; -describe('AuthService', () => { - let evmSenderService: SenderEVMBridge; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [Web3Module, ConfigurationModule], - providers: [ - SenderEVMBridge, - { provide: JwtService, useValue: mockJwtService }, - { provide: EventLogRepository, useValue: mockEventLogRepository }, - { provide: CommonConfigRepository, useValue: mockCommonConfigRepository }, - { provide: TokenPairRepository, useValue: mockTokenPairRepository }, - { provide: TokenPriceRepository, useValue: mockTokenPriceRepository }, - ], - }).compile(); - - evmSenderService = module.get(SenderEVMBridge); +beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + Web3Module, + ConfigurationModule, + ConfigModule.forRoot({ + isGlobal: true, + }), + ], + providers: [ + SenderEVMBridge, // Include the AuthService provider + EventLogRepository, + CommonConfigRepository, + MultiSignatureRepository, + { + provide: ETHBridgeContract, + useValue: { + getValidatorThreshold: jest.fn(), + }, + }, + { provide: JwtService, useValue: mockJwtService }, + { + provide: DataSource, + + useValue: { + createQueryRunner: jest.fn().mockReturnValue({ + connect: jest.fn(), + startTransaction: jest.fn(), + commitTransaction: jest.fn(), + rollbackTransaction: jest.fn(), + release: jest.fn(), + manager: { + save: jest.fn(), + findOne: jest.fn(), + update: jest.fn(), + findOneBy: jest.fn(), + }, + }), + }, + }, + { + provide: CrawlContractRepository, + useValue: { + findOne: jest.fn(), + create: jest.fn(), + save: jest.fn(), + }, + }, + { + provide: TokenPairRepository, + useValue: { + getTokenPair: jest.fn(), + }, + }, + { + provide: LoggerService, + useValue: { + getLogger: jest.fn().mockReturnValue({ + info: jest.fn(), + log: jest.fn(), + }), + }, + }, + ], + }).compile(); + + newEthBridgeContract = module.get(ETHBridgeContract); + senderEVMBridge = module.get(SenderEVMBridge); + dataSource = module.get(DataSource); + queryRunner = dataSource.createQueryRunner(); + eventLogRepository = module.get(EventLogRepository); + commonConfigRepository = module.get(CommonConfigRepository); + tokenPairRepository = module.get(TokenPairRepository); + multiSignatureRepository = module.get(MultiSignatureRepository); + loggerService = module.get(LoggerService); +}); + +describe('handleValidateUnlockTxEVM', () => { + const commonConfig = { + id: 1, + dailyQuota: 500, + tip: 0.5, + asset: 'ETH', + } as CommonConfig; + + const tokenPair = { + id: 2, + fromChain: ENetworkName.MINA, + toChain: ENetworkName.ETH, + fromSymbol: EAsset.WETH, + toSymbol: EAsset.ETH, + fromAddress: 'B62qqki2ZnVzaNsGaTDAP6wJYCth5UAcY6tPX2TQYHdwD8D4uBgrDKC', + toAddress: '0x0000000000000000000000000000000000000000', + fromDecimal: 9, + toDecimal: 18, + fromScAddress: 'B62qoArtCz52mtxKxtGR3sPdS9yq6DucRW53nAerndwg9oEhUvJvpRy', + toScAddress: '0x83e21AccD43Bb7C23C51e68fFa345fab3983FfeC', + status: ETokenPairStatus.ENABLE, + } as TokenPair; + + const data = { + id: 18, + deletedAt: null, + senderAddress: 'B62qjWwgHupW7k7fcTbb2Kszp4RPYBWYdL4KMmoqfkMH3iRN2FN8u5n', + amountFrom: '2', + tokenFromAddress: 'B62qqki2ZnVzaNsGaTDAP6wJYCth5UAcY6tPX2TQYHdwD8D4uBgrDKC', + networkFrom: ENetworkName.MINA, + tokenFromName: 'WETH', + txHashLock: '5JuRCbe4Bu9gbQBbLugbVLateiRNLa8YdJRvfWtvy9iuiNpVXuqr', + receiveAddress: '0x242CF8b33B29aa18205d07467f69177d2c4295DF', + amountReceived: '1989900500', + tokenReceivedAddress: '0x0000000000000000000000000000000000000000', + networkReceived: ENetworkName.ETH, + tokenReceivedName: EAsset.ETH, + txHashUnlock: '0xc1674376040b6cfed204930c5dfd0aae568c3d09e1602bf9fd084fb50bbf40f2', + blockNumber: 345594, + blockTimeLock: 1725877528, + protocolFee: '10099500', + event: EEventName.LOCK, + toTokenDecimal: 18, + status: EEventStatus.WAITING, + retry: 0, + validator: [] as MultiSignature[], + } as EventLog; + it('should handle validator signature generation', async () => { + const wallet = senderEVMBridge.getWallet(); + + data.validator.push({ + validator: wallet.address, + txId: 18, + retry: 2, + signature: + '0xc096d8abb2af534fa09b62ca3825a202172239ee0ab3d8438680faca0f0e59153fef0bdc0681162d94cad9fe77b05d4c1945be9c46cb89f9b2821d8576fb28d31b', + } as MultiSignature); + jest.spyOn(eventLogRepository, 'getValidatorPendingSignature').mockResolvedValue(data); + jest.spyOn(commonConfigRepository, 'getCommonConfig').mockResolvedValue(commonConfig); + jest.spyOn(tokenPairRepository, 'getTokenPair').mockResolvedValue(tokenPair); + jest.spyOn(multiSignatureRepository, 'findOne').mockResolvedValue(data.validator[0]); + jest.spyOn(multiSignatureRepository, 'update').mockResolvedValue(undefined); + + await senderEVMBridge.unlockEVMTransaction(); + + expect(eventLogRepository.getValidatorPendingSignature).toHaveBeenCalled(); + expect(commonConfigRepository.getCommonConfig).toHaveBeenCalled(); + expect(tokenPairRepository.getTokenPair).toHaveBeenCalled(); }); - it('should handle lock events', async () => { - mockJwtService.sign.mockResolvedValue('true'); - mockEventLogRepository.getEventLockWithNetwork.mockResolvedValue({ - id: 333, - tokenReceivedAddress: '0x0000000000000000000000000000000000000000', - tokenFromAddress: 'B62qqki2ZnVzaNsGaTDAP6wJYCth5UAcY6tPX2TQYHdwD8D4uBgrDKC', - receiveAddress: '0xb3Edf83eA590F44f5c400077EBd94CCFE10E4Bb0', - amountFrom: '99499999', - txHashLock: '0x14ee1dbeff96dc2b2170b209472d83184b7640669f8fb24336994fa83e214c10', - senderAddress: 'B62qjWwgHupW7k7fcTbb2Kszp4RPYBWYdL4KMmoqfkMH3iRN2FN8u5n', - }); - mockEventLogRepository.sumAmountBridgeOfUserInDay.mockResolvedValue(0); - mockCommonConfigRepository.getCommonConfig.mockResolvedValue({ - asset: 'ETH', - tip: '0.5', - dailyQuota: '500', - id: 1, - }); - mockTokenPairRepository.getTokenPair.mockResolvedValue({ - id: 5, - deletedAt: null, - fromChain: 'mina', - toChain: 'eth', - fromSymbol: 'WETH', - toSymbol: 'ETH', - fromAddress: 'B62qqki2ZnVzaNsGaTDAP6wJYCth5UAcY6tPX2TQYHdwD8D4uBgrDKC', - toAddress: '0x0000000000000000000000000000000000000000', - fromDecimal: 9, - toDecimal: 18, - fromScAddress: 'B62qoArtCz52mtxKxtGR3sPdS9yq6DucRW53nAerndwg9oEhUvJvpRy', - toScAddress: '0x83e21AccD43Bb7C23C51e68fFa345fab3983FfeC', - status: 'enable', - }); - const result = await evmSenderService.handleUnlockEVM(); - expect(result.success).toBe(true); + it('should handle Unlock EVM and send to blockchain', async () => { + const threshold = await newEthBridgeContract.getValidatorThreshold(); + expect(threshold).toBe('1'); + jest.spyOn(commonConfigRepository, 'getCommonConfig').mockResolvedValue(commonConfig); + jest.spyOn(eventLogRepository, 'getEventLockWithNetwork').mockResolvedValue(data); + jest.spyOn(eventLogRepository, 'updateLockEvenLog').mockResolvedValue(undefined); + jest.spyOn(tokenPairRepository, 'getTokenPair').mockResolvedValue(tokenPair); + jest.spyOn(eventLogRepository, 'updateStatusAndRetryEvenLog').mockResolvedValue(undefined); + + await senderEVMBridge.handleUnlockEVM(); }); }); diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 04a0839..44c65aa 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -6,7 +6,7 @@ import { UserRepository } from 'database/repositories/user.repository'; import { Logger } from 'log4js'; import { DataSource } from 'typeorm'; -import { ENetworkName } from '@constants/blockchain.constant'; +import { ENetworkName, FIXED_ESTIMATE_GAS } from '@constants/blockchain.constant'; import { EEnvKey } from '@constants/env.constant'; import { EError } from '@constants/error.constant'; @@ -92,13 +92,14 @@ export class UsersService { this.configService.get(EEnvKey.DECIMAL_TOKEN_MINA), ); } else { - gasFee = await this.ethBridgeContract.getEstimateGas( - tokenPair.toAddress, - addDecimal(0, tokenPair.toDecimal), - 1, - process.env.ADMIN_ADDRESS_EVM, - 0, - ); + // gasFee = await this.ethBridgeContract.getEstimateGas( + // tokenPair.toAddress, + // addDecimal(0, tokenPair.toDecimal), + // 1, + // process.env.ADMIN_ADDRESS_EVM, + // 0, + // ); + gasFee = FIXED_ESTIMATE_GAS; } return { amount: calculateFee(amount, gasFee, configTip.tip) }; From fb6ca9825690d00f59c7d17b912da9706c2b816d Mon Sep 17 00:00:00 2001 From: Sotatek-ThinhNguyen2 Date: Tue, 17 Sep 2024 14:34:57 +0700 Subject: [PATCH 6/6] fix: get gas EVM chain --- src/constants/blockchain.constant.ts | 1 - src/constants/env.constant.ts | 2 ++ src/modules/crawler/sender.evmbridge.ts | 14 +++++++++++--- src/modules/users/users.service.ts | 14 +++++--------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/constants/blockchain.constant.ts b/src/constants/blockchain.constant.ts index 45bc517..ff49b9c 100644 --- a/src/constants/blockchain.constant.ts +++ b/src/constants/blockchain.constant.ts @@ -1,7 +1,6 @@ export const DEFAULT_DECIMAL_PLACES = 6; export const DEFAULT_ADDRESS_PREFIX = '0x'; export const DECIMAL_BASE = 10; -export const FIXED_ESTIMATE_GAS = 100000; export enum ENetworkName { ETH = 'eth', MINA = 'mina', diff --git a/src/constants/env.constant.ts b/src/constants/env.constant.ts index 428362d..915e878 100644 --- a/src/constants/env.constant.ts +++ b/src/constants/env.constant.ts @@ -41,6 +41,8 @@ export enum EEnvKey { BASE_MINA_BRIDGE_FEE = 'BASE_MINA_BRIDGE_FEE', ETH_BRIDGE_DOMAIN_NAME = 'ETH_BRIDGE_DOMAIN_NAME', ETH_BRIDGE_DOMAIN_VERSION = 'ETH_BRIDGE_DOMAIN_VERSION', + GAS_FEE_EVM = 'GAS_FEE_EVM', + DECIMAL_TOKEN_EVM = 'DECIMAL_TOKEN_EVM', } export enum EEnvironments { diff --git a/src/modules/crawler/sender.evmbridge.ts b/src/modules/crawler/sender.evmbridge.ts index 749d7fa..3281569 100644 --- a/src/modules/crawler/sender.evmbridge.ts +++ b/src/modules/crawler/sender.evmbridge.ts @@ -10,7 +10,7 @@ import { Logger } from 'log4js'; import { getEthBridgeAddress } from '@config/common.config'; -import { DECIMAL_BASE, EEventStatus, ENetworkName, FIXED_ESTIMATE_GAS } from '@constants/blockchain.constant'; +import { DECIMAL_BASE, EEventStatus, ENetworkName } from '@constants/blockchain.constant'; import { EEnvKey } from '@constants/env.constant'; import { EError } from '@constants/error.constant'; @@ -68,7 +68,11 @@ export class SenderEVMBridge { // receiveAddress, // 0, // ); - const protocolFee = calculateFee(amountReceive, FIXED_ESTIMATE_GAS, configTip.tip); + const protocolFee = calculateFee( + amountReceive, + addDecimal(this.configService.get(EEnvKey.GAS_FEE_EVM), this.configService.get(EEnvKey.DECIMAL_TOKEN_EVM)), + configTip.tip, + ); const result = await this.ethBridgeContract.unlock( tokenReceivedAddress, BigNumber(amountReceive), @@ -115,7 +119,11 @@ export class SenderEVMBridge { // receiveAddress, // 0, // ); - const protocolFee = calculateFee(amountReceive, FIXED_ESTIMATE_GAS, configTip.tip); + const protocolFee = calculateFee( + amountReceive, + addDecimal(this.configService.get(EEnvKey.GAS_FEE_EVM), this.configService.get(EEnvKey.DECIMAL_TOKEN_EVM)), + configTip.tip, + ); const signTx = await this.getSignature(wallet, { token: tokenReceivedAddress, diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 44c65aa..2ed93e9 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -6,7 +6,7 @@ import { UserRepository } from 'database/repositories/user.repository'; import { Logger } from 'log4js'; import { DataSource } from 'typeorm'; -import { ENetworkName, FIXED_ESTIMATE_GAS } from '@constants/blockchain.constant'; +import { ENetworkName } from '@constants/blockchain.constant'; import { EEnvKey } from '@constants/env.constant'; import { EError } from '@constants/error.constant'; @@ -92,14 +92,10 @@ export class UsersService { this.configService.get(EEnvKey.DECIMAL_TOKEN_MINA), ); } else { - // gasFee = await this.ethBridgeContract.getEstimateGas( - // tokenPair.toAddress, - // addDecimal(0, tokenPair.toDecimal), - // 1, - // process.env.ADMIN_ADDRESS_EVM, - // 0, - // ); - gasFee = FIXED_ESTIMATE_GAS; + gasFee = addDecimal( + this.configService.get(EEnvKey.GAS_FEE_EVM), + this.configService.get(EEnvKey.DECIMAL_TOKEN_EVM), + ); } return { amount: calculateFee(amount, gasFee, configTip.tip) };