Skip to content

Commit 0ae8dd0

Browse files
fix(sdk-coin-sol): add verification for Solana consolidation transactions
- Add `consolidationToBaseAddress` verification option to BaseCoin interface - Implement verification logic in Solana's verifyTransaction method to ensure consolidation transactions only send funds to the wallet's root address - Add verification step to wallet.sendAccountConsolidations to validate all transactions before signing - Add unit tests to verify consolidation transaction validation - Remove debug console.dir in error handling TICKET: WP-3968
1 parent 927f03a commit 0ae8dd0

File tree

2 files changed

+210
-9
lines changed

2 files changed

+210
-9
lines changed

modules/sdk-coin-sol/src/sol.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,17 @@ export class Sol extends BaseCoin {
232232
return Math.pow(10, this._staticsCoin.decimalPlaces);
233233
}
234234

235-
async verifyTransaction(params: SolVerifyTransactionOptions): Promise<any> {
235+
async verifyTransaction(params: SolVerifyTransactionOptions): Promise<boolean> {
236236
// asset name to transfer amount map
237237
const totalAmount: Record<string, BigNumber> = {};
238238
const coinConfig = coins.get(this.getChain());
239-
const { txParams: txParams, txPrebuild: txPrebuild, memo: memo, durableNonce: durableNonce } = params;
239+
const {
240+
txParams: txParams,
241+
txPrebuild: txPrebuild,
242+
memo: memo,
243+
durableNonce: durableNonce,
244+
verification: verificationOptions,
245+
} = params;
240246
const transaction = new Transaction(coinConfig);
241247
const rawTx = txPrebuild.txBase64 || txPrebuild.txHex;
242248
const consolidateId = txPrebuild.consolidateId;
@@ -312,6 +318,15 @@ export class Sol extends BaseCoin {
312318
if (recipientChecks.includes(false)) {
313319
throw new Error('Tx outputs does not match with expected txParams recipients');
314320
}
321+
} else if (verificationOptions?.consolidationToBaseAddress) {
322+
//verify funds are sent to walletRootAddress for a consolidation
323+
const filteredOutputs = explainedTx.outputs.map((output) => _.pick(output, ['address', 'amount', 'tokenName']));
324+
325+
for (const output of filteredOutputs) {
326+
if (output.address !== walletRootAddress) {
327+
throw new Error('tx outputs does not match with expected address');
328+
}
329+
}
315330
}
316331

317332
const transactionJson = transaction.toJson();

modules/sdk-coin-sol/test/unit/sol.ts

Lines changed: 193 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
import { BitGoAPI } from '@bitgo/sdk-api';
1+
import * as _ from 'lodash';
2+
import * as should from 'should';
3+
import * as sinon from 'sinon';
4+
import nock from 'nock';
5+
import assert from 'assert';
6+
7+
import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
8+
9+
import { BitGoAPI, encrypt } from '@bitgo/sdk-api';
210
import {
11+
common,
12+
Environments,
313
generateRandomPassword,
14+
IWallet,
415
MPCSweepTxs,
516
MPCTx,
617
MPCTxs,
18+
TransactionPrebuild,
719
TssUtils,
820
TxRequest,
921
Wallet,
10-
Environments,
22+
WalletCoinSpecific,
1123
} from '@bitgo/sdk-core';
1224
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
1325
import { coins } from '@bitgo/statics';
14-
import assert from 'assert';
15-
import * as _ from 'lodash';
16-
import * as should from 'should';
17-
import * as sinon from 'sinon';
1826
import { KeyPair, Sol, Tsol } from '../../src';
1927
import { Transaction } from '../../src/lib';
2028
import { AtaInit, InstructionParams, TokenTransfer } from '../../src/lib/iface';
@@ -23,7 +31,6 @@ import * as testData from '../fixtures/sol';
2331
import * as resources from '../resources/sol';
2432
import { getBuilderFactory } from './getBuilderFactory';
2533
import { solBackupKey } from './fixtures/solBackupKey';
26-
import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
2734

2835
describe('SOL:', function () {
2936
let bitgo: TestBitGoAPI;
@@ -2834,5 +2841,184 @@ describe('SOL:', function () {
28342841
}
28352842
);
28362843
});
2844+
2845+
it('should verify consolidation transaction', async function () {
2846+
// Set up wallet data
2847+
const walletData = {
2848+
id: '5b34252f1bf349930e34020a00000000',
2849+
coin: 'tsol',
2850+
keys: [
2851+
'5b3424f91bf349930e34017500000000',
2852+
'5b3424f91bf349930e34017600000000',
2853+
'5b3424f91bf349930e34017700000000',
2854+
],
2855+
coinSpecific: {
2856+
rootAddress: wallet.pub,
2857+
},
2858+
multisigType: 'tss',
2859+
};
2860+
const fakePrv = encrypt('password', 'prv');
2861+
2862+
const walletObj = new Wallet(bitgo, basecoin, walletData);
2863+
const bgUrl = common.Environments['mock'].uri;
2864+
2865+
nock(bgUrl)
2866+
.get('/api/v2/tsol/key/5b3424f91bf349930e34017500000000')
2867+
.reply(200, [
2868+
{
2869+
encryptedPrv: fakePrv,
2870+
},
2871+
]);
2872+
2873+
// Mock the API response for buildAccountConsolidations
2874+
nock(bgUrl)
2875+
.post('/api/v2/tsol/wallet/5b34252f1bf349930e34020a00000000/consolidateAccount/build')
2876+
.reply(200, [
2877+
{
2878+
txRequestId: '4fdd0cae-2563-43b1-b5cf-94865158ca10',
2879+
walletId: '63068ed4efa63a000877f02fd4b0fa6d',
2880+
txHex:
2881+
'02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f50130abf8d0d8943c5b9a51a574886a7d7b3d8db18f0ddb4ab8b6d3ec27e3c2f36f3339bb92d4296af6ae4d3abfbb07877f77d0033c883de08fa4a2eea670d0201020674a9df2b94aa4b4ada1202dc2891be366501d0acb4a01ca3e02e7fd6c1f505a71c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f8401c3f67cfa52518b34a09b08f4ea77e1c4fb9d89bfaccdc33cf8b8a9cf8d7bf0e04c89c50428e4eda5cbb759427c370f0a29a50bb0d1407e57924b0cc5b36f000000000000000000000000000000000000000000000000000000000000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000040c9568195d67eb6b396fdbe97ba2e276622ef0023af09f23e6e0292abb678d90204030305010404000000040200020c020000000100000000000000',
2882+
feeInfo: {
2883+
fee: 10000,
2884+
feeString: '10000',
2885+
},
2886+
txInfo: {
2887+
minerFee: '10000',
2888+
spendAmount: '1547864',
2889+
spendAmounts: [
2890+
{
2891+
coinName: 'tsol',
2892+
amountString: '1547864',
2893+
},
2894+
],
2895+
payGoFee: '0',
2896+
outputs: [
2897+
{
2898+
address: '8rQXeVEMrKvtWCEJirEM6cKYnbZuTqVTbqRPiMMAJ8R4',
2899+
value: 1547864,
2900+
wallet: '63068ed4efa63a000877f02f',
2901+
wallets: ['63068ed4efa63a000877f02f'],
2902+
enterprise: '62d71a6b86068f0008f029fd',
2903+
enterprises: ['62d71a6b86068f0008f029fd'],
2904+
valueString: '1547864',
2905+
coinName: 'tsol',
2906+
walletType: 'hot',
2907+
walletTypes: ['hot'],
2908+
},
2909+
],
2910+
inputs: [
2911+
{
2912+
value: 1547864,
2913+
address: 'CmYsN3f8bcm4BDkFJWNsvYgjRxMTLH6vbJWNfYdmH7GU',
2914+
valueString: '1547864',
2915+
},
2916+
{
2917+
value: 10000,
2918+
address: 'CmYsN3f8bcm4BDkFJWNsvYgjRxMTLH6vbJWNfYdmH7GU',
2919+
valueString: '10000',
2920+
},
2921+
],
2922+
type: 'Send',
2923+
},
2924+
consolidateId: '68a7d5d0c66e74e216b97173bd558c6d',
2925+
coin: 'tsol',
2926+
},
2927+
]);
2928+
2929+
// Call the function to test
2930+
await assert.rejects(
2931+
async () => {
2932+
await walletObj.sendAccountConsolidations({
2933+
walletPassphrase: 'password',
2934+
});
2935+
},
2936+
{
2937+
message: 'tx outputs does not match with expected address',
2938+
}
2939+
);
2940+
});
2941+
2942+
it('should verify valid a consolidation transaction', async () => {
2943+
const consolidationTx = {
2944+
txRequestId: '4fdd0cae-2563-43b1-b5cf-94865158ca10',
2945+
walletId: '63068ed4efa63a000877f02fd4b0fa6d',
2946+
txHex:
2947+
'02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002010206aeda253a331de489838246df93879440af8c62ac4967658edc2bb5d52b9759d91c96172044f1217c3784e8f02f49e2c8fc3591e81294ab54394f9d22fd7b7a8f74a9df2b94aa4b4ada1202dc2891be366501d0acb4a01ca3e02e7fd6c1f505a7d2734f3952f3eb4aefcf6c7a6092e979dd3fe5563ccfaca1cc92652a15ddd393000000000000000000000000000000000000000000000000000000000000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000428dad41bbedeb38018379e4ceeb7e80757d74dd00ae8c47c38d597477339ad80204030305010404000000040200020c02000000589e170000000000',
2948+
feeInfo: {
2949+
fee: 10000,
2950+
feeString: '10000',
2951+
},
2952+
txInfo: {
2953+
minerFee: '10000',
2954+
spendAmount: '1547864',
2955+
spendAmounts: [
2956+
{
2957+
coinName: 'tsol',
2958+
amountString: '1547864',
2959+
},
2960+
],
2961+
payGoFee: '0',
2962+
outputs: [
2963+
{
2964+
address: '8rQXeVEMrKvtWCEJirEM6cKYnbZuTqVTbqRPiMMAJ8R4',
2965+
value: 1547864,
2966+
wallet: '63068ed4efa63a000877f02f',
2967+
wallets: ['63068ed4efa63a000877f02f'],
2968+
enterprise: '62d71a6b86068f0008f029fd',
2969+
enterprises: ['62d71a6b86068f0008f029fd'],
2970+
valueString: '1547864',
2971+
coinName: 'tsol',
2972+
walletType: 'hot',
2973+
walletTypes: ['hot'],
2974+
},
2975+
],
2976+
inputs: [
2977+
{
2978+
value: 1547864,
2979+
address: 'CmYsN3f8bcm4BDkFJWNsvYgjRxMTLH6vbJWNfYdmH7GU',
2980+
valueString: '1547864',
2981+
},
2982+
{
2983+
value: 10000,
2984+
address: 'CmYsN3f8bcm4BDkFJWNsvYgjRxMTLH6vbJWNfYdmH7GU',
2985+
valueString: '10000',
2986+
},
2987+
],
2988+
type: 'Send',
2989+
},
2990+
consolidateId: '68a7d5d0c66e74e216b97173bd558c6d',
2991+
coin: 'tsol',
2992+
};
2993+
2994+
const mockedWallet: Partial<IWallet> = {
2995+
coinSpecific: () => {
2996+
const cs = {
2997+
rootAddress: '8rQXeVEMrKvtWCEJirEM6cKYnbZuTqVTbqRPiMMAJ8R4',
2998+
} as WalletCoinSpecific;
2999+
return cs;
3000+
},
3001+
};
3002+
3003+
try {
3004+
if (
3005+
!(await basecoin.verifyTransaction({
3006+
blockhash: '',
3007+
feePayer: '',
3008+
txParams: {},
3009+
txPrebuild: consolidationTx as unknown as TransactionPrebuild,
3010+
walletType: 'tss',
3011+
wallet: mockedWallet as IWallet,
3012+
verification: {
3013+
consolidationToBaseAddress: true,
3014+
},
3015+
}))
3016+
) {
3017+
assert.fail('Transaction should pass verification');
3018+
}
3019+
} catch (e) {
3020+
assert.fail('Transaction should pass verification');
3021+
}
3022+
});
28373023
});
28383024
});

0 commit comments

Comments
 (0)