Skip to content

Commit e7c9782

Browse files
committed
Fix zorsh serialization with BigInt conversion and option handling
- Add ensureBigInt() helper to convert numbers to BigInt for u128/u64 fields - Convert undefined to null for zorsh option types - Preserve prototype chain when converting class instances - Update all encoding functions to use ensureBigInt() - Fix tests to use encodeTransaction() and handle Uint8Array correctly - Remove unused Test class from tests
1 parent df68e3b commit e7c9782

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
lines changed

src/transactions/schema.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@ import type { DelegateAction } from './delegate.js';
99
import { DelegateActionPrefix } from './prefix.js';
1010
import type { Signature } from './signature.js';
1111

12+
/**
13+
* Recursively converts numeric values to BigInt for u128/u64 fields in objects
14+
* and undefined to null for option types to ensure compatibility with zorsh serialization
15+
*/
16+
function ensureBigInt(obj: any): any {
17+
if (obj === null || obj === undefined) return obj;
18+
if (typeof obj === 'number') return BigInt(obj);
19+
if (typeof obj === 'bigint') return obj;
20+
if (obj instanceof Uint8Array) return obj;
21+
if (Array.isArray(obj)) return obj.map(ensureBigInt);
22+
if (typeof obj === 'object') {
23+
// Preserve the prototype chain for class instances
24+
const result: any = Object.create(Object.getPrototypeOf(obj));
25+
for (const [key, value] of Object.entries(obj)) {
26+
// Convert numeric fields that should be BigInt
27+
if (['deposit', 'stake', 'gas', 'nonce', 'allowance', 'maxBlockHeight'].includes(key)) {
28+
if (value === null || value === undefined) {
29+
// zorsh's option type requires null, not undefined
30+
result[key] = value === undefined ? null : value;
31+
} else if (typeof value === 'number' || typeof value === 'bigint') {
32+
result[key] = BigInt(value);
33+
} else {
34+
result[key] = value;
35+
}
36+
} else {
37+
result[key] = ensureBigInt(value);
38+
}
39+
}
40+
return result;
41+
}
42+
return obj;
43+
}
44+
1245
/**
1346
* Borsh-encode a delegate action for inclusion as an action within a meta transaction
1447
* NB per NEP-461 this requires a Borsh-serialized prefix specific to delegate actions, ensuring
@@ -18,7 +51,7 @@ import type { Signature } from './signature.js';
1851
export function encodeDelegateAction(delegateAction: DelegateAction) {
1952
return new Uint8Array([
2053
...SCHEMA.DelegateActionPrefix.serialize(new DelegateActionPrefix()),
21-
...SCHEMA.DelegateAction.serialize(delegateAction as any),
54+
...SCHEMA.DelegateAction.serialize(ensureBigInt(delegateAction)),
2255
]);
2356
}
2457

@@ -27,7 +60,7 @@ export function encodeDelegateAction(delegateAction: DelegateAction) {
2760
* @param signedDelegate Signed delegate to be executed in a meta transaction
2861
*/
2962
export function encodeSignedDelegate(signedDelegate: SignedDelegate) {
30-
return SCHEMA.SignedDelegate.serialize(signedDelegate as any);
63+
return SCHEMA.SignedDelegate.serialize(ensureBigInt(signedDelegate));
3164
}
3265

3366
/**
@@ -37,7 +70,7 @@ export function encodeSignedDelegate(signedDelegate: SignedDelegate) {
3770
*/
3871
export function encodeTransaction(transaction: Transaction | SignedTransaction) {
3972
const schema = 'signature' in transaction ? SCHEMA.SignedTransaction : SCHEMA.Transaction;
40-
return schema.serialize(transaction as any);
73+
return schema.serialize(ensureBigInt(transaction));
4174
}
4275

4376
/**

test/transactions/serialize.test.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ const {
3131
useGlobalContract
3232
} = actionCreators;
3333

34-
class Test {
35-
constructor(props: any) {
36-
for (const [k, v] of Object.entries(props || {})) {
37-
this[k] = v;
38-
}
39-
}
40-
}
41-
4234
test('serialize object', async () => {
4335
const value = { x: 255, y: 20, z: '123', q: Uint8Array.from([1, 2, 3]) };
4436
const schema = b.struct({ x: b.u8(), y: b.u16(), z: b.string(), q: b.bytes() });
@@ -75,7 +67,7 @@ test('serialize multi-action tx', async () => {
7567
const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM');
7668
const transaction = createTransaction('test.near', publicKey, '123', 1n, actions, blockHash);
7769
// expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y');
78-
const serialized = Buffer.from(SCHEMA.Transaction.serialize(transaction as any));
70+
const serialized = Buffer.from(encodeTransaction(transaction));
7971
expect(serialized.toString('hex')).toEqual('09000000746573742e6e656172000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80100000000000000030000003132330fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef608000000000103000000010203020300000071717103000000010203e80300000000000040420f00000000000000000000000000037b0000000000000000000000000000000440420f00000000000000000000000000000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef805000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef800000000000000000000030000007a7a7a010000000300000077777706000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80703000000313233');
8072
});
8173

@@ -231,7 +223,7 @@ describe('Global Contract Actions Serialization', () => {
231223
const deserializedAction = deserializedTx.actions[0].useGlobalContract;
232224
expect(deserializedAction).toBeDefined();
233225
if (deserializedAction) { // Type guard
234-
expect(deserializedAction.contractIdentifier).toEqual({ CodeHash: Array.from(sampleCodeHash) });
226+
expect(deserializedAction.contractIdentifier.CodeHash).toEqual(sampleCodeHash);
235227
}
236228
});
237229

0 commit comments

Comments
 (0)