Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update isAddress to not throw #3251

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions packages/addresses/src/__tests__/address-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,97 @@ const originalGetBase58Encoder = originalBase58Module.getBase58Encoder();
const originalGetBase58Decoder = originalBase58Module.getBase58Decoder();

describe('Address', () => {
describe('isAddress()', () => {
let isAddress: typeof import('../address').isAddress;
// Reload `isAddress` before each test to reset memoized state
beforeEach(async () => {
await jest.isolateModulesAsync(async () => {
const base58ModulePromise =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import('../address');
isAddress = (await base58ModulePromise).isAddress;
});
});

describe('using the real base58 implementation', () => {
beforeEach(() => {
// use real implementation
jest.mocked(getBase58Encoder).mockReturnValue(originalGetBase58Encoder);
});
it('does not throw when supplied a non-base58 address', () => {
expect(() => {
isAddress('not-a-base-58-encoded-string');
}).not.toThrow();
});
it('returns false when supplied a non-base58 string', () => {
expect(isAddress('not-a-base-58-encoded-string')).toEqual(false);
});
it('returns false when the decoded byte array has a length other than 32 bytes', () => {
expect(
isAddress(
// 31 bytes [128, ..., 128]
'2xea9jWJ9eca3dFiefTeSPP85c6qXqunCqL2h2JNffM',
),
).toEqual(false);
});
it('returns true when supplied a base-58 encoded address', () => {
expect(isAddress('11111111111111111111111111111111')).toEqual(true);
});
});
describe('using a mock base58 implementation', () => {
const mockEncode = jest.fn();
beforeEach(() => {
// use mock implementation
mockEncode.mockClear();
jest.mocked(getBase58Encoder).mockReturnValue({
encode: mockEncode,
} as unknown as VariableSizeEncoder<string>);
});

[32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44].forEach(len => {
it(`attempts to encode input strings of exactly ${len} characters`, () => {
try {
isAddress('1'.repeat(len));
// eslint-disable-next-line no-empty
} catch {}
expect(mockEncode).toHaveBeenCalled();
});
});
it('does not attempt to decode too-short input strings', () => {
try {
isAddress(
// 31 bytes [0, ..., 0]
'1111111111111111111111111111111', // 31 characters
);
// eslint-disable-next-line no-empty
} catch {}
expect(mockEncode).not.toHaveBeenCalled();
});
it('does not attempt to decode too-long input strings', () => {
try {
isAddress(
// 33 bytes [0, 255, ..., 255]
'1JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG', // 45 characters
);
// eslint-disable-next-line no-empty
} catch {}
expect(mockEncode).not.toHaveBeenCalled();
});
it('memoizes getBase58Encoder when called multiple times', () => {
try {
isAddress('1'.repeat(32));
// eslint-disable-next-line no-empty
} catch {}
try {
isAddress('1'.repeat(32));
// eslint-disable-next-line no-empty
} catch {}
expect(jest.mocked(getBase58Encoder)).toHaveBeenCalledTimes(1);
});
});
});

describe('assertIsAddress()', () => {
let assertIsAddress: typeof import('../address').assertIsAddress;
// Reload `assertIsAddress` before each test to reset memoized state
Expand Down
14 changes: 9 additions & 5 deletions packages/addresses/src/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ export function isAddress(putativeAddress: string): putativeAddress is Address<t
}
// Slow-path; actually attempt to decode the input string.
const base58Encoder = getMemoizedBase58Encoder();
const bytes = base58Encoder.encode(putativeAddress);
const numBytes = bytes.byteLength;
if (numBytes !== 32) {
return false;
try {
const bytes = base58Encoder.encode(putativeAddress);
const numBytes = bytes.byteLength;
if (numBytes !== 32) {
return false;
}
return true;
} catch (e) {
return false
ryoid marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
}

export function assertIsAddress(putativeAddress: string): asserts putativeAddress is Address<typeof putativeAddress> {
Expand Down