Skip to content

Commit

Permalink
feat: improve fromEvmAddress developer experience (#1309)
Browse files Browse the repository at this point in the history
* feat: improve developer experience of from evm address function

* chore: changeset

* feat: rename toEvmAddress to toWrappedEvmAddress

* feat: rename wrapped evm address function
  • Loading branch information
danielbate authored Oct 6, 2023
1 parent 36b5284 commit d7562c2
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .changeset/two-lions-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/address": minor
"@fuel-ts/errors": minor
---

Improve developer experience of `fromEvmAddress` address helper function
2 changes: 1 addition & 1 deletion apps/docs/src/guide/types/evm-address.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ An Ethereum Virtual Machine (EVM) Address can be represented using the `EvmAddre

## Creating an EVM Address

An EVM Address only has 20 bytes therefore the first 12 bytes of the `Bits256` value are set to 0. Within the SDK, an `Address` can be instantiated and converted to an EVM Address using the `toEvmAddress()` function:
An EVM Address only has 20 bytes therefore the first 12 bytes of the `Bits256` value are set to 0. Within the SDK, an `Address` can be instantiated and converted to a wrapped and Sway compatible EVM Address using the `toEvmAddress()` function:

<<< @/../../docs-snippets/src/guide/types/evm-address.test.ts#evm-address-2{ts:line-numbers}

Expand Down
77 changes: 67 additions & 10 deletions packages/address/src/address.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { FuelError } from '@fuel-ts/errors';
import { expectToThrowFuelError } from '@fuel-ts/errors/test-utils';
import type { Bech32Address, EvmAddress } from '@fuel-ts/interfaces';
import type { B256AddressEvm, Bech32Address, EvmAddress } from '@fuel-ts/interfaces';
import signMessageTest from '@fuel-ts/testcases/src/signMessage.json';

import Address from './address';
import * as utils from './utils';

const PUBLIC_KEY = signMessageTest.publicKey;
const ADDRESS_B256 = '0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a';
const ADDRESS_B256_EVM = '0x00000000000000000000000007a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a';
const ADDRESS_B256_EVM_PADDED: B256AddressEvm =
'0x00000000000000000000000007a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a';
const ADDRESS_EVM = '0x07a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a';
const ADDRESS_BECH32: Bech32Address =
'fuel1a7r2l2tfdncdccu9utzq0fhptxs3q080kl32up3klvea8je2ne9qrqnt6n';
const ADDRESS_WORDS = [
Expand Down Expand Up @@ -128,6 +130,36 @@ describe('Address utils', () => {
expect(result).toBeTruthy();
});

test('isEvmAddress (EvmAddress)', () => {
const result = utils.isEvmAddress(ADDRESS_EVM);

expect(result).toBeTruthy();
});

test('isEvmAddress (invalid chars)', () => {
const result = utils.isEvmAddress(`${ADDRESS_EVM}/?`);

expect(result).toBeFalsy();
});

test('isEvmAddress (too long)', () => {
const result = utils.isEvmAddress(`${ADDRESS_EVM}abc12345`);

expect(result).toBeFalsy();
});

test('isEvmAddress (too short)', () => {
const result = utils.isEvmAddress('0x123');

expect(result).toBeFalsy();
});

test('isEvmAddress (no hex prefix)', () => {
const result = utils.isEvmAddress('07a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a');

expect(result).toBeTruthy();
});

test('getBytesFromBech32 (bech32 to Uint8Array)', () => {
const result = utils.getBytesFromBech32(ADDRESS_BECH32);

Expand Down Expand Up @@ -166,7 +198,7 @@ describe('Address utils', () => {
test('clearFirst12BytesFromB256 (b256 to evm b256)', () => {
const result = utils.clearFirst12BytesFromB256(ADDRESS_B256);

expect(result).toEqual(ADDRESS_B256_EVM);
expect(result).toEqual(ADDRESS_B256_EVM_PADDED);
});

test('clearFirst12BytesFromB256 (invalid B256)', async () => {
Expand All @@ -177,6 +209,24 @@ describe('Address utils', () => {
);
await expectToThrowFuelError(() => utils.clearFirst12BytesFromB256(invalidB256), expectedError);
});

test('padFirst12BytesOfEvmAddress (evm Address to b256)', () => {
const result = utils.padFirst12BytesOfEvmAddress(ADDRESS_EVM);

expect(result).toEqual(ADDRESS_B256_EVM_PADDED);
});

test('padFirst12BytesOfEvmAddress (invalid EVM Address)', async () => {
const invalidEvmAddress = '0x123';
const expectedError = new FuelError(
FuelError.CODES.INVALID_EVM_ADDRESS,
'Invalid EVM address format.'
);
await expectToThrowFuelError(
() => utils.padFirst12BytesOfEvmAddress(invalidEvmAddress),
expectedError
);
});
});

describe('Address class', () => {
Expand Down Expand Up @@ -243,6 +293,12 @@ describe('Address class', () => {
expect(address.toB256()).toEqual(signMessageTest.b256Address);
});

test('create an Address class fromDynamicInput [evmAddress]', () => {
const address = Address.fromDynamicInput(ADDRESS_EVM);

expect(address.toB256()).toEqual(ADDRESS_B256_EVM_PADDED);
});

test('create an Address class fromDynamicInput [bad input]', async () => {
const expectedError = new FuelError(
FuelError.CODES.PARSE_FAILED,
Expand All @@ -265,16 +321,17 @@ describe('Address class', () => {
const evmAddress: EvmAddress = address.toEvmAddress();

expect(evmAddress).toBeDefined();
expect(evmAddress.value).toBe(ADDRESS_B256_EVM);
expect(evmAddress.value).toBe(ADDRESS_B256_EVM_PADDED);
});

test('create an Address class fromEvmAddress', () => {
const evmAddress: EvmAddress = {
value: ADDRESS_B256_EVM,
};
test('create an Address from an Evm Address', () => {
const address = Address.fromEvmAddress(ADDRESS_EVM);

const address = Address.fromEvmAddress(evmAddress);
const evmAddressWrapped: EvmAddress = {
value: ADDRESS_B256_EVM_PADDED,
};

expect(address.toB256()).toEqual(ADDRESS_B256_EVM);
expect(address.toEvmAddress()).toMatchObject(evmAddressWrapped);
expect(address.toB256()).toEqual(ADDRESS_B256_EVM_PADDED);
});
});
14 changes: 11 additions & 3 deletions packages/address/src/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
isPublicKey,
isB256,
clearFirst12BytesFromB256,
isEvmAddress,
padFirst12BytesOfEvmAddress,
} from './utils';

/**
Expand Down Expand Up @@ -200,18 +202,24 @@ export default class Address extends AbstractAddress {
return Address.fromB256(address);
}

if (isEvmAddress(address)) {
return Address.fromEvmAddress(address);
}

throw new FuelError(
FuelError.CODES.PARSE_FAILED,
`Unknown address format: only 'Bech32', 'B256', or 'Public Key (512)' are supported.`
);
}

/**
* Takes an `EvmAddress` and returns back an `Address`
* Takes an Evm Address and returns back an `Address`
*
* @returns A new `Address` instance
*/
static fromEvmAddress(evmAddress: EvmAddress): Address {
return new Address(toBech32(evmAddress.value));
static fromEvmAddress(evmAddress: string): Address {
const paddedAddress = padFirst12BytesOfEvmAddress(evmAddress);

return new Address(toBech32(paddedAddress));
}
}
27 changes: 27 additions & 0 deletions packages/address/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ export function isPublicKey(address: string): boolean {
return (address.length === 130 || address.length === 128) && /(0x)?[0-9a-f]{128}$/i.test(address);
}

/**
* Determines if a given string is in EVM Address format
*
* @hidden
*/
export function isEvmAddress(address: string): boolean {
return (address.length === 42 || address.length === 40) && /(0x)?[0-9a-f]{40}$/i.test(address);
}

/**
* Takes a Bech32 address and returns the byte data
*
Expand Down Expand Up @@ -163,3 +172,21 @@ export const clearFirst12BytesFromB256 = (b256: B256Address): B256AddressEvm =>

return bytes;
};

/**
* Pads the first 12 bytes of an Evm address. This is useful for padding addresses returned from
* the EVM to interact with the Sway EVM Address Type.
*
* @param address - Evm address to be padded
* @returns Evm address padded to a b256 address
*
* @hidden
*/
export const padFirst12BytesOfEvmAddress = (address: string): B256AddressEvm => {
if (!isEvmAddress(address)) {
throw new FuelError(FuelError.CODES.INVALID_EVM_ADDRESS, 'Invalid EVM address format.');
}

const prefixedAddress = address.startsWith('0x') ? address : `0x${address}`;
return prefixedAddress.replace('0x', '0x000000000000000000000000') as B256AddressEvm;
};
1 change: 1 addition & 0 deletions packages/errors/src/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum ErrorCode {

// address
INVALID_BECH32_ADDRESS = 'invalid-bech32-address',
INVALID_EVM_ADDRESS = 'invalid-evm-address',

// provider
INVALID_URL = 'invalid-url',
Expand Down

0 comments on commit d7562c2

Please sign in to comment.