-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Convert CAIP-10 address validation to schema (#2110)
* Don't allow empty/hex strings to be validated as numeric strings * Convert CAIP-10 address validation to schema --------- Co-authored-by: Hector Gómez Varela <[email protected]>
- Loading branch information
1 parent
20e8be8
commit 83f24e9
Showing
6 changed files
with
178 additions
and
137 deletions.
There are no files selected for viewing
138 changes: 138 additions & 0 deletions
138
src/routes/safes/entities/caip-10-addresses.entity.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { Caip10AddressesSchema } from '@/routes/safes/entities/caip-10-addresses.entity'; | ||
import { faker } from '@faker-js/faker'; | ||
import { getAddress } from 'viem'; | ||
|
||
describe('Caip10AddressesSchema', () => { | ||
const caip10Addresses = faker.helpers.multiple( | ||
() => { | ||
return { | ||
chainId: faker.string.numeric(), | ||
address: faker.finance.ethereumAddress(), | ||
}; | ||
}, | ||
{ | ||
count: { min: 1, max: 5 }, | ||
}, | ||
); | ||
|
||
it('should parse CAIP-10 addresses', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${address.chainId}:${address.address}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(result.success).toBe(true); | ||
}); | ||
|
||
it('should checksum addresses', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${address.chainId}:${address.address}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect( | ||
result.success && result.data.map(({ address }) => address), | ||
).toStrictEqual( | ||
caip10Addresses.map(({ address }) => { | ||
return getAddress(address); | ||
}), | ||
); | ||
}); | ||
|
||
it('should throw for incorrectly formatted CAIP-10 addresses', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${address.chainId}-${address.address}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(!result.success && result.error.issues).toStrictEqual( | ||
Array.from({ length: caip10Addresses.length }).flatMap(() => [ | ||
{ | ||
code: 'custom', | ||
message: 'Invalid base-10 numeric string', | ||
path: ['chainId'], | ||
}, | ||
{ | ||
code: 'invalid_type', | ||
expected: 'string', | ||
message: 'Required', | ||
path: ['address'], | ||
received: 'undefined', | ||
}, | ||
]), | ||
); | ||
}); | ||
|
||
it('should throw for non-numerical chainIds', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${faker.string.hexadecimal()}:${address.address}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(!result.success && result.error.issues).toStrictEqual( | ||
Array.from({ length: caip10Addresses.length }).map(() => ({ | ||
code: 'custom', | ||
message: 'Invalid base-10 numeric string', | ||
path: ['chainId'], | ||
})), | ||
); | ||
}); | ||
|
||
it('should throw for invalid addresses', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${address.chainId}:${faker.string.numeric()}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(!result.success && result.error.issues).toStrictEqual( | ||
Array.from({ length: caip10Addresses.length }).flatMap(() => [ | ||
{ | ||
code: 'custom', | ||
message: 'Invalid address', | ||
path: ['address'], | ||
}, | ||
]), | ||
); | ||
}); | ||
|
||
it('should throw for missing chainIds', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `:${address.address}`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(!result.success && result.error.issues).toStrictEqual( | ||
Array.from({ length: caip10Addresses.length }).flatMap(() => [ | ||
{ | ||
code: 'custom', | ||
message: 'Invalid base-10 numeric string', | ||
path: ['chainId'], | ||
}, | ||
]), | ||
); | ||
}); | ||
|
||
it('should throw for missing addresses', () => { | ||
const caip10AddressesString = caip10Addresses | ||
.map((address) => `${address.chainId}:`) | ||
.join(','); | ||
|
||
const result = Caip10AddressesSchema.safeParse(caip10AddressesString); | ||
|
||
expect(!result.success && result.error.issues).toStrictEqual( | ||
Array.from({ length: caip10Addresses.length }).flatMap(() => [ | ||
{ | ||
code: 'custom', | ||
message: 'Invalid address', | ||
path: ['address'], | ||
}, | ||
]), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { z } from 'zod'; | ||
import { asError } from '@/logging/utils'; | ||
import { AddressSchema } from '@/validation/entities/schemas/address.schema'; | ||
import { NumericStringSchema } from '@/validation/entities/schemas/numeric-string.schema'; | ||
|
||
export const Caip10AddressesSchema = z.string().transform((str, ctx) => { | ||
return str.split(',').map((item) => { | ||
try { | ||
const [chainId, address] = item.split(':'); | ||
return z | ||
.object({ | ||
chainId: NumericStringSchema, | ||
address: AddressSchema, | ||
}) | ||
.parse({ chainId, address }); | ||
} catch (e) { | ||
if (e instanceof z.ZodError) { | ||
e.issues.forEach((issue) => { | ||
ctx.addIssue(issue); | ||
}); | ||
} else { | ||
ctx.addIssue({ | ||
code: z.ZodIssueCode.custom, | ||
message: asError(e).message, | ||
}); | ||
} | ||
return z.NEVER; | ||
} | ||
}); | ||
}); | ||
|
||
export type Caip10Addresses = z.infer<typeof Caip10AddressesSchema>; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters