Skip to content

Commit

Permalink
FEVM address conversion update (#319)
Browse files Browse the repository at this point in the history
* Add id mask check to newDelegatedEthAddress

* Add eth address check to isEthIdMaskAddress

* Update FEVM address conversion tests

* Fix checksum of t411f example address

* Update index.ts

* Ensure isEthAddress only validates hex-string type eth addresses

* Prevent returning an ID mask address from ethAddressFromDelegated

* Add more checks for invalid f410f addresses
  • Loading branch information
navFooh authored Jun 25, 2024
1 parent bda4408 commit b25af4a
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 40 deletions.
125 changes: 91 additions & 34 deletions packages/filecoin-address/src/__tests__/address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
idFromAddress,
delegatedFromEthAddress,
ethAddressFromDelegated,
ethAddressFromID
ethAddressFromID,
idFromEthAddress,
isEthIdMaskAddress,
isEthAddress
} from '../index'

describe('address', () => {
Expand Down Expand Up @@ -331,38 +334,92 @@ describe('address', () => {
})
})

const hex = '0x52963EF50e27e06D72D59fcB4F3c2a687BE3cfEf'
const del = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa'

describe('decode f4 addresses', () => {
expect(decode(del).toString()).toBe(del)
})

describe('delegatedFromEthAddress', () => {
expect(delegatedFromEthAddress(hex, CoinType.TEST)).toBe(del)
})

describe('ethAddressFromDelegated', () => {
expect(ethAddressFromDelegated(del)).toBe(hex)
})

describe('ethAddressFromID', () => {
expect(ethAddressFromID('t01')).toBe(
'0xff00000000000000000000000000000000000001'
)
expect(ethAddressFromID('t0100')).toBe(
'0xff00000000000000000000000000000000000064'
)
expect(ethAddressFromID('t05088')).toBe(
'0xff000000000000000000000000000000000013e0'
)
})

test('it should validate correct filecoin addresses', () => {
expect(validateAddressString(del)).toBe(true)
})

