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

Zod API response validation #94

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
5 changes: 5 additions & 0 deletions .changeset/sweet-rocks-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tenderly/sdk': patch
---

add zod api response validation
13 changes: 13 additions & 0 deletions lib/common.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from 'zod';
import { Network } from './common.types';

export const pathSchema = z.string();

export const web3AddressSchema = z.string().length(42).startsWith('0x');

export const tenderlyConfigurationSchema = z.object({
accountName: z.string(),
projectName: z.string(),
accessKey: z.string(),
network: z.union([z.nativeEnum(Network), z.number()]),
});
15 changes: 6 additions & 9 deletions lib/types.ts → lib/common.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export type Path = string;
export type Web3Address = string;
import { z } from 'zod';
import { pathSchema, tenderlyConfigurationSchema, web3AddressSchema } from './common.schema';

export type Path = z.infer<typeof pathSchema>;
export type Web3Address = z.infer<typeof web3AddressSchema>;
export type TenderlyConfiguration = z.infer<typeof tenderlyConfigurationSchema>;

export enum Network {
MAINNET = 1,
Expand Down Expand Up @@ -39,13 +43,6 @@ export enum Network {
SEPOLIA = 11155111,
}

export type TenderlyConfiguration = {
accountName: string;
projectName: string;
accessKey: string;
network: Network | number;
};

// helper types
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
export type EmptyObject = Record<PropertyKey, never>;
2 changes: 1 addition & 1 deletion lib/core/ApiClientProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiClient, ApiVersion } from './ApiClient';
import { EmptyObject } from '../types';
import { EmptyObject } from '../common.types';

export class ApiClientProvider {
static instance: ApiClientProvider;
Expand Down
2 changes: 1 addition & 1 deletion lib/core/Tenderly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TenderlyConfiguration } from '../types';
import { TenderlyConfiguration } from '../common.types';
import { WalletRepository, ContractRepository } from '../repositories';
import { Simulator } from '../executors';
import { VerificationRequest } from '../repositories/contracts/contracts.types';
Expand Down
2 changes: 1 addition & 1 deletion lib/errors/Error.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AxiosError, isAxiosError } from 'axios';
import { WithRequired } from '../types';
import { WithRequired } from '../common.types';

export interface TenderlyError {
readonly id?: string;
Expand Down
4 changes: 2 additions & 2 deletions lib/executors/Simulator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiClient } from '../core/ApiClient';
import { Web3Address } from '../types';
import { Web3Address } from '../common.types';
import {
SimulationParameters,
SimulationRequest,
Expand All @@ -16,7 +16,7 @@ import {
SimulateBundleResponse,
SimulationRequestOverride,
} from './Simulator.types';
import { TenderlyConfiguration } from '../types';
import { TenderlyConfiguration } from '../common.types';
import { handleError, EncodingError } from '../errors';
import { ApiClientProvider } from '../core/ApiClientProvider';
import { isTenderlyAxiosError } from '../errors/Error.types';
Expand Down
2 changes: 1 addition & 1 deletion lib/executors/Simulator.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Web3Address } from '../types';
import { Web3Address } from '../common.types';

