Skip to content

Commit

Permalink
refactor: add superstruct and validate request/response utils (#305)
Browse files Browse the repository at this point in the history
* chore: add superstruct

* chore: add rpc request validator utils

* chore: add test for rpc utils
  • Loading branch information
stanleyyconsensys authored Jul 31, 2024
1 parent e6f76f8 commit 03bdbec
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/starknet-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"ethereum-unit-converter": "^0.0.17",
"ethers": "^5.5.1",
"starknet": "6.11.0",
"starknet_v4.22.0": "npm:[email protected]"
"starknet_v4.22.0": "npm:[email protected]",
"superstruct": "^2.0.2"
},
"devDependencies": {
"@babel/preset-typescript": "^7.23.3",
Expand Down
51 changes: 51 additions & 0 deletions packages/starknet-snap/src/utils/rpc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { InvalidParamsError, SnapError } from '@metamask/snaps-sdk';
import { object } from 'superstruct';
import type { Struct } from 'superstruct';

import { validateRequest, validateResponse } from './rpc';
import { AddressStruct } from './superstruct';

const struct = object({
signerAddress: AddressStruct,
});

const params = {
signerAddress:
'0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd',
};

describe('validateRequest', () => {
it('does not throw error if the request is valid', () => {
expect(() =>
validateRequest(params, struct as unknown as Struct),
).not.toThrow();
});

it('throws `InvalidParamsError` if the request is invalid', () => {
const requestParams = {
signerAddress: 1234,
};

expect(() =>
validateRequest(requestParams, struct as unknown as Struct),
).toThrow(InvalidParamsError);
});
});

describe('validateResponse', () => {
it('does not throw error if the response is valid', () => {
expect(() =>
validateResponse(params, struct as unknown as Struct),
).not.toThrow();
});

it('throws `Invalid Response` error if the response is invalid', () => {
const response = {
signerAddress: 1234,
};

expect(() =>
validateResponse(response, struct as unknown as Struct),
).toThrow(new SnapError('Invalid Response'));
});
});
35 changes: 35 additions & 0 deletions packages/starknet-snap/src/utils/rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { InvalidParamsError, SnapError } from '@metamask/snaps-sdk';
import type { Struct } from 'superstruct';
import { assert } from 'superstruct';

/**
* Validates that the request parameters conform to the expected structure defined by the provided struct.
*
* @template Params - The expected structure of the request parameters.
* @param requestParams - The request parameters to validate.
* @param struct - The expected structure of the request parameters.
* @throws {InvalidParamsError} If the request parameters do not conform to the expected structure.
*/
export function validateRequest<Params>(requestParams: Params, struct: Struct) {
try {
assert(requestParams, struct);
} catch (error) {
throw new InvalidParamsError(error.message) as unknown as Error;
}
}

/**
* Validates that the response conforms to the expected structure defined by the provided struct.
*
* @template Params - The expected structure of the response.
* @param response - The response to validate.
* @param struct - The expected structure of the response.
* @throws {SnapError} If the response does not conform to the expected structure.
*/
export function validateResponse<Params>(response: Params, struct: Struct) {
try {
assert(response, struct);
} catch (error) {
throw new SnapError('Invalid Response') as unknown as Error;
}
}
21 changes: 21 additions & 0 deletions packages/starknet-snap/src/utils/superstruct.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { StructError, assert } from 'superstruct';

import { AddressStruct } from './superstruct';

describe('AddressStruct', () => {
it.each([
'0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd',
'4882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd',
])('does not throw error if the address is valid - %s', (address) => {
expect(() => assert(address, AddressStruct)).not.toThrow();
});

it.each([
// non hex string - charactor is not within [0-9a-fA-F]
'0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f2zzz',
// invalid length - 66/63 chars expected
'372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd',
])('throws error if the address is invalid - %s', (address) => {
expect(() => assert(address, AddressStruct)).toThrow(StructError);
});
});
22 changes: 22 additions & 0 deletions packages/starknet-snap/src/utils/superstruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { validateAndParseAddress } from 'starknet';
import { refine, string } from 'superstruct';

export const AddressStruct = refine(
string(),
'AddressStruct',
(value: string) => {
try {
const trimmedAddress = value.toString().replace(/^0x0?/u, '');

// Check if the address is 63 characters long, the expected length of a StarkNet address exclude 0x0.
if (trimmedAddress.length !== 63) {
return 'Invalid address format';
}

validateAndParseAddress(trimmedAddress);
} catch (error) {
return 'Invalid address format';
}
return true;
},
);
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,7 @@ __metadata:
sinon-chai: ^3.7.0
starknet: 6.11.0
starknet_v4.22.0: "npm:[email protected]"
superstruct: ^2.0.2
ts-jest: ^29.1.0
ts-node: ^10.9.2
typescript: ^4.7.4
Expand Down Expand Up @@ -25626,6 +25627,13 @@ __metadata:
languageName: node
linkType: hard

"superstruct@npm:^2.0.2":
version: 2.0.2
resolution: "superstruct@npm:2.0.2"
checksum: a5f75b72cb8b14b86f4f7f750dae8c5ab0e4e1d92414b55e7625bae07bbcafad81c92486e7e32ccacd6ae1f553caff2b92a50ff42ad5093fd35b9cb7f4e5ec86
languageName: node
linkType: hard

"supports-color@npm:8.1.1, supports-color@npm:^8.0.0":
version: 8.1.1
resolution: "supports-color@npm:8.1.1"
Expand Down

0 comments on commit 03bdbec

Please sign in to comment.