From 88de32b3a4f35fbf99ff0d4f5ed6fa17ba2eca2f Mon Sep 17 00:00:00 2001 From: Derek Chen Date: Fri, 19 Dec 2025 12:21:53 -0500 Subject: [PATCH] fix: celo unstake methods collide with weth unwrap Ticket: SC-4630 --- modules/abstract-eth/src/lib/transaction.ts | 2 +- .../src/lib/transactionBuilder.ts | 2 +- modules/abstract-eth/src/lib/utils.ts | 19 ++++++++++++++++--- modules/abstract-eth/test/unit/utils.ts | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/modules/abstract-eth/src/lib/transaction.ts b/modules/abstract-eth/src/lib/transaction.ts index 221ccad20f..386d5d6b09 100644 --- a/modules/abstract-eth/src/lib/transaction.ts +++ b/modules/abstract-eth/src/lib/transaction.ts @@ -90,7 +90,7 @@ export class Transaction extends BaseTransaction { if (txData.id) { this._id = txData.id; } - this._type = classifyTransaction(txData.data); + this._type = classifyTransaction(txData.data, this._coinConfig.name); // reset arrays to empty to ensure that they are only set with one set of fresh values this._inputs = []; diff --git a/modules/abstract-eth/src/lib/transactionBuilder.ts b/modules/abstract-eth/src/lib/transactionBuilder.ts index 85c284fb6c..212856624b 100644 --- a/modules/abstract-eth/src/lib/transactionBuilder.ts +++ b/modules/abstract-eth/src/lib/transactionBuilder.ts @@ -183,7 +183,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { * @param {boolean} isFirstSigner if the transaction is being signed by the first signer */ protected loadBuilderInput(transactionJson: TxData, isFirstSigner?: boolean): void { - const decodedType = classifyTransaction(transactionJson.data); + const decodedType = classifyTransaction(transactionJson.data, this._coinConfig.name); this.type(decodedType); this.counter(transactionJson.nonce); this.value(transactionJson.value); diff --git a/modules/abstract-eth/src/lib/utils.ts b/modules/abstract-eth/src/lib/utils.ts index aaecc58522..9c24ad7656 100644 --- a/modules/abstract-eth/src/lib/utils.ts +++ b/modules/abstract-eth/src/lib/utils.ts @@ -664,7 +664,7 @@ export function decodeFlushTokensData(data: string, to?: string): FlushTokensDat * @param {string} data The data to classify the transaction with * @returns {TransactionType} The classified transaction type */ -export function classifyTransaction(data: string): TransactionType { +export function classifyTransaction(data: string, coinName?: string): TransactionType { if (data.length < 10) { // contract calls must have at least 4 bytes (method id) and '0x' // if it doesn't have enough data to be a contract call it must be a single sig send @@ -672,14 +672,27 @@ export function classifyTransaction(data: string): TransactionType { } // TODO(STLX-1970): validate if we are going to constraint to some methods allowed - let transactionType = transactionTypesMap[data.slice(0, 10).toLowerCase()]; - if (transactionType === undefined) { + const methodId = data.slice(0, 10).toLowerCase(); + const isCeloStaking = + CELO_STAKING_METHOD_IDS.has(methodId) && coinName && (coinName === 'celo' || coinName === 'tcelo'); + let transactionType = transactionTypesMap[methodId]; + + if ((!isCeloStaking && CELO_STAKING_METHOD_IDS.has(methodId)) || transactionType === undefined) { transactionType = TransactionType.ContractCall; } return transactionType; } +const CELO_STAKING_METHOD_IDS = new Set([ + LockMethodId, + VoteMethodId, + ActivateMethodId, + UnvoteMethodId, + UnlockMethodId, + WithdrawMethodId, +]); + /** * A transaction types map according to the starting part of the encoded data */ diff --git a/modules/abstract-eth/test/unit/utils.ts b/modules/abstract-eth/test/unit/utils.ts index 53ac91fe1b..9ca5963f4d 100644 --- a/modules/abstract-eth/test/unit/utils.ts +++ b/modules/abstract-eth/test/unit/utils.ts @@ -4,7 +4,9 @@ import { flushERC1155TokensData, decodeFlushERC721TokensData, decodeFlushERC1155TokensData, + classifyTransaction, } from '../../src/lib/utils'; +import { TransactionType } from '@bitgo/sdk-core'; describe('Abstract ETH Utils', () => { describe('ERC721 Flush Functions', () => { @@ -228,4 +230,20 @@ describe('Abstract ETH Utils', () => { decoded1155.tokenAddress.toLowerCase().should.equal(tokenAddressChecksum.toLowerCase()); }); }); + + describe('classifyTransaction', () => { + describe('CELO Staking Method ID Collision', () => { + const WITHDRAW_DATA = '0x2e1a7d4d0000000000000000000000000000000000000000000000000000000005f5e100'; + + it('should classify as StakingWithdraw on CELO chains', () => { + classifyTransaction(WITHDRAW_DATA, 'celo').should.equal(TransactionType.StakingWithdraw); + classifyTransaction(WITHDRAW_DATA, 'tcelo').should.equal(TransactionType.StakingWithdraw); + }); + + it('should classify as ContractCall on non-CELO chains', () => { + classifyTransaction(WITHDRAW_DATA, 'eth').should.equal(TransactionType.ContractCall); + classifyTransaction(WITHDRAW_DATA).should.equal(TransactionType.ContractCall); + }); + }); + }); });