opnet
Version:
The perfect library for building Bitcoin-based applications.
99 lines (88 loc) • 4.89 kB
text/typescript
import { describe, expect, it } from 'vitest';
import { stringToBuffer } from '../build/utils/StringToBuffer.js';
import { ContractData } from '../build/contracts/ContractData.js';
describe('Hex prefix stripping (anchored replace)', () => {
describe('stringToBuffer', () => {
it('should strip leading 0x prefix from hex string', () => {
const result = stringToBuffer('0xabcdef');
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(3);
expect(result[0]).toBe(0xab);
expect(result[1]).toBe(0xcd);
expect(result[2]).toBe(0xef);
});
it('should pass through hex string without 0x prefix', () => {
const result = stringToBuffer('abcdef');
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(3);
expect(result[0]).toBe(0xab);
});
it('should NOT strip 0x appearing in the middle of a hex string', () => {
// Hex string that naturally contains the bytes 0x somewhere mid-string
// e.g. "dead0xbeef" -- the "0x" is part of the data, not a prefix
const withMid0x = 'dead0xbeef';
// Old broken behavior: .replace('0x','') -> "deadbeef" (4 bytes, WRONG)
// Correct behavior: no leading 0x, pass through as-is -> should fail or parse all 5 bytes
// Since "dead0xbeef" is not valid hex (contains 'x'), fromHex should handle it
// The key point: we must NOT silently strip '0x' from the middle
const result = stringToBuffer('0xdead0000beef');
expect(result.length).toBe(6);
expect(result[0]).toBe(0xde);
expect(result[1]).toBe(0xad);
expect(result[2]).toBe(0x00);
expect(result[3]).toBe(0x00);
expect(result[4]).toBe(0xbe);
expect(result[5]).toBe(0xef);
});
it('should handle 32-byte hash with 0x prefix', () => {
const hash = '0x' + 'ab'.repeat(32);
const result = stringToBuffer(hash);
expect(result.length).toBe(32);
expect(result.every((b) => b === 0xab)).toBe(true);
});
it('should handle 32-byte hash without 0x prefix', () => {
const hash = 'ab'.repeat(32);
const result = stringToBuffer(hash);
expect(result.length).toBe(32);
expect(result.every((b) => b === 0xab)).toBe(true);
});
it('should handle empty string after prefix strip', () => {
// Edge case: just "0x" with nothing after
expect(() => stringToBuffer('0x')).not.toThrow();
});
});
describe('ContractData - deployerAddress base64 with natural 0x', () => {
it('should not corrupt base64 strings containing 0x mid-string', () => {
// This is the exact scenario from Marcus's bug report:
// deployerAddress is base64 and naturally contains '0x' in the encoded data
const rawContract = {
contractAddress: 'opr1sqp9fdxh7k65t5wpu78vgq9qgh5648t26puxwd22p',
contractPublicKey: 'MLtcZsUGLY58TFfCDmDv3xFtsQTh92H5yvyGHh4Q4ts=',
bytecode: 'AAAA', // minimal valid base64
wasCompressed: false,
deployedTransactionId: 'abc123',
deployedTransactionHash: 'def456',
deployerPubKey: 'AseKmAJDF0G7CT6S+Om6asMKEd9QwUI9k9etw3WDpTHS',
// This base64 naturally contains '0x' in the middle: ...OSO0xRjQ...
deployerAddress: '7b35xyUK7jCoKcgiOmUQ7OGMOSO0xRjQycTYCOxe/Lw=',
contractSeed: 'lV6tU9txOGiJGIY+jE7hNI1q2InzW0vLUX7iSruMHUXC+RHGZurklKAnbpo3cpTs0EAUK1D7noxZOCsVRV3plA==',
contractSaltHash: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
};
// This should NOT throw -- old code threw InvalidCharacterError
// because .replace('0x','') ripped '0x' from middle of the base64
expect(() => new ContractData(rawContract as any)).not.toThrow();
});
it('should not crash on 0x-prefixed deployerAddress (hex strip only)', () => {
// Test that the hex prefix stripping itself works correctly
// We test the stripping logic directly since ContractData also validates
// MLDSA key lengths which is unrelated to the 0x fix
const hexAddr = '0x' + 'ab'.repeat(32);
const stripped = hexAddr.startsWith('0x') ? hexAddr.slice(2) : hexAddr;
expect(stripped).toBe('ab'.repeat(32));
expect(stripped.length).toBe(64);
// Verify the old .replace would also work here (since 0x is at the start)
const oldStripped = hexAddr.replace('0x', '');
expect(oldStripped).toBe(stripped);
});
});
});