diff --git a/offchain-modules/config.json.example b/offchain-modules/config.json.example index 0a06af88..281cd56e 100644 --- a/offchain-modules/config.json.example +++ b/offchain-modules/config.json.example @@ -95,7 +95,7 @@ "EE782FE170F680D6CAB340ECA5ED2F6E05B0B9809082CF745207E87734211C72" ] }, - "feeLimit": 1000000 + "feeLimit": 10000000 } } } diff --git a/offchain-modules/src/packages/handlers/ckb.ts b/offchain-modules/src/packages/handlers/ckb.ts index ecc1615e..ffb5c610 100644 --- a/offchain-modules/src/packages/handlers/ckb.ts +++ b/offchain-modules/src/packages/handlers/ckb.ts @@ -14,6 +14,7 @@ import Transaction = CKBComponents.Transaction; import { Script as LumosScript } from '@ckb-lumos/base'; import { BigNumber } from 'ethers'; import { RecipientCellData } from '@force-bridge/ckb/tx-helper/generated/eth_recipient_cell'; +import { getAssetTypeByAsset } from '@force-bridge/xchain/tron/utils'; // CKB handler // 1. Listen CKB chain to get new burn events. @@ -55,7 +56,7 @@ export class CkbHandler { { ckbTxHash: burn.ckbTxHash, asset: burn.asset, - assetType: burn.asset, + assetType: getAssetTypeByAsset(burn.asset), amount: burn.amount, recipientAddress: burn.recipientAddress, }, diff --git a/offchain-modules/src/packages/handlers/tron.ts b/offchain-modules/src/packages/handlers/tron.ts index 31d664eb..956ef0eb 100644 --- a/offchain-modules/src/packages/handlers/tron.ts +++ b/offchain-modules/src/packages/handlers/tron.ts @@ -6,6 +6,7 @@ import { ITronLock, TronUnlock, ICkbMint, TronLock } from '@force-bridge/db/mode import { ChainType } from '@force-bridge/ckb/model/asset'; import { promises } from 'fs'; import { sign } from '@force-bridge/ckb/tx-helper/signer'; +import { getAssetTypeByAsset } from '@force-bridge/xchain/tron/utils'; const TronWeb = require('tronweb'); const TronGrid = require('trongrid'); @@ -77,6 +78,10 @@ export class TronHandler { fingerprint: fingerprint, }); for (const data of txs.data) { + if (Object.keys(data.token_info).length == 0) { + logger.debug('invalid trc20 tx, token info is undefined', data); + continue; + } const tx = await this.tronWeb.trx.getTransaction(data.transaction_id); const event = { tx_hash: data.transaction_id, @@ -103,17 +108,6 @@ export class TronHandler { return { ckbRecipient, sudtExtraData }; } - private getAssetTypeByAsset(asset: string) { - switch (asset.length) { - case TRX_ASSET_LENGTH: - return 'trx'; - case TRC10_ASSET_LENGTH: - return 'trc10'; - default: - return 'trc20'; - } - } - private transferEventToCkbMint(event: TronLockEvent) { const { ckbRecipient, sudtExtraData } = this.analyzeMemo(event.memo); return { @@ -132,7 +126,7 @@ export class TronHandler { txIndex: 0, sender: event.sender, asset: event.asset, - assetType: this.getAssetTypeByAsset(event.asset), + assetType: getAssetTypeByAsset(event.asset), amount: event.amount, memo: event.memo, timestamp: event.timestamp, @@ -162,7 +156,7 @@ export class TronHandler { const trc20LockEvents = await this.getTrc20TxsLockEvents(minTimestamp); const totalLockEvents = trxAndTrc10Events.concat(trc20LockEvents); - logger.debug('total lock events', totalLockEvents.length); + logger.debug('total lock events', totalLockEvents); for (const event of totalLockEvents) { if (event.timestamp <= minTimestamp) { @@ -227,7 +221,6 @@ export class TronHandler { for (const key of this.committee.keys) { signed_tx = await this.tronWeb.trx.multiSign(signed_tx, key); } - return signed_tx; } @@ -276,7 +269,6 @@ export class TronHandler { logger.debug('flush pending tx to confirm'); const pendingRecords = await this.db.getTronUnlockRecords('pending'); for (const pendingRecord of pendingRecords) { - // todo: check tx is confirmed try { const confirmedTx = await this.tronWeb.trx.getConfirmedTransaction(pendingRecord.tronTxHash); console.log(confirmedTx); @@ -306,6 +298,7 @@ export class TronHandler { signedTx = await this.multiSignTransferTrc20(unlockRecord); break; } + unlockRecord.tronTxHash = signedTx.txID; unlockRecord.tronTxIndex = 0; unlockRecord.status = 'pending'; diff --git a/offchain-modules/src/packages/xchain/tron/utils.ts b/offchain-modules/src/packages/xchain/tron/utils.ts new file mode 100644 index 00000000..5c9a3f15 --- /dev/null +++ b/offchain-modules/src/packages/xchain/tron/utils.ts @@ -0,0 +1,13 @@ +const TRX_ASSET_LENGTH = 3; +const TRC10_ASSET_LENGTH = 7; + +export function getAssetTypeByAsset(asset: string): string { + switch (asset.length) { + case TRX_ASSET_LENGTH: + return 'trx'; + case TRC10_ASSET_LENGTH: + return 'trc10'; + default: + return 'trc20'; + } +} diff --git a/offchain-modules/src/scripts/integration-test/tron.ts b/offchain-modules/src/scripts/integration-test/tron.ts index 58c687f5..d9c272aa 100644 --- a/offchain-modules/src/scripts/integration-test/tron.ts +++ b/offchain-modules/src/scripts/integration-test/tron.ts @@ -37,6 +37,36 @@ async function transferTrx(tronWeb, from, to, amount, memo, priv) { return broad_tx; } +async function transferTrc10(tronWeb, from, to, amount, tokenID, memo, priv) { + const unsigned_tx = await tronWeb.transactionBuilder.sendToken(to, amount, tokenID, from); + const unsignedWithMemoTx = await tronWeb.transactionBuilder.addUpdateData(unsigned_tx, memo, 'utf8'); + const signed_tx = await tronWeb.trx.sign(unsignedWithMemoTx, priv); + const broad_tx = await tronWeb.trx.broadcast(signed_tx); + return broad_tx; +} + +async function transferTrc20(tronWeb, from, to, amount, contractAddress, memo, priv) { + const options = {}; + const functionSelector = 'transfer(address,uint256)'; + const params = [ + { type: 'address', value: to }, + { type: 'uint256', value: amount }, + ]; + + const unsigned_tx = await tronWeb.transactionBuilder.triggerSmartContract( + contractAddress, + functionSelector, + options, + params, + from, + ); + const unsignedWithMemoTx = await tronWeb.transactionBuilder.addUpdateData(unsigned_tx.transaction, memo, 'utf8'); + + const signed_tx = await tronWeb.trx.sign(unsignedWithMemoTx, priv); + const broad_tx = await tronWeb.trx.broadcast(signed_tx); + return broad_tx; +} + async function main() { const conn = await createConnection(); @@ -59,14 +89,51 @@ async function main() { const recipientLockscript = 'ckt1qyqyph8v9mclls35p6snlaxajeca97tc062sa5gahk'; const sudtExtraData = 'transfer 100 to ckt1qyqyph8v9mclls35p6snlaxajeca97tc062sa5gahk'; const memo = recipientLockscript.concat(',').concat(sudtExtraData); - const lockRes = await transferTrx(tronWeb, from, to, amount, memo, userPrivateKey); - const txHash: string = lockRes.transaction.txID; + + const trxLockRes = await transferTrx(tronWeb, from, to, amount, memo, userPrivateKey); + const trxTxHash: string = trxLockRes.transaction.txID; + + const trc10LockRes = await transferTrc10(tronWeb, from, to, amount, '1000696', memo, userPrivateKey); + const trc10TxHash: string = trc10LockRes.transaction.txID; + + const trc20LockRes = await transferTrc20( + tronWeb, + from, + to, + amount, + 'TVWvkCasxAJUyzPKMQ2Rus1NtmBwrkVyBR', + memo, + userPrivateKey, + ); + const trc20TxHash: string = trc20LockRes.transaction.txID; // create tron unlock const recipientAddress = 'TS6VejPL8cQy6pA8eDGyusmmhCrXHRdJK6'; - let sendBurn = false; let burnTxHash; - const checkEffect = async () => { + + const getBalance = async (assetName) => { + const account = new Account(PRI_KEY); + const ownLockHash = ckb.utils.scriptToHash(await account.getLockscript()); + const asset = new TronAsset(assetName, ownLockHash); + const bridgeCellLockscript = { + codeHash: ForceBridgeCore.config.ckb.deps.bridgeLock.script.codeHash, + hashType: ForceBridgeCore.config.ckb.deps.bridgeLock.script.hashType, + args: asset.toBridgeLockscriptArgs(), + }; + const sudtArgs = ckb.utils.scriptToHash(bridgeCellLockscript); + const sudtType = { + codeHash: ForceBridgeCore.config.ckb.deps.sudtType.script.codeHash, + hashType: ForceBridgeCore.config.ckb.deps.sudtType.script.hashType, + args: sudtArgs, + }; + const balance = await collector.getSUDTBalance( + new Script(sudtType.codeHash, sudtType.args, sudtType.hashType), + await account.getLockscript(), + ); + return balance; + }; + + const checkLock = async (txHash, assetName, assetType, sendBurn) => { // check TronLock and CkbMint saved. const tronLockRecords = await conn.manager.find(TronLock, { where: { @@ -79,8 +146,8 @@ async function main() { assert(tronLockRecord.memo === memo); assert(tronLockRecord.sender === from); - assert(tronLockRecord.asset === 'trx'); - assert(tronLockRecord.assetType === 'trx'); + assert(tronLockRecord.asset === assetName); + assert(tronLockRecord.assetType === assetType); const ckbMintRecords = await conn.manager.find(CkbMint, { where: { @@ -93,38 +160,23 @@ async function main() { assert(ckbMintRecord.chain === ChainType.TRON); assert(ckbMintRecord.sudtExtraData === sudtExtraData); assert(ckbMintRecord.status === 'success'); - // assert(ckbMintRecord.asset === 'trx'); + assert(ckbMintRecord.asset === assetName); assert(ckbMintRecord.amount === amount.toString()); assert(ckbMintRecord.recipientLockscript === recipientLockscript); // check sudt balance. - const account = new Account(PRI_KEY); - const ownLockHash = ckb.utils.scriptToHash(await account.getLockscript()); - const asset = new TronAsset('trx', ownLockHash); - const bridgeCellLockscript = { - codeHash: ForceBridgeCore.config.ckb.deps.bridgeLock.script.codeHash, - hashType: ForceBridgeCore.config.ckb.deps.bridgeLock.script.hashType, - args: asset.toBridgeLockscriptArgs(), - }; - const sudtArgs = ckb.utils.scriptToHash(bridgeCellLockscript); - const sudtType = { - codeHash: ForceBridgeCore.config.ckb.deps.sudtType.script.codeHash, - hashType: ForceBridgeCore.config.ckb.deps.sudtType.script.hashType, - args: sudtArgs, - }; - const balance = await collector.getSUDTBalance( - new Script(sudtType.codeHash, sudtType.args, sudtType.hashType), - await account.getLockscript(), - ); + const balance = await getBalance(assetName); if (!sendBurn) { + logger.debug('assetName', assetName); logger.debug('sudt balance:', balance); logger.debug('expect balance:', new Amount(amount.toString())); assert(balance.eq(new Amount(amount.toString()))); } + }; + const burn = async (sendBurn, assetName, txHash) => { const burnAmount = 1; - // send burn tx if (!sendBurn) { const account = new Account(PRI_KEY); const ownLockHash = ckb.utils.scriptToHash(await account.getLockscript()); @@ -132,15 +184,21 @@ async function main() { const burnTx = await generator.burn( await account.getLockscript(), recipientAddress, - new TronAsset('trx', ownLockHash), + new TronAsset(assetName, ownLockHash), new Amount(burnAmount.toString()), ); const signedTx = ckb.signTransaction(PRI_KEY)(burnTx); burnTxHash = await ckb.rpc.sendTransaction(signedTx); console.log(`burn Transaction has been sent with tx hash ${burnTxHash}`); await waitUntilCommitted(ckb, burnTxHash, 60); - sendBurn = true; + return burnTxHash; } + return txHash; + }; + + const checkUnlock = async (burnTxHash, assetName) => { + const burnAmount = 1; + const balance = await getBalance(assetName); logger.debug('sudt balance:', balance); const expectBalance = new Amount((amount - burnAmount).toString()); @@ -159,10 +217,25 @@ async function main() { }; // try 100 times and wait for 3 seconds every time. + let trxSendBurn = false; + let trc10SendBurn = false; + let burnTrxTxHash = ''; + let burnTrc10TxHash = ''; + for (let i = 0; i < 100; i++) { await asyncSleep(10000); try { - await checkEffect(); + await checkLock(trxTxHash, 'trx', 'trx', trxSendBurn); + burnTrxTxHash = await burn(trxSendBurn, 'trx', burnTrxTxHash); + trxSendBurn = true; + await checkUnlock(burnTrxTxHash, 'trx'); + + await checkLock(trc10TxHash, '1000696', 'trc10', trc10SendBurn); + burnTrc10TxHash = await burn(trc10SendBurn, '1000696', burnTrc10TxHash); + trc10SendBurn = true; + await checkUnlock(burnTrc10TxHash, '1000696'); + + //await checkEffect(trc20TxHash, 'TVWvkCasxAJUyzPKMQ2Rus1NtmBwrkVyBR', 'trc20'); } catch (e) { logger.warn('The tron component integration not pass yet.', { i, e }); continue;