From b53a9d1ece85316ed943a92b09bd9d5f22198ab1 Mon Sep 17 00:00:00 2001 From: Mohit Khandelwal Date: Tue, 5 Nov 2024 20:09:20 +0530 Subject: [PATCH] refactor(sdk-coin-rune): address utility methods Ticket: COIN-2191 --- CODEOWNERS | 1 + modules/sdk-coin-rune/src/lib/utils.ts | 91 ++++++++++++++++++------ modules/sdk-coin-rune/test/unit/utils.ts | 55 +++++++++++++- 3 files changed, 122 insertions(+), 25 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 09517f104d..1ed00c2a3e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -60,6 +60,7 @@ /modules/sdk-coin-near/ @BitGo/ethalt-team /modules/sdk-coin-opeth/ @BitGo/ethalt-team /modules/sdk-coin-polygon/ @BitGo/ethalt-team +/modules/sdk-coin-rune/ @BitGo/ethalt-team /modules/sdk-coin-sol/ @BitGo/ethalt-team /modules/sdk-coin-stx/ @BitGo/ethalt-team /modules/sdk-coin-sui/ @BitGo/ethalt-team diff --git a/modules/sdk-coin-rune/src/lib/utils.ts b/modules/sdk-coin-rune/src/lib/utils.ts index 848bc47f2b..163f4ffbb3 100644 --- a/modules/sdk-coin-rune/src/lib/utils.ts +++ b/modules/sdk-coin-rune/src/lib/utils.ts @@ -22,14 +22,8 @@ export class RuneUtils extends CosmosUtils { const value = this.registry.decode(message); return { value: { - fromAddress: - this.networkType === NetworkType.TESTNET - ? bech32.encode(TESTNET_ADDRESS_PREFIX, value.fromAddress) - : bech32.encode(MAINNET_ADDRESS_PREFIX, value.fromAddress), - toAddress: - this.networkType === NetworkType.TESTNET - ? bech32.encode(TESTNET_ADDRESS_PREFIX, value.toAddress) - : bech32.encode(MAINNET_ADDRESS_PREFIX, value.toAddress), + fromAddress: this.getEncodedAddress(value.fromAddress), + toAddress: this.getEncodedAddress(value.toAddress), amount: value.amount, }, typeUrl: message.typeUrl, @@ -39,23 +33,74 @@ export class RuneUtils extends CosmosUtils { /** @inheritdoc */ isValidAddress(address: string | Buffer): boolean { - if (address === undefined) { + if (address === undefined || address === null) { return false; } - if (typeof address !== 'string') { - const encodedAddress = - this.networkType === NetworkType.TESTNET - ? bech32.encode(TESTNET_ADDRESS_PREFIX, address) - : bech32.encode(MAINNET_ADDRESS_PREFIX, address); - if (this.networkType === NetworkType.TESTNET) { - return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.testnetAccountAddressRegex); - } - return this.isValidCosmosLikeAddressWithMemoId(encodedAddress, constants.mainnetAccountAddressRegex); - } else { - if (this.networkType === NetworkType.TESTNET) { - return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex); - } - return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex); + if (address instanceof Uint8Array) { + return this.isValidDecodedAddress(address); + } + if (typeof address === 'string') { + return this.isValidEncodedAddress(address); + } + return false; + } + + /** + * Validates a decoded address in `Buffer` form by encoding it and + * checking if the encoded version is valid + * + * @param address - The decoded address as a `Buffer`. + * @returns `true` if the encoded address is valid, `false` otherwise. + */ + private isValidDecodedAddress(address: Buffer): boolean { + const encodedAddress = this.getEncodedAddress(address); + return this.isValidEncodedAddress(encodedAddress); + } + + /** + * Validates an encoded address string against network-specific criteria. + * + * @param address - The encoded address as a `string`. + * @returns `true` if the address meets network-specific validation criteria, `false` otherwise. + */ + private isValidEncodedAddress(address: string): boolean { + if (this.networkType === NetworkType.TESTNET) { + return this.isValidCosmosLikeAddressWithMemoId(address, constants.testnetAccountAddressRegex); + } + return this.isValidCosmosLikeAddressWithMemoId(address, constants.mainnetAccountAddressRegex); + } + + /** + * Encodes a given address `Buffer` into a bech32 string format, based on the current network type. + * Primarily serves as a utility to convert a `Buffer`-type address to a bech32 encoded string + * + * @param address - The address to be encoded, provided as a `Buffer`. + * @returns A bech32-encoded string representing the address. + * @throws Error - Throws an error if encoding fails + */ + getEncodedAddress(address: Buffer): string { + try { + return this.networkType === NetworkType.TESTNET + ? bech32.encode(TESTNET_ADDRESS_PREFIX, address) + : bech32.encode(MAINNET_ADDRESS_PREFIX, address); + } catch (error) { + throw new Error(`Failed to encode address: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Decodes a bech32-encoded address string back into a `Buffer`. + * Primarily serves as a utility to convert a string-type address into its binary representation, + * + * @param address - The bech32-encoded address as a `string`. + * @returns The decoded address as a `Buffer`. + * @throws Error - Throws an error if decoding fails + */ + getDecodedAddress(address: string): Buffer { + try { + return bech32.decode(address).data; + } catch (error) { + throw new Error(`Failed to decode address: ${error instanceof Error ? error.message : String(error)}`); } } diff --git a/modules/sdk-coin-rune/test/unit/utils.ts b/modules/sdk-coin-rune/test/unit/utils.ts index 65ea031d8d..3e15d3de8d 100644 --- a/modules/sdk-coin-rune/test/unit/utils.ts +++ b/modules/sdk-coin-rune/test/unit/utils.ts @@ -1,13 +1,18 @@ import { NetworkType } from '@bitgo/statics'; import should from 'should'; import { RuneUtils } from '../../src/lib/utils'; -import { blockHash, mainnetCoinAmounts, txIds } from '../resources/rune'; -import { testnetCoinAmounts } from '../resources/trune'; +import { MAINNET_ADDRESS_PREFIX, TESTNET_ADDRESS_PREFIX } from '../../src/lib/constants'; +import { blockHash, mainnetCoinAmounts, txIds, mainnetAddress } from '../resources/rune'; +import { testnetCoinAmounts, testnetAddress } from '../resources/trune'; +const bech32 = require('bech32-buffer'); describe('utils', () => { const mainnetUtils = new RuneUtils(NetworkType.MAINNET); const testnetUtils = new RuneUtils(NetworkType.TESTNET); + const testnetDecodedAddress = bech32.decode(testnetAddress.address1).data; + const mainnetDecodedAddress = bech32.decode(mainnetAddress.address1).data; + it('should validate block hash correctly', () => { should.equal(mainnetUtils.isValidBlockId(blockHash.hash1), true); should.equal(mainnetUtils.isValidBlockId(blockHash.hash2), true); @@ -57,4 +62,50 @@ describe('utils', () => { 'transactionBuilder: validateAmount: Invalid denom: ' + testnetCoinAmounts.amount5.denom ); }); + + it('should validate mainnet address', () => { + should.equal(mainnetUtils.isValidAddress(mainnetAddress.address1), true); + should.equal(mainnetUtils.isValidAddress(mainnetAddress.validMemoIdAddress), true); + should.equal(mainnetUtils.isValidAddress(mainnetDecodedAddress), true); + should.equal(mainnetUtils.isValidAddress(mainnetAddress.invalidMemoIdAddress), false); + should.equal(mainnetUtils.isValidAddress(testnetAddress.address1), false); + should.equal(mainnetUtils.isValidAddress('12345'), false); + }); + + it('should validate testnet address', () => { + should.equal(testnetUtils.isValidAddress(testnetAddress.address1), true); + should.equal(testnetUtils.isValidAddress(testnetAddress.validMemoIdAddress), true); + should.equal(mainnetUtils.isValidAddress(testnetDecodedAddress), true); + should.equal(testnetUtils.isValidAddress(testnetAddress.invalidMemoIdAddress), false); + should.equal(testnetUtils.isValidAddress(mainnetAddress.address1), false); + should.equal(testnetUtils.isValidAddress('12345'), false); + }); + + it('should convert string type testnet address to Uint8Array', () => { + const decodedAddress = testnetUtils.getDecodedAddress(testnetAddress.address1); + should.equal(decodedAddress instanceof Uint8Array, true); + should.equal(decodedAddress.length, 20); + }); + + it('should convert string type mainnet address to Uint8Array', () => { + const decodedAddress = mainnetUtils.getDecodedAddress(mainnetAddress.address1); + should.equal(decodedAddress instanceof Uint8Array, true); + should.equal(decodedAddress.length, 20); + }); + + it('should convert Uint8Array type testnet address to string', () => { + const encodedAddress = testnetUtils.getEncodedAddress(testnetDecodedAddress); + should.equal(encodedAddress, testnetAddress.address1); + should.equal(typeof encodedAddress, 'string'); + should.equal(encodedAddress.length, 44); + should.equal(encodedAddress.startsWith(TESTNET_ADDRESS_PREFIX), true); + }); + + it('should convert Uint8Array type mainnet address to string', () => { + const encodedAddress = mainnetUtils.getEncodedAddress(mainnetDecodedAddress); + should.equal(encodedAddress, mainnetAddress.address1); + should.equal(typeof encodedAddress, 'string'); + should.equal(encodedAddress.length, 43); + should.equal(encodedAddress.startsWith(MAINNET_ADDRESS_PREFIX), true); + }); });