test('it should invalidate incorrect filecoin addresses', () => {
expect(validateAddressString(del.slice(0, -1))).toBe(false)
describe('FEVM', () => {
const eth = '0x52963EF50e27e06D72D59fcB4F3c2a687BE3cfEf'
const ethId01 = '0xff00000000000000000000000000000000000001'
const ethId0100 = '0xff00000000000000000000000000000000000064'
const ethId05088 = '0xff000000000000000000000000000000000013e0'
const ethInvalid = '0x8Ba1f109551bD432803012645Ac136ddd64DBa72'
const ethIcap = 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'
const t01 = 't01'
const t0100 = 't0100'
const t05088 = 't05088'
const t1 = 't16xlkjp3dcfrsb257duoqfgj7glo2uvvgxyy4gmy'
const t2 = 't2e467euxin5y6vsmiw4ts3l4cme4zio4cvfx5b5a'
const t3 =
't3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a'
const t410f = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa'
const t410fIdMask = 't410f74aaaaaaaaaaaaaaaaaaaaaaaaaaaaabvo5mkdi'
const t410fShort = 't410fkkld55ioe7qg24wvt7fu6pbkndgcenb6'
const t410fLong = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pebagbaf3x4ox2'
const t411f = 't411fkkld55ioe7qg24wvt7fu6pbknb56ht7poxmy4mq'

test('decode f4 addresses', () => {
expect(decode(t410f).toString()).toBe(t410f)
})

test('delegatedFromEthAddress', () => {
expect(delegatedFromEthAddress(eth, CoinType.TEST)).toBe(t410f)
expect(() => delegatedFromEthAddress(ethId01, CoinType.TEST)).toThrow()
})

test('ethAddressFromDelegated', () => {
expect(() => ethAddressFromDelegated(eth)).toThrow()
expect(() => ethAddressFromDelegated(t01)).toThrow()
expect(() => ethAddressFromDelegated(t1)).toThrow()
expect(() => ethAddressFromDelegated(t2)).toThrow()
expect(() => ethAddressFromDelegated(t3)).toThrow()
expect(ethAddressFromDelegated(t410f)).toBe(eth)
expect(() => ethAddressFromDelegated(t410fIdMask)).toThrow()
expect(() => ethAddressFromDelegated(t410fShort)).toThrow()
expect(() => ethAddressFromDelegated(t410fLong)).toThrow()
expect(() => ethAddressFromDelegated(t411f)).toThrow()
})

test('isEthAddress', () => {
expect(isEthAddress(eth)).toBe(true)
expect(isEthAddress(ethId01)).toBe(true)
expect(isEthAddress(ethId0100)).toBe(true)
expect(isEthAddress(ethId05088)).toBe(true)
expect(isEthAddress(ethInvalid)).toBe(false)
expect(isEthAddress(ethIcap)).toBe(false)
expect(isEthAddress(t01)).toBe(false)
expect(isEthAddress(t1)).toBe(false)
expect(isEthAddress(t2)).toBe(false)
expect(isEthAddress(t3)).toBe(false)
expect(isEthAddress(t410f)).toBe(false)
})

test('isEthIdMaskAddress', () => {
expect(isEthIdMaskAddress(eth)).toBe(false)
expect(isEthIdMaskAddress(ethId01)).toBe(true)
expect(isEthIdMaskAddress(ethId0100)).toBe(true)
expect(isEthIdMaskAddress(ethId05088)).toBe(true)
})

test('idFromEthAddress', () => {
expect(() => idFromEthAddress(eth, CoinType.TEST)).toThrow()
expect(idFromEthAddress(ethId01, CoinType.TEST)).toBe(t01)
expect(idFromEthAddress(ethId0100, CoinType.TEST)).toBe(t0100)
expect(idFromEthAddress(ethId05088, CoinType.TEST)).toBe(t05088)
})

test('ethAddressFromID', () => {
expect(ethAddressFromID(t01)).toBe(ethId01)
expect(ethAddressFromID(t0100)).toBe(ethId0100)
expect(ethAddressFromID(t05088)).toBe(ethId05088)
expect(() => ethAddressFromID(t1)).toThrow()
expect(() => ethAddressFromID(t2)).toThrow()
expect(() => ethAddressFromID(t3)).toThrow()
expect(() => ethAddressFromID(t410f)).toThrow()
})

test('it should validate correct filecoin addresses', () => {
expect(validateAddressString(t410f)).toBe(true)
})

test('it should invalidate incorrect filecoin addresses', () => {
expect(validateAddressString(t410f.slice(0, -1))).toBe(false)
})
})
})
29 changes: 23 additions & 6 deletions packages/filecoin-address/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ export function newDelegatedEthAddress(
ethAddr: EthAddress,
coinType?: CoinType
): Address {
if (!ethers.isAddress(ethAddr)) throw new Error('Invalid Ethereum address')
if (!isEthAddress(ethAddr)) throw new Error('Invalid Ethereum address')
if (isEthIdMaskAddress(ethAddr))
throw new Error('Cannot convert ID mask to delegated address')

return newDelegatedAddress(
DelegatedNamespace.EVM,
Expand Down Expand Up @@ -387,8 +389,6 @@ export function delegatedFromEthAddress(
ethAddr: EthAddress,
coinType: CoinType = defaultCoinType
): string {
if (isEthIdMaskAddress(ethAddr))
throw new Error('Cannot convert ID mask address to delegated')
return newDelegatedEthAddress(ethAddr, coinType).toString()
}

Expand All @@ -397,23 +397,40 @@ export function delegatedFromEthAddress(
*/

export function ethAddressFromDelegated(delegated: string): EthAddress {
const ethAddress = `0x${decode(delegated).subAddrHex}`
return ethers.getAddress(ethAddress) as EthAddress // Adds checksum
const { namespace, subAddrHex } = decode(delegated)
if (namespace !== DelegatedNamespace.EVM)
throw new Error(
`Expected namespace ${DelegatedNamespace.EVM}, found ${namespace}`
)

// Add checksum
const ethAddress = ethers.getAddress(`0x${subAddrHex}`) as EthAddress

// Prevent returning an ID mask address
if (isEthIdMaskAddress(ethAddress))
throw new Error('Delegated address invalid, represented ID mask address')

return ethAddress
}

/**
* isEthAddress determines whether the input is an Ethereum address
*/

export function isEthAddress(address: string): address is EthAddress {
return ethers.isAddress(address) && Number(address) !== 0
return (
ethers.isHexString(address) &&
ethers.isAddress(address) &&
Number(address) !== 0
)
}

/**
* isEthIdMaskAddress determines whether the input is an Ethereum ID mask address
*/

export function isEthIdMaskAddress(ethAddr: EthAddress): boolean {
if (!isEthAddress(ethAddr)) return false
const bytes = ethers.getBytes(ethAddr)
const prefix = bytes.slice(0, ethIdMaskPrefixLength)
return uint8arrays.equals(prefix, ethIdMaskPrefix)
Expand Down

0 comments on commit b25af4a

Please sign in to comment.