Skip to content

Commit b25af4a

Browse files
authored
FEVM address conversion update (#319)
* 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
1 parent bda4408 commit b25af4a

File tree

2 files changed

+114
-40
lines changed

2 files changed

+114
-40
lines changed

packages/filecoin-address/src/__tests__/address.spec.ts

+91-34
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {
2020
idFromAddress,
2121
delegatedFromEthAddress,
2222
ethAddressFromDelegated,
23-
ethAddressFromID
23+
ethAddressFromID,
24+
idFromEthAddress,
25+
isEthIdMaskAddress,
26+
isEthAddress
2427
} from '../index'
2528

2629
describe('address', () => {
@@ -331,38 +334,92 @@ describe('address', () => {
331334
})
332335
})
333336

334-
const hex = '0x52963EF50e27e06D72D59fcB4F3c2a687BE3cfEf'
335-
const del = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa'
336-
337-
describe('decode f4 addresses', () => {
338-
expect(decode(del).toString()).toBe(del)
339-
})
340-
341-
describe('delegatedFromEthAddress', () => {
342-
expect(delegatedFromEthAddress(hex, CoinType.TEST)).toBe(del)
343-
})
344-
345-
describe('ethAddressFromDelegated', () => {
346-
expect(ethAddressFromDelegated(del)).toBe(hex)
347-
})
348-
349-
describe('ethAddressFromID', () => {
350-
expect(ethAddressFromID('t01')).toBe(
351-
'0xff00000000000000000000000000000000000001'
352-
)
353-
expect(ethAddressFromID('t0100')).toBe(
354-
'0xff00000000000000000000000000000000000064'
355-
)
356-
expect(ethAddressFromID('t05088')).toBe(
357-
'0xff000000000000000000000000000000000013e0'
358-
)
359-
})
360-
361-
test('it should validate correct filecoin addresses', () => {
362-
expect(validateAddressString(del)).toBe(true)
363-
})
364-
365-
test('it should invalidate incorrect filecoin addresses', () => {
366-
expect(validateAddressString(del.slice(0, -1))).toBe(false)
337+
describe('FEVM', () => {
338+
const eth = '0x52963EF50e27e06D72D59fcB4F3c2a687BE3cfEf'
339+
const ethId01 = '0xff00000000000000000000000000000000000001'
340+
const ethId0100 = '0xff00000000000000000000000000000000000064'
341+
const ethId05088 = '0xff000000000000000000000000000000000013e0'
342+
const ethInvalid = '0x8Ba1f109551bD432803012645Ac136ddd64DBa72'
343+
const ethIcap = 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'
344+
const t01 = 't01'
345+
const t0100 = 't0100'
346+
const t05088 = 't05088'
347+
const t1 = 't16xlkjp3dcfrsb257duoqfgj7glo2uvvgxyy4gmy'
348+
const t2 = 't2e467euxin5y6vsmiw4ts3l4cme4zio4cvfx5b5a'
349+
const t3 =
350+
't3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a'
351+
const t410f = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa'
352+
const t410fIdMask = 't410f74aaaaaaaaaaaaaaaaaaaaaaaaaaaaabvo5mkdi'
353+
const t410fShort = 't410fkkld55ioe7qg24wvt7fu6pbkndgcenb6'
354+
const t410fLong = 't410fkkld55ioe7qg24wvt7fu6pbknb56ht7pebagbaf3x4ox2'
355+
const t411f = 't411fkkld55ioe7qg24wvt7fu6pbknb56ht7poxmy4mq'
356+
357+
test('decode f4 addresses', () => {
358+
expect(decode(t410f).toString()).toBe(t410f)
359+
})
360+
361+
test('delegatedFromEthAddress', () => {
362+
expect(delegatedFromEthAddress(eth, CoinType.TEST)).toBe(t410f)
363+
expect(() => delegatedFromEthAddress(ethId01, CoinType.TEST)).toThrow()
364+
})
365+
366+
test('ethAddressFromDelegated', () => {
367+
expect(() => ethAddressFromDelegated(eth)).toThrow()
368+
expect(() => ethAddressFromDelegated(t01)).toThrow()
369+
expect(() => ethAddressFromDelegated(t1)).toThrow()
370+
expect(() => ethAddressFromDelegated(t2)).toThrow()
371+
expect(() => ethAddressFromDelegated(t3)).toThrow()
372+
expect(ethAddressFromDelegated(t410f)).toBe(eth)
373+
expect(() => ethAddressFromDelegated(t410fIdMask)).toThrow()
374+
expect(() => ethAddressFromDelegated(t410fShort)).toThrow()
375+
expect(() => ethAddressFromDelegated(t410fLong)).toThrow()
376+
expect(() => ethAddressFromDelegated(t411f)).toThrow()
377+
})
378+
379+
test('isEthAddress', () => {
380+
expect(isEthAddress(eth)).toBe(true)
381+
expect(isEthAddress(ethId01)).toBe(true)
382+
expect(isEthAddress(ethId0100)).toBe(true)
383+
expect(isEthAddress(ethId05088)).toBe(true)
384+
expect(isEthAddress(ethInvalid)).toBe(false)
385+
expect(isEthAddress(ethIcap)).toBe(false)
386+
expect(isEthAddress(t01)).toBe(false)
387+
expect(isEthAddress(t1)).toBe(false)
388+
expect(isEthAddress(t2)).toBe(false)
389+
expect(isEthAddress(t3)).toBe(false)
390+
expect(isEthAddress(t410f)).toBe(false)
391+
})
392+
393+
test('isEthIdMaskAddress', () => {
394+
expect(isEthIdMaskAddress(eth)).toBe(false)
395+
expect(isEthIdMaskAddress(ethId01)).toBe(true)
396+
expect(isEthIdMaskAddress(ethId0100)).toBe(true)
397+
expect(isEthIdMaskAddress(ethId05088)).toBe(true)
398+
})
399+
400+
test('idFromEthAddress', () => {
401+
expect(() => idFromEthAddress(eth, CoinType.TEST)).toThrow()
402+
expect(idFromEthAddress(ethId01, CoinType.TEST)).toBe(t01)
403+
expect(idFromEthAddress(ethId0100, CoinType.TEST)).toBe(t0100)
404+
expect(idFromEthAddress(ethId05088, CoinType.TEST)).toBe(t05088)
405+
})
406+
407+
test('ethAddressFromID', () => {
408+
expect(ethAddressFromID(t01)).toBe(ethId01)
409+
expect(ethAddressFromID(t0100)).toBe(ethId0100)
410+
expect(ethAddressFromID(t05088)).toBe(ethId05088)
411+
expect(() => ethAddressFromID(t1)).toThrow()
412+
expect(() => ethAddressFromID(t2)).toThrow()
413+
expect(() => ethAddressFromID(t3)).toThrow()
414+
expect(() => ethAddressFromID(t410f)).toThrow()
415+
})
416+
417+
test('it should validate correct filecoin addresses', () => {
418+
expect(validateAddressString(t410f)).toBe(true)
419+
})
420+
421+
test('it should invalidate incorrect filecoin addresses', () => {
422+
expect(validateAddressString(t410f.slice(0, -1))).toBe(false)
423+
})
367424
})
368425
})

packages/filecoin-address/src/index.ts

+23-6
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ export function newDelegatedEthAddress(
221221
ethAddr: EthAddress,
222222
coinType?: CoinType
223223
): Address {
224-
if (!ethers.isAddress(ethAddr)) throw new Error('Invalid Ethereum address')
224+
if (!isEthAddress(ethAddr)) throw new Error('Invalid Ethereum address')
225+
if (isEthIdMaskAddress(ethAddr))
226+
throw new Error('Cannot convert ID mask to delegated address')
225227

226228
return newDelegatedAddress(
227229
DelegatedNamespace.EVM,
@@ -387,8 +389,6 @@ export function delegatedFromEthAddress(
387389
ethAddr: EthAddress,
388390
coinType: CoinType = defaultCoinType
389391
): string {
390-
if (isEthIdMaskAddress(ethAddr))
391-
throw new Error('Cannot convert ID mask address to delegated')
392392
return newDelegatedEthAddress(ethAddr, coinType).toString()
393393
}
394394

@@ -397,23 +397,40 @@ export function delegatedFromEthAddress(
397397
*/
398398

399399
export function ethAddressFromDelegated(delegated: string): EthAddress {
400-
const ethAddress = `0x${decode(delegated).subAddrHex}`
401-
return ethers.getAddress(ethAddress) as EthAddress // Adds checksum
400+
const { namespace, subAddrHex } = decode(delegated)
401+
if (namespace !== DelegatedNamespace.EVM)
402+
throw new Error(
403+
`Expected namespace ${DelegatedNamespace.EVM}, found ${namespace}`
404+
)
405+
406+
// Add checksum
407+
const ethAddress = ethers.getAddress(`0x${subAddrHex}`) as EthAddress
408+
409+
// Prevent returning an ID mask address
410+
if (isEthIdMaskAddress(ethAddress))
411+
throw new Error('Delegated address invalid, represented ID mask address')
412+
413+
return ethAddress
402414
}
403415

404416
/**
405417
* isEthAddress determines whether the input is an Ethereum address
406418
*/
407419

408420
export function isEthAddress(address: string): address is EthAddress {
409-
return ethers.isAddress(address) && Number(address) !== 0
421+
return (
422+
ethers.isHexString(address) &&
423+
ethers.isAddress(address) &&
424+
Number(address) !== 0
425+
)
410426
}
411427

412428
/**
413429
* isEthIdMaskAddress determines whether the input is an Ethereum ID mask address
414430
*/
415431

416432
export function isEthIdMaskAddress(ethAddr: EthAddress): boolean {
433+
if (!isEthAddress(ethAddr)) return false
417434
const bytes = ethers.getBytes(ethAddr)
418435
const prefix = bytes.slice(0, ethIdMaskPrefixLength)
419436
return uint8arrays.equals(prefix, ethIdMaskPrefix)

0 commit comments

Comments
 (0)