eth-compress 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/jit-compressor.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { LibZip } from 'solady';
2
+ import { MIN_BODY_SIZE } from './index';
2
3
 
3
- const MAX_160_BIT = (1n << 128n) - 1n;
4
+ const MAX_128_BIT = (1n << 128n) - 1n;
5
+ const MAX_256_BIT = (1n << 256n) - 1n;
4
6
 
5
7
  const _normHex = (hex: string): string => hex.replace(/^0x/, '').toLowerCase();
6
8
 
@@ -22,6 +24,22 @@ const _uint8ArrayToHex = (bytes: Uint8Array): string => {
22
24
  return hex;
23
25
  };
24
26
 
27
+ const not = (a: bigint): bigint => ~a & MAX_256_BIT;
28
+ const and = (a: bigint, b: bigint): bigint => a & b & MAX_256_BIT;
29
+ const or = (a: bigint, b: bigint): bigint => (a | b) & MAX_256_BIT;
30
+ const xor = (a: bigint, b: bigint): bigint => (a ^ b) & MAX_256_BIT;
31
+ const shl = (shift: bigint, value: bigint): bigint => (value << shift) & MAX_256_BIT;
32
+ const shr = (shift: bigint, value: bigint): bigint => (value >> shift) & MAX_256_BIT;
33
+ const sub = (a: bigint, b: bigint): bigint => (a - b) & MAX_256_BIT;
34
+ const sigext = (byteSize: bigint, value: bigint): bigint => {
35
+ const numBytes = Number(byteSize) + 1;
36
+ const mask = (1n << BigInt(numBytes * 8)) - 1n;
37
+ const signBit = 1n << BigInt(numBytes * 8 - 1);
38
+ const maskedVal = value & mask;
39
+ const extended = maskedVal & signBit ? maskedVal | (~mask & MAX_256_BIT) : maskedVal;
40
+ return extended & MAX_256_BIT;
41
+ };
42
+
25
43
  /**
26
44
  * Generates FastLZ (LZ77) decompressor bytecode. The generated code decompresses incoming calldata and forwards it to the target address.
27
45
  * @param address - Target contract address
@@ -29,7 +47,7 @@ const _uint8ArrayToHex = (bytes: Uint8Array): string => {
29
47
  * @pure
30
48
  */
31
49
  //! @__PURE__
32
- export const flzFwdBytecode = (address: string): string =>
50
+ const flzFwdBytecode = (address: string): string =>
33
51
  `0x365f73${_normHex(address)}815b838110602f575f80848134865af1503d5f803e3d5ff35b803590815f1a8060051c908115609857600190600783149285831a6007018118840218600201948383011a90601f1660081b0101808603906020811860208211021890815f5b80830151818a015201858110609257505050600201019201916018565b82906075565b6001929350829150019101925f5b82811060b3575001916018565b85851060c1575b60010160a6565b936001818192355f1a878501530194905060ba56`;
34
52
 
35
53
  /**
@@ -39,19 +57,9 @@ export const flzFwdBytecode = (address: string): string =>
39
57
  * @pure
40
58
  */
41
59
  //! @__PURE__
42
- export const rleFwdBytecode = (address: string): string =>
60
+ const rleFwdBytecode = (address: string): string =>
43
61
  `0x5f5f5b368110602d575f8083813473${_normHex(address)}5af1503d5f803e3d5ff35b600180820192909160031981019035185f1a8015604c57815301906002565b505f19815282820192607f9060031981019035185f1a818111156072575b160101906002565b838101368437606a56`;
44
62
 
