From d4b2fea48fdcae9b8fd4028606ec58dc6bf7f38d Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Tue, 3 Dec 2024 13:21:39 -0500 Subject: [PATCH] Fix infinite loop in address parsing (#746) * fix potential inifinite loop * simple example file showing how to parse addresses * toNative can handle a UniversalAddress value * add unit tests to document and test this behavior * handle Uint8Array too * check it starts with 0x * test --- core/definitions/__tests__/address.ts | 2 +- core/definitions/src/address.ts | 17 ++++++++++--- core/definitions/src/universalAddress.ts | 2 +- examples/package.json | 3 ++- examples/src/parseAddress.ts | 20 ++++++++++++++++ platforms/evm/__tests__/unit/platform.test.ts | 24 +++++++++++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 examples/src/parseAddress.ts diff --git a/core/definitions/__tests__/address.ts b/core/definitions/__tests__/address.ts index 651ea11b8..475fa0a5a 100644 --- a/core/definitions/__tests__/address.ts +++ b/core/definitions/__tests__/address.ts @@ -35,4 +35,4 @@ describe("UniversalAddress tests", function () { const ua = new UniversalAddress(appId, "algorandAppId"); expect(ua.toString()).toEqual(appAddress) }); -}); \ No newline at end of file +}); diff --git a/core/definitions/src/address.ts b/core/definitions/src/address.ts index 0af2f7a52..145099bae 100644 --- a/core/definitions/src/address.ts +++ b/core/definitions/src/address.ts @@ -102,9 +102,20 @@ export function toNative( } return nativeAddress; - } catch (_) { - // try to parse it as a universal address - return (UniversalAddress.instanceof(ua) ? ua : new UniversalAddress(ua)).toNative(chain); + } catch (e: any) { + const err = `Error parsing address as a native ${chain} address: ${e.message}`; + + if (UniversalAddress.instanceof(ua)) { + throw err; + } else { + // If we were given a string or Uint8Array value, which is ambiguously either a + // NativeAddress or UniversalAddress, and it failed to parse directly + // as a NativeAddress, we try one more time to parse it as a UniversalAddress + // first and then convert that to a NativeAddress. + console.error(err); + console.error('Attempting to parse as UniversalAddress'); + return (new UniversalAddress(ua)).toNative(chain); + } } } diff --git a/core/definitions/src/universalAddress.ts b/core/definitions/src/universalAddress.ts index caa9a41a4..ff14a3bb5 100644 --- a/core/definitions/src/universalAddress.ts +++ b/core/definitions/src/universalAddress.ts @@ -26,7 +26,7 @@ export class UniversalAddress implements Address { } toNative[0]>(chainOrPlatform: T): NativeAddress { - return toNative(chainOrPlatform, this.toUint8Array()); + return toNative(chainOrPlatform, this); } unwrap(): Uint8Array { diff --git a/examples/package.json b/examples/package.json index 9d4af61b7..2dc9d970f 100644 --- a/examples/package.json +++ b/examples/package.json @@ -37,6 +37,7 @@ "wrapped": "tsx src/createWrapped.ts", "tb": "tsx src/tokenBridge.ts", "cctp": "tsx src/cctp.ts", + "parseAddress": "tsx src/parseAddress.ts", "demo": "tsx src/index.ts", "cosmos": "tsx src/cosmos.ts", "msg": "tsx src/messaging.ts", @@ -53,4 +54,4 @@ "dependencies": { "@wormhole-foundation/sdk": "1.0.3" } -} \ No newline at end of file +} diff --git a/examples/src/parseAddress.ts b/examples/src/parseAddress.ts new file mode 100644 index 000000000..79d08d4ce --- /dev/null +++ b/examples/src/parseAddress.ts @@ -0,0 +1,20 @@ +import { toNative, toUniversal } from "@wormhole-foundation/sdk"; + +const ETHEREUM_ADDRESS = '0xaaee1a9723aadb7afa2810263653a34ba2c21c7a'; +const ETHEREUM_ADDRESS_UNIVERSAL = toUniversal('Ethereum', ETHEREUM_ADDRESS).toString(); + +(async function () { + // We can parse an Ethereum address from its native or universal format + const parsedEthereumAddr1 = toNative('Ethereum', ETHEREUM_ADDRESS); + const parsedEthereumAddr2 = toNative('Ethereum', ETHEREUM_ADDRESS_UNIVERSAL); + console.log(parsedEthereumAddr1); + console.log(parsedEthereumAddr2); + + // Parsing a Sui address as Ethereum will throw: + try { + toNative('Ethereum', '0xabd62c91e3bd89243c592b93b9f45cf9f584be3df4574e05ae31d02fcfef67fc'); + } catch (e) { + console.error(e); + } +})(); + diff --git a/platforms/evm/__tests__/unit/platform.test.ts b/platforms/evm/__tests__/unit/platform.test.ts index 196a82321..a4947ad9e 100644 --- a/platforms/evm/__tests__/unit/platform.test.ts +++ b/platforms/evm/__tests__/unit/platform.test.ts @@ -20,6 +20,10 @@ import { chains, } from '@wormhole-foundation/sdk-connect'; +import { + toNative, +} from '@wormhole-foundation/sdk-definitions'; + import '@wormhole-foundation/sdk-evm-core'; import '@wormhole-foundation/sdk-evm-tokenbridge'; import { EvmPlatform } from '../../src/platform.js'; @@ -38,6 +42,26 @@ const configs = CONFIG[network].chains; // const satisfiesInterface: PlatformUtils = EvmPlatform; describe('EVM Platform Tests', () => { + describe("Parse Ethereum address", function () { + test("should correctly parse Ethereum addresses", () => { + expect(() => + toNative('Ethereum', '0xaaee1a9723aadb7afa2810263653a34ba2c21c7a') + ).toBeTruthy(); + }); + + test("should correctly handle zero-padded Ethereum addresses (in universal address format)", () => { + expect(() => + toNative('Ethereum', '0x000000000000000000000000aaee1a9723aadb7afa2810263653a34ba2c21c7a') + ).toBeTruthy(); + }); + + test("should throw when parsing an invalid Ethereum addresses", () => { + expect(() => + toNative('Ethereum', '0xabd62c91e3bd89243c592b93b9f45cf9f584be3df4574e05ae31d02fcfef67fc') + ).toThrow(); + }); + }); + describe('Get Token Bridge', () => { test('No RPC', async () => { const p = new EvmPlatform(network, {});