Skip to content

Commit fd47129

Browse files
committed
feat(sdk-coin-sol): big endian support for verifyTransaction
TICKET: WP-6284
1 parent 8361510 commit fd47129

File tree

2 files changed

+148
-3
lines changed

2 files changed

+148
-3
lines changed

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

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,41 @@ export interface SolConsolidationRecoveryOptions extends MPCConsolidationRecover
178178
const HEX_REGEX = /^[0-9a-fA-F]+$/;
179179
const BLIND_SIGNING_TX_TYPES_TO_CHECK = { enabletoken: 'AssociatedTokenAccountInitialization' };
180180

181+
/**
182+
* Get amount string corrected for architecture-specific endianness issues.
183+
*
184+
* On s390x (big-endian) architecture, the Solana transaction parser (via @solana/web3.js)
185+
* incorrectly reads little-endian u64 amounts as big-endian, resulting in corrupted values.
186+
*
187+
* This function corrects all amounts on s390x by swapping byte order to undo
188+
* the incorrect byte order that happened during transaction parsing.
189+
*
190+
* @param amount - The amount to check and potentially fix
191+
* @returns The corrected amount as a string
192+
*/
193+
function getAmountBasedOnEndianness(amount: string | number): string {
194+
const amountStr = String(amount);
195+
196+
// Only s390x architecture has this endianness issue
197+
const isS390x = process.arch === 's390x';
198+
if (!isS390x) {
199+
return amountStr;
200+
}
201+
202+
try {
203+
const amountBN = BigInt(amountStr);
204+
// On s390x, the parser ALWAYS reads u64 as big-endian when it's actually little-endian
205+
// So we ALWAYS need to swap bytes to get the correct value
206+
const buf = Buffer.alloc(8);
207+
buf.writeBigUInt64BE(amountBN, 0);
208+
const fixed = buf.readBigUInt64LE(0);
209+
return fixed.toString();
210+
} catch (e) {
211+
// If conversion fails, return original value
212+
return amountStr;
213+
}
214+
}
215+
181216
export class Sol extends BaseCoin {
182217
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
183218

@@ -371,8 +406,12 @@ export class Sol extends BaseCoin {
371406
const recipientFromTx = filteredOutputs[index]; // This address should be an ATA
372407

373408
// Compare the BigNumber values because amount is (string | number)
374-
const userAmount = new BigNumber(recipientFromUser.amount);
375-
const txAmount = new BigNumber(recipientFromTx.amount);
409+
// Apply s390x endianness fix if needed
410+
const userAmountStr = String(recipientFromUser.amount);
411+
const txAmountStr = getAmountBasedOnEndianness(recipientFromTx.amount);
412+
413+
const userAmount = new BigNumber(userAmountStr);
414+
const txAmount = new BigNumber(txAmountStr);
376415
if (!userAmount.isEqualTo(txAmount)) {
377416
return false;
378417
}
@@ -459,10 +498,13 @@ export class Sol extends BaseCoin {
459498
const explainedTxTotal: Record<string, BigNumber> = {};
460499

461500
for (const output of explainedTx.outputs) {
501+
// Apply s390x endianness fix to output amounts before summing
502+
const outputAmountStr = getAmountBasedOnEndianness(output.amount);
503+
462504
// total output amount based on each token
463505
const assetName = output.tokenName || this.getChain();
464506
const amount = explainedTxTotal[assetName] || new BigNumber(0);
465-
explainedTxTotal[assetName] = amount.plus(output.amount);
507+
explainedTxTotal[assetName] = amount.plus(outputAmountStr);
466508
}
467509

468510
if (!_.isEqual(explainedTxTotal, totalAmount)) {

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,109 @@ describe('SOL:', function () {
599599
} as any);
600600
validTransaction.should.equal(true);
601601
});
602+
603+
it('should handle normal amounts on non-s390x architecture', async function () {
604+
const originalArch = process.arch;
605+
try {
606+
Object.defineProperty(process, 'arch', {
607+
value: 'x64',
608+
writable: true,
609+
configurable: true,
610+
});
611+
612+
const txParams = newTxParams();
613+
const txPrebuild = newTxPrebuild();
614+
const validTransaction = await basecoin.verifyTransaction({
615+
txParams,
616+
txPrebuild,
617+
memo,
618+
durableNonce,
619+
wallet: walletObj,
620+
} as any);
621+
622+
validTransaction.should.equal(true);
623+
} finally {
624+
Object.defineProperty(process, 'arch', {
625+
value: originalArch,
626+
writable: true,
627+
configurable: true,
628+
});
629+
}
630+
});
631+
632+
it('should byte-swap small amounts on s390x', async function () {
633+
const originalArch = process.arch;
634+
try {
635+
Object.defineProperty(process, 'arch', {
636+
value: 's390x',
637+
writable: true,
638+
configurable: true,
639+
});
640+
641+
const txParams = {
642+
txPrebuild: newTxPrebuild(),
643+
recipients: [
644+
{
645+
address: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
646+
amount: '10000', // Small amount that would be corrupted to huge value
647+
},
648+
],
649+
};
650+
const txPrebuild = newTxPrebuild();
651+
const validTransaction = await basecoin.verifyTransaction({
652+
txParams,
653+
txPrebuild,
654+
memo,
655+
durableNonce,
656+
wallet: walletObj,
657+
} as any);
658+
659+
validTransaction.should.equal(true);
660+
} finally {
661+
Object.defineProperty(process, 'arch', {
662+
value: originalArch,
663+
writable: true,
664+
configurable: true,
665+
});
666+
}
667+
});
668+
669+
it('should byte-swap large amounts on s390x', async function () {
670+
const originalArch = process.arch;
671+
try {
672+
Object.defineProperty(process, 'arch', {
673+
value: 's390x',
674+
writable: true,
675+
configurable: true,
676+
});
677+
678+
const txParams = {
679+
txPrebuild: newTxPrebuild(),
680+
recipients: [
681+
{
682+
address: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
683+
amount: '504403158265495552', // Large amount (~5×10^17) that would be corrupted to 7
684+
},
685+
],
686+
};
687+
const txPrebuild = newTxPrebuild();
688+
const validTransaction = await basecoin.verifyTransaction({
689+
txParams,
690+
txPrebuild,
691+
memo,
692+
durableNonce,
693+
wallet: walletObj,
694+
} as any);
695+
696+
validTransaction.should.equal(true);
697+
} finally {
698+
Object.defineProperty(process, 'arch', {
699+
value: originalArch,
700+
writable: true,
701+
configurable: true,
702+
});
703+
}
704+
});
602705
});
603706

604707
it('should accept valid address', function () {

0 commit comments

Comments
 (0)