Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: param validation for all RPC methods #29

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

andreabadesso
Copy link
Collaborator

@andreabadesso andreabadesso commented Jan 15, 2025

Acceptance Criteria

  • We should include parameter validation for all RPC methods
  • If a rpc request fails validation, the handler should throw a specific error

Checklist

  • If you are requesting a merge into master, confirm this code is production-ready and can be included in future releases as soon as it gets merged
  • Make sure either the unit tests and/or the QA tests are capable of testing the new features
  • Make sure you do not include new dependencies in the project unless strictly necessary and do not include dev-dependencies as production ones. More dependencies increase the possibility of one of them being hijacked and affecting us.

@andreabadesso andreabadesso added the enhancement New feature or request label Jan 15, 2025
@andreabadesso andreabadesso self-assigned this Jan 15, 2025
@andreabadesso andreabadesso changed the title feat: added validation for createToken and sendNanoContractTx feat: param validation for all RPC methods Jan 20, 2025
@andreabadesso andreabadesso requested review from luislhl and removed request for tuliomir January 23, 2025 12:41
@andreabadesso andreabadesso force-pushed the feat/parameter-validation branch from a6f68cf to 5fcf239 Compare January 29, 2025 16:19
@andreabadesso andreabadesso requested a review from luislhl January 30, 2025 13:18
@andreabadesso andreabadesso requested review from r4mmer and removed request for pedroferreira1 January 30, 2025 16:34
name: z.string().min(1),
symbol: z.string().min(1),
amount: z.number().positive(),
address: z.string().nullable().optional(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use .nullish() instead of .nullable().optional()

amount: z.number().positive(),
address: z.string().nullable().optional(),
change_address: z.string().nullable().optional(),
create_mint: z.boolean().optional().default(true),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
create_mint: z.boolean().optional().default(true),
create_mint: z.boolean().default(true),

The deault already makes the field optional.

> foo = z.object({ bar: z.number().default(123) })
> foo.parse({})
{ bar: 123 }

name: params.name,
symbol: params.symbol,
amount: params.amount,
address: params.address ?? null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the null as default?

symbol: params.symbol,
amount: params.amount,
address: params.address ?? null,
changeAddress: params.change_address ?? null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add a transform on the schema.

const foo = z.object({
  // schema definition
}).transform(input => ({
  name: input.name,
  address: input.address,
  changeAddress: input.change_address,
  // ... other keys
}));

This way when we parse the object it already comes out as camelcase.

Comment on lines +118 to +132
params.name,
params.symbol,
params.amount,
{
changeAddress: params.change_address,
address: params.address,
createMint: params.create_mint,
mintAuthorityAddress: params.mint_authority_address,
allowExternalMintAuthorityAddress: params.allow_external_mint_authority_address,
createMelt: params.create_melt,
meltAuthorityAddress: params.melt_authority_address,
allowExternalMeltAuthorityAddress: params.allow_external_melt_authority_address,
data: params.data,
pinCode: pinCodeResponse.data.pinCode,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(non-blocking): Maybe we should add this to the transform.

We could transform the data to be a params.options with these fields, which would make the call something like:
wallet.createNewToken(params.name, params.symbol, params.amount, params.options);

This would make any changes (new arguments, etc.) only need to be changed on the schema and transform.
But this is just a suggestion, maybe the flat object is better for testing or using elsewhere.

Comment on lines +28 to +41
}).refine(
(data) => {
if (data.type === 'index') {
return data.index !== undefined;
}
if (data.type === 'full_path') {
return data.full_path !== undefined;
}
return true;
},
{
message: "index is required when type is 'index', full_path is required when type is 'full_path'",
}
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}).refine(
(data) => {
if (data.type === 'index') {
return data.index !== undefined;
}
if (data.type === 'full_path') {
return data.full_path !== undefined;
}
return true;
},
{
message: "index is required when type is 'index', full_path is required when type is 'full_path'",
}
);
}).refine(
(data) => {
return (data.type !== 'index' || data.index !== undefined);
},
{
message: "index is required when type is 'index'",
}
).refine(
(data) => {
return (data.type !== 'full_path' || data.full_path !== undefined);
},
{
message: "full_path is required when type is 'full_path'",
}
);

We can chain the refines so the message is more descriptive of the case

const getAddressSchema = z.object({
type: z.enum(['first_empty', 'full_path', 'index', 'client']),
index: z.number().int().nonnegative().optional(),
full_path: z.string().min(1).optional(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know fullpath is not implemented, but are we going to expect a m/44'/280'/0'/0/index?
If so, maybe we should validate this here, or at least comment that we should add validation later

Comment on lines +23 to +26
const getAddressSchema = z.object({
type: z.enum(['first_empty', 'full_path', 'index', 'client']),
index: z.number().int().nonnegative().optional(),
full_path: z.string().min(1).optional(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This schema of type + optional fields may be better expressed as a discriminated union.
This would make the refines obsolete since in the index type the index argument would be required.

Comment on lines +49 to +50
try {
const params = getBalanceSchema.parse(rpcRequest.params);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't want to put the entire function inside a try catch and handle parsing errors at the end you could use safeParse which returns a success flag with the data or error.

What do you think?

@@ -20,7 +21,20 @@ import {
RpcResponse,
SendNanoContractTxLoadingFinishedTrigger,
} from '../types';
import { PromptRejectedError, SendNanoContractTxError } from '../errors';
import { PromptRejectedError, SendNanoContractTxError, InvalidParamsError } from '../errors';
import { NanoContractAction } from '@hathor/wallet-lib/lib/nano_contracts/types';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should expose these types in the lib index instead of importing from the internal path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: In Review (WIP)
Development

Successfully merging this pull request may close these issues.

3 participants