45
- /**
46
- * JIT Compiles decompressor bytecode
47
- * @param calldata - Calldata to compress
48
- * @pure
49
- */
50
- //! @__PURE__
51
- export const jitBytecode = function (calldata: string): string {
52
- return _jitDecompressor('0x' + _normHex(calldata));
53
- };
54
-
55
63
  const _jitDecompressor = function (calldata: string): string {
56
64
  const hex = _normHex(calldata);
57
65
  const originalBuf = _hexToUint8Array(hex);
@@ -72,9 +80,11 @@ const _jitDecompressor = function (calldata: string): string {
72
80
  let stack: bigint[] = [];
73
81
  let trackedMemSize = 0;
74
82
  let mem = new Map<number, bigint>();
83
+ let firstPass = true;
75
84
  const getStackIdx = (val: bigint): number => {
76
- const idx = stack.lastIndexOf(val);
77
- return idx === -1 ? -1 : stack.length - 1 - idx;
85
+ let idx = stack.lastIndexOf(val);
86
+ idx = idx === -1 ? -1 : stack.length - 1 - idx;
87
+ return idx > 15 ? -1 : idx;
78
88
  };
79
89
 
80
90
  const opFreq = new Map<number, number>();
@@ -86,20 +96,15 @@ const _jitDecompressor = function (calldata: string): string {
86
96
 
87
97
  let pushCounter = 0;
88
98
  const stackCnt = new Map<bigint, number>();
89
-
90
99
  const pop2 = (): [bigint, bigint] => [stack.pop()!, stack.pop()!];
91
- const MASK32 = (1n << 256n) - 1n;
92
-
93
100
  const ctr = <K>(m: Map<K, number>, k: K, delta: number) => m.set(k, (m.get(k) || 0) + delta);
94
101
  const inc = <K>(m: Map<K, number>, k: K) => ctr(m, k, 1);
95
- const dec = <K>(m: Map<K, number>, k: K) => ctr(m, k, -1);
96
- const pushOp = (op: number) => {
102
+ const pushOp = (op: number, d?: number[] | null) => {
97
103
  ops.push(op);
98
104
  inc(opFreq, op);
99
- };
100
- const pushD = (d: number[] | null) => {
101
- data.push(d || null);
102
- inc(dataFreq, d || null);
105
+ const imm = d ?? null;
106
+ data.push(imm);
107
+ inc(dataFreq, imm);
103
108
  };
104
109
  const pushS = (v: bigint, freq: number = 1) => {
105
110
  stack.push(v);
@@ -117,57 +122,68 @@ const _jitDecompressor = function (calldata: string): string {
117
122
  pushS(32n, 0);
118
123
  } else if (op === 0x59) {
119
124
  pushS(BigInt(trackedMemSize), 0);
125
+ } else if (op === 0x0b) {
126
+ // SIGNEXTEND
127
+ const [byteSize, val] = pop2();
128
+ pushS(sigext(byteSize, val), 1);
129
+ } else if (op === 0x19) {
130
+ // NOT
131
+ const val = stack.pop()!;
132
+ pushS(not(val), 0);
133
+ } else if (op === 0x18) {
134
+ // XOR
135
+ const [a, b] = pop2();
136
+ pushS(xor(a, b), 1);
137
+ } else if (op === 0x16) {
138
+ // AND
139
+ const [a, b] = pop2();
140
+ pushS(and(a, b), 1);
141
+ } else if (op === 0x03) {
142
+ // SUB
143
+ const [a, b] = pop2();
144
+ pushS(sub(a, b), 1);
120
145
  } else if (op === 0x1b) {
146
+ // SHL
121
147
  let [shift, val] = pop2();
122
- if (ops[ops.length - 1] == 144) {
123
- ops.pop();
124
- data.pop();
125
- [shift, val] = [val, shift];
126
- }
127
- pushS((val << BigInt(shift)) & MASK32);
148
+ pushS(shl(shift, val), 1);
149
+ } else if (op === 0x1c) {
150
+ // SHR
151
+ let [shift, val] = pop2();
152
+ pushS(shr(shift, val), 1);
128
153
  } else if (op === 0x17) {
129
154
  // OR
130
- const [a, b] = pop2();
131
- if (ops[ops.length - 1] == 144) {
132
- ops.pop();
133
- data.pop();
134
- }
135
- pushS((a | b) & MASK32);
155
+ let [a, b] = pop2();
156
+ pushS(or(a, b), 1);
136
157
  } else if ((op >= 0x60 && op <= 0x7f) || op === 0x5f) {
137
158
  // PUSH
138
159
  let v = 0n;
139
160
  for (const b of imm || []) v = (v << 8n) | BigInt(b);
140
161
  if (v == 224n) {
141
- // Special‑case the literal 0xe0 (224):
142
- // the decompressor is always deployed at 0x...00e0, so the final
143
- // byte of ADDRESS is exactly 0xe0. Since we must send our own
144
- // address with the eth_call anyway, we can synthesize this value
145
- // with a single opcode instead of encoding a literal, effectively
146
- // giving us one more hot constant slot on the stack.
147
- pushS(v);
162
+ pushS(v, 0);
148
163
  pushOp(0x30); // ADDRESS
149
- pushD(null);
164
+ return;
165
+ }
166
+ if (v == 32n) {
167
+ pushS(v, 0);
168
+ pushOp(0x36); // ADDRESS
169
+ return;
170
+ }
171
+ if (v === BigInt(trackedMemSize)) {
172
+ pushS(v, 0);
173
+ pushOp(0x59); // MemSize
150
174
  return;
151
175
  }
152
176
  const idx = getStackIdx(v);
153
177
  if (idx !== -1 && op != 0x5f) {
154
- const last = stackFreq.get(v) == 0;
155
- if (idx == 0 && last) {
156
- dec(stackFreq, v);
157
- return;
158
- }
159
- if (idx == 1 && last) {
160
- pushOp(144);
161
- const [a, b] = pop2();
162
- stack.push(b);
163
- stack.push(a);
164
- pushD(null);
165
- dec(stackFreq, v);
166
- return;
167
- }
168
- pushS(v, -1);
178
+ let pushCtr = firstPass ? 1 : -1;
179
+ pushS(v, pushCtr);
169
180
  pushOp(128 + idx);
170
- pushD(null);
181
+ return;
182
+ }
183
+ if (v == MAX_256_BIT) {
184
+ pushS(v);
185
+ pushOp(0x5f); // 0
186
+ pushOp(0x19); // NOT
171
187
  return;
172
188
  }
173
189
  pushS(v);
@@ -179,7 +195,7 @@ const _jitDecompressor = function (calldata: string): string {
179
195
  // MSTORE
180
196
  const [offset, value] = pop2();
181
197
  const k = Number(offset);
182
- mem.set(k, value & MASK32);
198
+ mem.set(k, value & MAX_256_BIT);
183
199
  trackMem(k, 32);
184
200
  } else if (op === 0x53) {
185
201
  // MSTORE8
@@ -189,10 +205,8 @@ const _jitDecompressor = function (calldata: string): string {
189
205
  // RETURN
190
206
  pop2();
191
207
  }
192
- pushOp(op);
193
- pushD(imm || null);
208
+ pushOp(op, imm || null);
194
209
  };
195
- const isInStack = (w) => stack.includes(w) || w == 0xe0 || w == 32n;
196
210
  const op = (opcode: number) => addOp(opcode);
197
211
  const pushN = (value: number | bigint) => {
198
212
  if (value > 0 && value === trackedMemSize) return addOp(0x59);
@@ -234,6 +248,7 @@ const _jitDecompressor = function (calldata: string): string {
234
248
  const emitOp = (o: number) => (plan.push({ t: 'op', o }), op(o));
235
249
  pushN(1n);
236
250
  // First pass: decide how to build each 32-byte word without emitting bytecode
251
+ const _stack = [1n, 32n, 224n];
237
252
  for (let base = 0; base < n; base += 32) {
238
253
  const word = new Uint8Array(32);
239
254
  word.set(buf.slice(base, Math.min(base + 32, n)), 0);
@@ -256,6 +271,15 @@ const _jitDecompressor = function (calldata: string): string {
256
271
  for (const b of literal) literalVal = (literalVal << 8n) | BigInt(b);
257
272
  const baseBytes = Math.ceil(Math.log2(base + 1) / 8);
258
273
  const wordHex = _uint8ArrayToHex(word);
274
+ const shlCost = estShlCost(seg);
275
+
276
+ const inStack = _stack.includes(literalVal);
277
+ if (inStack) {
278
+ emitPushB(literal);
279
+ emitPushN(base);
280
+ emitOp(0x52); // MSTORE
281
+ continue;
282
+ }
259
283
  if (literalCost > 8) {
260
284
  if (wordCache.has(wordHex)) {
261
285
  if (literalCost > wordCacheCost.get(wordHex)! + baseBytes) {
@@ -273,31 +297,134 @@ const _jitDecompressor = function (calldata: string): string {
273
297
  }
274
298
  }
275
299
 
276
- // Convert literal bytes to bigint for stack comparison
277
-
278
300
  const byte8s = seg.every(({ s, e }) => s === e);
279
- if (isInStack(literal)) {
280
- emitPushB(literal);
281
- } else if (byte8s) {
282
- for (const { s } of seg) {
283
- emitPushN(word[s]);
284
- emitPushN(base + s);
285
- emitOp(0x53); // MSTORE8
286
- }
287
- continue;
288
- } else if (literalCost <= estShlCost(seg)) {
301
+ const byte8sCost = seg.length * 3; // each: PUSH1 (value), PUSH1 (offset), MSTORE8
302
+ if (inStack) {
289
303
  emitPushB(literal);
290
304
  } else {
291
- let first = true;
292
- for (const { s, e } of seg) {
293
- const suffix0s = 31 - e;
294
- emitPushB(word.slice(s, e + 1));
295
- if (suffix0s > 0) {
296
- emitPushN(suffix0s * 8);
297
- emitOp(0x1b); // SHL
305
+ // Aggregate all costs
306
+ let bestCost = literalCost;
307
+ let bestEmit: (() => void) | null = () => {
308
+ emitPushB(literal);
309
+ };
310
+ if (literalVal == MAX_256_BIT) {
311
+ bestCost = 2;
312
+ bestEmit = () => {
313
+ emitPushN(notVal);
314
+ emitOp(0x19);
315
+ };
316
+ }
317
+ // Try NOT: PUSH<n> ~val, NOT
318
+ const notVal = not(literalVal);
319
+ let notBytes = 0;
320
+ let tmp = notVal;
321
+ while (tmp > 0n) {
322
+ ++notBytes;
323
+ tmp >>= 8n;
324
+ }
325
+ notBytes = 1 + notBytes;
326
+ if (notBytes === 0) notBytes;
327
+ const notCost = notBytes + 1; // PUSH<n> + NOT
328
+ if (notCost < bestCost) {
329
+ bestCost = notCost;
330
+ bestEmit = () => {
331
+ emitPushN(notVal);
332
+ emitOp(0x19);
333
+ };
334
+ }
335
+
336
+ // Try SUB: PUSH1 0, PUSH<n> val, SUB
337
+ const subVal = sub(0n, literalVal);
338
+ let subBytes = 0;
339
+ tmp = subVal;
340
+ while (tmp > 0n) {
341
+ ++subBytes;
342
+ tmp >>= 8n;
343
+ }
344
+ if (subBytes === 0) subBytes = 1;
345
+ if (_stack.includes(subVal)) subBytes = 1;
346
+ const subCost = 1 + (1 + subBytes) + 1; // PUSH0 + PUSH<n> + SUB
347
+ if (subCost < bestCost) {
348
+ bestCost = subCost;
349
+ bestEmit = () => {
350
+ emitPushN(0);
351
+ emitPushN(subVal);
352
+ emitOp(0x03);
353
+ };
354
+ }
355
+
356
+ // Try SIGNEXTEND: PUSH<n> truncated, PUSH1 byteSize, SIGNEXTEND
357
+ for (let numBytes = 1; numBytes < literal.length; numBytes++) {
358
+ const mask = (1n << BigInt(numBytes * 8)) - 1n;
359
+ const truncated = literalVal & mask;
360
+ const extended = sigext(BigInt(numBytes - 1), truncated);
361
+ if (extended === literalVal && (truncated & (1n << BigInt(numBytes * 8 - 1))) !== 0n) {
362
+ let trueByteCost = 1 + numBytes;
363
+ if (_stack.includes(BigInt(extended))) trueByteCost = 1;
364
+ let signCost = trueByteCost + (1 + 1) + 1; // PUSH<n> + PUSH1 + SIGNEXTEND
365
+ if (signCost < bestCost) {
366
+ bestCost = signCost;
367
+ bestEmit = () => {
368
+ emitPushN(truncated);
369
+ emitPushN(numBytes - 1);
370
+ emitOp(0x0b);
371
+ };
372
+ }
373
+ break;
298
374
  }
299
- if (!first) emitOp(0x17); // OR
300
- first = false;
375
+ }
376
+
377
+ // Try SHIFT+NOT: PUSH<n> val, PUSH1 shift, SHL, NOT
378
+ for (let shiftBits = 8; shiftBits <= 248; shiftBits += 8) {
379
+ const shifted = shr(BigInt(shiftBits), literalVal);
380
+ if (shifted === 0n) break;
381
+
382
+ const notShifted = not(shifted);
383
+ const reconstructed = shl(BigInt(shiftBits), notShifted);
384
+
385
+ if (reconstructed === literalVal) {
386
+ let shiftedBytes = 0;
387
+ let tmpShifted = notShifted;
388
+ while (tmpShifted > 0n) {
389
+ ++shiftedBytes;
390
+ tmpShifted >>= 8n;
391
+ }
392
+ if (shiftedBytes === 0) shiftedBytes = 1;
393
+ const shiftNotCost = 1 + shiftedBytes + 2 + 1 + 1; // PUSH<n> + PUSH1 + SHL + NOT
394
+ if (shiftNotCost < bestCost) {
395
+ bestCost = shiftNotCost;
396
+ bestEmit = () => {
397
+ emitPushN(notShifted);
398
+ emitPushN(shiftBits);
399
+ emitOp(0x1b); // SHL
400
+ emitOp(0x19); // NOT
401
+ };
402
+ }
403
+ }
404
+ }
405
+
406
+ if (byte8s && byte8sCost < bestCost && byte8sCost <= shlCost) {
407
+ for (const { s } of seg) {
408
+ emitPushN(word[s]);
409
+ emitPushN(base + s);
410
+ emitOp(0x53); // MSTORE8
411
+ }
412
+ continue; // Skip the single MSTORE at the end
413
+ } else if (shlCost < bestCost) {
414
+ // Use SHL/OR
415
+ let first = true;
416
+ for (const { s, e } of seg) {
417
+ const suffix0s = 31 - e;
418
+ emitPushB(word.slice(s, e + 1));
419
+ if (suffix0s > 0) {
420
+ emitPushN(suffix0s * 8);
421
+ emitOp(0x1b); // SHL
422
+ }
423
+ if (!first) emitOp(0x17); // OR
424
+ first = false;
425
+ }
426
+ } else {
427
+ bestEmit!();
301
428
  }
302
429
  }
303
430
  emitPushN(base);
@@ -314,9 +441,9 @@ const _jitDecompressor = function (calldata: string): string {
314
441
  .filter(([val, freq]) => freq > 1 && val > 1n && val !== 32n && val !== 224n)
315
442
  .sort((a, b) => stackCnt.get(b[0])! - stackCnt.get(a[0])!)
316
443
  .filter(([val, _]) => {
317
- return typeof val === 'number' ? BigInt(val) : val <= MAX_160_BIT;
444
+ return typeof val === 'number' ? BigInt(val) : val <= MAX_128_BIT;
318
445
  })
319
- .slice(0, 13)
446
+ .slice(0, 15)
320
447
  .forEach(([val, _]) => {
321
448
  pushN(val);
322
449
  });
@@ -362,16 +489,18 @@ const _jitDecompressor = function (calldata: string): string {
362
489
  return '0x' + _uint8ArrayToHex(new Uint8Array(out)) + '345f355af13d5f5f3e3d5ff3';
363
490
  };
364
491
 
365
- const MIN_SIZE_FOR_COMPRESSION = 1150;
366
492
  const DECOMPRESSOR_ADDRESS = '0x00000000000000000000000000000000000000e0';
367
493
 
368
- const _jit = 'jit';
369
- const _flz = 'flz';
370
- const _cd = 'cd';
371
-
372
494
  /**
373
495
  * Compresses eth_call payload using JIT, FastLZ (FLZ), or calldata RLE (CD) compression.
374
496
  * Auto-selects best algorithm if not specified. Only compresses if >800 bytes and beneficial.
497
+ *
498
+ * Only applies compression to calls that:
499
+ * - target the latest block ID
500
+ * - have no state overrides
501
+ * - have a target address and calldata
502
+ * - have no other properties (nonce, gas, etc.)
503
+ *
375
504
  * @param payload - eth_call RPC payload
376
505
  * @param alg - 'jit' | 'flz' | 'cd' | undefined (auto)
377
506
  * @returns (un)compressed eth_call payload
@@ -379,65 +508,63 @@ const _cd = 'cd';
379
508
  */
380
509
  //! @__PURE__
381
510
  export const compress_call = function (payload: any, alg?: string): any {
382
- const rpcMethod = payload.params?.[0]?.method || payload.method;
383
- if (rpcMethod && rpcMethod !== 'eth_call') return payload;
384
-
385
- // Extract data and target address from params[0] if available, otherwise top-level
386
- const txObj = payload.params?.[0] || payload;
387
- const blockParam = payload.params?.[1] || 'latest';
388
- const existingStateOverride = payload.params?.[2] || {};
389
-
390
- // If there are any existing state overrides for the decompressor address, do not compress
391
- const hex = _normHex(txObj.data || '0x');
392
- const originalSize = (txObj.data || '0x').length;
393
- if (originalSize < MIN_SIZE_FOR_COMPRESSION || (existingStateOverride[DECOMPRESSOR_ADDRESS])) return payload;
394
-
395
- const targetAddress = txObj.to || '';
396
- const data = '0x' + hex;
397
-
398
- const autoSelect = !alg && originalSize < 1150;
399
- const flz = alg === _flz || autoSelect ? LibZip.flzCompress(data) : null;
400
- const cd = alg === _cd || autoSelect ? LibZip.cdCompress(data) : null;
401
- let selectedMethod = alg;
402
- if (!selectedMethod) {
403
- selectedMethod =
404
- originalSize >= 1150 && (originalSize < 3000 || originalSize >= 8000)
405
- ? _jit
406
- : originalSize >= 3000 && originalSize < 8000
407
- ? flz!.length < cd!.length
408
- ? _flz
409
- : _cd
410
- : _cd;
511
+ const { method, params } = payload;
512
+ if (method && method !== 'eth_call') return payload;
513
+ const txObj = params?.[0] || payload;
514
+ const blockParam = params?.[1];
515
+ const overrides = params?.[2];
516
+
517
+ // Validation
518
+ if (
519
+ (blockParam && blockParam !== 'latest') ||
520
+ (overrides && Object.keys(overrides).length > 0) ||
521
+ !txObj?.to ||
522
+ !txObj?.data ||
523
+ Object.keys(txObj).some((k) => !['to', 'data', 'from'].includes(k))
524
+ ) {
525
+ return payload;
411
526
  }
412
527
 
528
+ const originalSize = txObj.data.length;
529
+ if (originalSize < MIN_BODY_SIZE) return payload;
530
+
531
+ const inputData = '0x' + _normHex(txObj.data);
532
+ const to = txObj.to;
533
+
534
+ // Determine compression method and generate bytecode/calldata
413
535
  let bytecode: string;
414
536
  let calldata: string;
415
537
 
416
- if (selectedMethod === _jit) {
417
- bytecode = _jitDecompressor(data);
418
- calldata = '0x' + _normHex(targetAddress).padStart(64, '0');
538
+ if (alg === 'jit' || (!alg && (originalSize < 3000 || originalSize >= 8000))) {
539
+ bytecode = _jitDecompressor(inputData);
540
+ calldata = '0x' + _normHex(to).padStart(64, '0');
419
541
  } else {
420
- const isFlz = selectedMethod === _flz;
421
- calldata = isFlz ? flz! : cd!;
422
- bytecode = isFlz ? flzFwdBytecode(targetAddress) : rleFwdBytecode(targetAddress);
542
+ // Need FLZ and/or CD compression
543
+ const flzData = alg === 'flz' || !alg ? LibZip.flzCompress(inputData) : null;
544
+ const cdData = alg === 'cd' || (!alg && flzData) ? LibZip.cdCompress(inputData) : null;
545
+
546
+ // Pick the best or requested one
547
+ const useFlz =
548
+ alg === 'flz' || (!alg && flzData && (!cdData || flzData.length < cdData.length));
549
+
550
+ if (useFlz) {
551
+ calldata = flzData!;
552
+ bytecode = flzFwdBytecode(to);
553
+ } else {
554
+ calldata = cdData!;
555
+ bytecode = rleFwdBytecode(to);
556
+ }
423
557
  }
424
558
 
425
- const compressedSize = bytecode.length + calldata.length;
426
- if (compressedSize >= originalSize) return payload;
559
+ // Skip if not beneficial
560
+ if (bytecode.length + calldata.length >= originalSize) return payload;
427
561
 
428
562
  return {
429
563
  ...payload,
430
564
  params: [
431
- {
432
- ...txObj,
433
- to: DECOMPRESSOR_ADDRESS,
434
- data: calldata,
435
- },
436
- blockParam,
437
- {
438
- ...existingStateOverride,
439
- [DECOMPRESSOR_ADDRESS]: { code: bytecode },
440
- },
565
+ { ...txObj, to: DECOMPRESSOR_ADDRESS, data: calldata },
566
+ blockParam || 'latest',
567
+ { ...overrides, [DECOMPRESSOR_ADDRESS]: { code: bytecode } },
441
568
  ],
442
569
  };
443
570
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eth-compress",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Client-to-server compression (viem-compatible) module for compressed, gas-efficient, low-latency eth_call requests.",
@@ -99,7 +99,7 @@
99
99
  "node": ">=22",
100
100
  "pnpm": ">=10"
101
101
  },
102
- "packageManager": "pnpm@10.22.0",
102
+ "packageManager": "pnpm@10.23.0",
103
103
  "pnpm": {
104
104
  "peerDependencyRules": {
105
105
  "ignoreMissing": [
@@ -111,16 +111,16 @@
111
111
  "solady": "0.1.26"
112
112
  },
113
113
  "devDependencies": {
114
- "@biomejs/biome": "2.3.6",
114
+ "@biomejs/biome": "2.3.7",
115
115
  "@ethereumjs/common": "10.1.0",
116
116
  "@ethereumjs/util": "10.1.0",
117
117
  "@ethereumjs/vm": "10.1.0",
118
- "@types/bun": "1.3.2",
118
+ "@types/bun": "1.3.3",
119
119
  "@types/node": "24.10.1",
120
120
  "esbuild": "0.27.0",
121
121
  "typescript": "5.9.3",
122
- "viem": "2.39.3",
123
- "vitest": "4.0.10"
122
+ "viem": "2.40.2",
123
+ "vitest": "4.0.14"
124
124
  },
125
125
  "browserslist": [
126
126
  ">0.3%",