diff --git a/src/utils/AddressUtils.ts b/src/utils/AddressUtils.ts index 1808499c7..4c0aa7f67 100644 --- a/src/utils/AddressUtils.ts +++ b/src/utils/AddressUtils.ts @@ -223,8 +223,20 @@ export class EvmAddress extends Address { } // Constructs a new EvmAddress type. - static from(hexString: string): EvmAddress { - return new this(utils.arrayify(hexString)); + static from(address: string, encoding: "base16" | "base58" = "base16"): EvmAddress { + if (encoding === "base16") { + return new this(utils.arrayify(address)); + } + + const decodedAddress = bs58.decode(address); + const padding = decodedAddress.subarray(0, 12); + const evmAddress = decodedAddress.subarray(12); + + if (padding.length !== 12 || utils.stripZeros(padding).length !== 0 || evmAddress.length !== 20) { + throw new Error(`Not a valid base58-encoded EVM address: ${address}`); + } + + return new this(evmAddress); } } @@ -242,7 +254,16 @@ export class SvmAddress extends Address { } // Constructs a new SvmAddress type. - static from(bs58Address: string): SvmAddress { - return new this(bs58.decode(bs58Address)); + static from(address: string, encoding: "base58" | "base16" = "base58"): SvmAddress { + if (encoding === "base58") { + return new this(bs58.decode(address)); + } + + const decodedAddress = utils.arrayify(address); + if (decodedAddress.length !== 32) { + throw new Error(`Not a valid base16-encoded SVM address: ${address}`); + } + + return new this(decodedAddress); } } diff --git a/test/AddressUtils.ts b/test/AddressUtils.ts index b27d63ca7..f4ec29cc6 100644 --- a/test/AddressUtils.ts +++ b/test/AddressUtils.ts @@ -1,5 +1,4 @@ -import { EvmAddress, Address, SvmAddress, toAddressType } from "../src/utils"; -import bs58 from "bs58"; +import { bs58, EvmAddress, Address, SvmAddress, toAddressType } from "../src/utils"; import { CHAIN_IDs } from "../src/constants"; import { expect, ethers } from "./utils"; @@ -21,13 +20,56 @@ describe("Address Utils: Address Type", function () { expect(ethers.utils.isHexString(svmToken.toAddress())).to.be.false; }); it("Coerces addresses to their proper type when possible", function () { - const validEvmAddress = randomBytes(20); - const invalidEvmAddress = randomBytes(32); - const evmAddress = toAddressType(validEvmAddress, CHAIN_IDs.MAINNET); - const invalidEvmAddress = toAddressType(invalidEvmAddress, CHAIN_IDs.MAINNET); + const evmAddress = toAddressType(randomBytes(20), CHAIN_IDs.MAINNET); expect(EvmAddress.isAddress(evmAddress)).to.be.true; + + const invalidEvmAddress = toAddressType(randomBytes(32), CHAIN_IDs.MAINNET); expect(EvmAddress.isAddress(invalidEvmAddress)).to.be.false; expect(Address.isAddress(invalidEvmAddress)).to.be.true; }); + it("Handles base58-encoded EVM addresses", function () { + const rawAddress = ethers.utils.arrayify(randomBytes(20)); + + // Valid padding length + let padding = new Uint8Array(12); + let b58Address = bs58.encode([...padding, ...rawAddress]).toString(); + const address = EvmAddress.from(b58Address, "base58"); + expect(address.toAddress()).to.equal(ethers.utils.getAddress(ethers.utils.hexlify(rawAddress))); + + // Wrong encoding + expect(() => EvmAddress.from(b58Address, "base16")).to.throw(Error, /invalid arrayify value/); + + // Invalid EVM address length + [19, 21].forEach((len) => { + b58Address = bs58.encode([...padding, ...ethers.utils.arrayify(randomBytes(len))]).toString(); + expect(() => EvmAddress.from(b58Address, "base58")).to.throw(Error, /Not a valid base58-encoded EVM address/); + }); + + // Invalid padding length. + [11, 13].forEach((len) => { + padding = new Uint8Array(len); + b58Address = bs58.encode([...padding, ...rawAddress]).toString(); + expect(() => EvmAddress.from(b58Address, "base58")).to.throw(Error, /Not a valid base58-encoded EVM address/); + }); + }); + it("Handles base16-encoded SVM addresses", function () { + const rawAddress = randomBytes(32); + const expectedAddress = bs58.encode(ethers.utils.arrayify(rawAddress)); + + // Valid address + const address = SvmAddress.from(rawAddress, "base16"); + expect(address.toAddress()).to.equal(expectedAddress); + + // Wrong encoding + expect(() => SvmAddress.from(rawAddress, "base58")).to.throw(Error, /Non-base58 character/); + + // Invalid SVM address length + [31, 33].forEach((len) => { + expect(() => SvmAddress.from(randomBytes(len), "base16")).to.throw( + Error, + /Not a valid base16-encoded SVM address/ + ); + }); + }); }); });