export type TransactionParameters = {
from: Web3Address;
Expand Down
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './core';
export * from './types';
export * from './common.types';
export * from './errors';
export * from './helpers';

Expand Down
2 changes: 1 addition & 1 deletion lib/repositories/contracts/contracts.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Network, Path, TenderlyConfiguration, Web3Address } from '../../types';
import { Network, Path, TenderlyConfiguration, Web3Address } from '../../common.types';
import { Repository } from '../Repository';
import { ApiClient } from '../../core/ApiClient';
import {
Expand Down
188 changes: 188 additions & 0 deletions lib/repositories/contracts/contracts.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { z } from 'zod';
import { Network } from '../../common.types';
import { web3AddressSchema } from '../../common.schema';

export const contractSchema = z.object({
address: web3AddressSchema,
network: z.nativeEnum(Network),
displayName: z.string().optional(),
tags: z.array(z.string()).optional(),
});

export const contractRequestSchema = z.object({
address: web3AddressSchema,
network_id: z.string(),
display_name: z.string().optional(),
});

export const getByParamsSchema = z.object({
tags: z.array(z.string()).optional(),
displayNames: z.array(z.string()).optional(),
});

const internalContractSchema = z.object({
id: z.string(),
contract_id: z.string(),
balance: z.string(),
network_id: z.string(),
public: z.boolean(),
export: z.boolean(),
verified_by: z.string(),
verification_date: z.string().nullable(),
address: web3AddressSchema,
contract_name: z.string(),
ens_domain: z.string().nullable(),
type: z.string(),
standard: z.string(),
standards: z.array(z.string()),
token_data: z.object({
symbol: z.string(),
name: z.string(),
main: z.boolean(),
}),
evm_version: z.string(),
compiler_version: z.string(),
optimizations_used: z.boolean(),
optimization_runs: z.number(),
libraries: z.null(),
data: z.object({
main_contract: z.number(),
contract_info: z
.object({
id: z.number(),
path: z.string(),
name: z.string(),
source: z.string(),
abi: z.array(z.unknown()),
raw_abi: z.array(z.unknown()),
states: z.array(z.unknown()),
})
.nullable(),
creation_block: z.number(),
creation_tx: z.string(),
creator_address: web3AddressSchema,
created_at: z.string(),
number_of_watches: z.null(),
language: z.string(),
in_project: z.boolean(),
number_of_files: z.number(),
}),
account: z.object({
id: z.string(),
contract_id: z.string(),
balance: z.string(),
network_id: z.string(),
public: z.boolean(),
export: z.boolean(),
verified_by: z.string(),
verification_date: z.string().nullable(),
address: web3AddressSchema,
contract_name: z.string(),
ens_domain: z.string().nullable(),
type: z.string(),
standard: z.string(),
standards: z.array(z.string()),
evm_version: z.string(),
compiler_version: z.string(),
optimizations_used: z.boolean(),
optimization_runs: z.number(),
libraries: z.null(),
data: z.null(),
creation_block: z.number(),
creation_tx: z.string(),
creator_address: web3AddressSchema,
created_at: z.string(),
number_of_watches: z.null(),
language: z.string(),
in_project: z.boolean(),
number_of_files: z.number(),
}),
project_id: z.string(),
added_by_id: z.string(),
previous_versions: z.array(z.unknown()),
details_visible: z.boolean(),
include_in_transaction_listing: z.boolean(),
display_name: z.string(),
account_type: z.string(),
verification_type: z.string(),
added_at: z.string(),
});

export const updateContractRequestSchema = z.object({
displayName: z.string().optional(),
appendTags: z.array(z.string()).optional(),
});

export const solidityCompilerVersionsSchema = z
.string()
.regex(new RegExp('^v\\d+\\.\\d+\\.\\d+$'), {
message: 'Compiler version is not in supported format v[number].[number].[number]',
});

export const solcConfigSchema = z.object({
version: solidityCompilerVersionsSchema,
sources: z.record(
z.object({
content: z.string(),
}),
),
settings: z.unknown(),
});

export const tenderlySolcConfigLibrariesSchema = z.record(
z.object({
addresses: z.record(web3AddressSchema),
}),
);

export const verificationRequestSchema = z.object({
contractToVerify: z.string(),
solc: solcConfigSchema,
config: z.object({
mode: z.union([z.literal('private'), z.literal('public')]),
}),
});

export const bytecodeMismatchErrorResponseSchema = z.object({
contract_id: z.string(),
expected: z.string(),
got: z.string(),
similarity: z.number(),
assumed_reason: z.string(),
});

export const contractResponseSchema = z.object({
id: z.string(),
account_type: z.literal('contract'),
contract: internalContractSchema,
display_name: z.string(),
tags: z
.array(
z.object({
tag: z.string(),
}),
)
.optional(),
});

export const compilationErrorResponseSchema = z.object({
source_location: z.object({
file: z.string(),
start: z.number(),
end: z.number(),
}),
error_ype: z.string(),
component: z.string(),
message: z.string(),
formatted_message: z.string(),
});

export const verificationResponseSchema = z.object({
compilation_errors: z.array(compilationErrorResponseSchema),
results: z.array(
z.object({
bytecode_mismatch_error: bytecodeMismatchErrorResponseSchema,
verified_contract: internalContractSchema,
}),
),
});
Loading