src/signing/subaccount.test.ts
v0.2.0 · 3.6 KB
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { describe, expect, it } from 'vitest';
import {
isBytes32,
isValidSubaccountName,
isZeroAddress,
normalizeAddress,
parseSubaccountBytes32,
productVerifyingContract,
signerBytes32,
subaccountBytes32,
ZERO_ADDRESS,
} from './subaccount.js';
const account = privateKeyToAccount(generatePrivateKey());
const addr = account.address.toLowerCase() as `0x${string}`;
describe('subaccountBytes32', () => {
it('encodes address + "default" + zero padding (documentation vector shape)', () => {
const b = subaccountBytes32(account.address, 'default');
expect(b).toHaveLength(66);
expect(b.startsWith(addr)).toBe(true);
// 'default' = 64 65 66 61 75 6c 74, then 5 zero bytes
expect(b.slice(42)).toBe(`64656661756c74${'0'.repeat(10)}`);
expect(subaccountBytes32(account.address)).toBe(b); // default name
});
it('is case-insensitive on the address and round-trips through parseSubaccountBytes32', () => {
const b = subaccountBytes32(account.address.toUpperCase().replace('0X', '0x'), 'bot1');
expect(parseSubaccountBytes32(b)).toEqual({ address: addr, name: 'bot1' });
expect(parseSubaccountBytes32(subaccountBytes32(addr, ''))).toEqual({ address: addr, name: '' });
expect(parseSubaccountBytes32(subaccountBytes32(addr, 'abcdefghijkl')).name).toBe('abcdefghijkl');
});
it('a different name is a different account (bytes differ)', () => {
expect(subaccountBytes32(addr, 'default')).not.toBe(subaccountBytes32(addr, 'Default'));
});
it('rejects malformed addresses and names', () => {
expect(() => subaccountBytes32('0x123', 'default')).toThrow(TypeError);
expect(() => subaccountBytes32(addr, 'thirteen chars')).toThrow(TypeError);
expect(() => subaccountBytes32(addr, 'with space')).toThrow(TypeError);
expect(() => subaccountBytes32(addr, 'café')).toThrow(TypeError);
expect(isValidSubaccountName('default')).toBe(true);
expect(isValidSubaccountName('abcdefghijklm')).toBe(false);
expect(() => parseSubaccountBytes32('0x12')).toThrow(TypeError);
});
});
describe('signerBytes32', () => {
it('is the address followed by 12 zero bytes', () => {
expect(signerBytes32(account.address)).toBe(`${addr}${'0'.repeat(24)}`);
expect(signerBytes32(ZERO_ADDRESS)).toBe(`0x${'0'.repeat(64)}`);
});
});
describe('productVerifyingContract', () => {
it('writes the product id as a 20-byte address', () => {
expect(productVerifyingContract(18)).toBe(`0x${'0'.repeat(38)}12`);
expect(productVerifyingContract(2)).toBe(`0x${'0'.repeat(39)}2`);
expect(productVerifyingContract(188)).toBe(`0x${'0'.repeat(38)}bc`);
expect(productVerifyingContract(0)).toBe(ZERO_ADDRESS);
});
it('rejects invalid ids', () => {
expect(() => productVerifyingContract(-1)).toThrow(RangeError);
expect(() => productVerifyingContract(1.5)).toThrow(RangeError);
expect(() => productVerifyingContract(Number.NaN)).toThrow(RangeError);
});
});
describe('address helpers', () => {
it('normalizeAddress lower-cases and validates', () => {
expect(normalizeAddress(` ${account.address} `)).toBe(addr);
expect(() => normalizeAddress('nope')).toThrow(TypeError);
expect(() => normalizeAddress(1 as unknown as string)).toThrow(TypeError);
});
it('isZeroAddress / isBytes32', () => {
expect(isZeroAddress(ZERO_ADDRESS)).toBe(true);
expect(isZeroAddress(addr)).toBe(false);
expect(isBytes32(`0x${'a'.repeat(64)}`)).toBe(true);
expect(isBytes32(`0x${'A'.repeat(64)}`)).toBe(true);
expect(isBytes32(`0x${'a'.repeat(63)}`)).toBe(false);
expect(isBytes32(42)).toBe(false);
});
});