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

Feature: Add client input validation #278

Merged
merged 7 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modules/client-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ TEMPLATE:
-->

## [UPCOMING]
### Added

- Add common schemas for validations

## 1.6.0

### Added

Expand Down
3 changes: 2 additions & 1 deletion modules/client-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@ethersproject/providers": "^5.5.0",
"@ethersproject/wallet": "^5.6.0",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0"
"graphql-request": "^4.3.0",
"yup": "^1.2.0"
}
}
29 changes: 29 additions & 0 deletions modules/client-common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { activeContractsList as activeContractsListV1_0_0 } from "@aragon/osx-et
import { ProposalMetadata, SupportedNetwork, SupportedVersion } from "./types";
import { NetworkDeployment } from "./internal";
import { Network } from "@ethersproject/networks";
import { keccak256 } from "@ethersproject/keccak256";
import { toUtf8Bytes } from "@ethersproject/strings";

/** Timeout that will be applied to operations involving
* many fetch requests that could take a long time */
Expand Down Expand Up @@ -344,3 +346,30 @@ export const ADDITIONAL_NETWORKS: Network[] = [
chainId: 31337,
},
];

const Permissions = {
UPGRADE_PERMISSION: "UPGRADE_PERMISSION",
SET_METADATA_PERMISSION: "SET_METADATA_PERMISSION",
EXECUTE_PERMISSION: "EXECUTE_PERMISSION",
WITHDRAW_PERMISSION: "WITHDRAW_PERMISSION",
SET_SIGNATURE_VALIDATOR_PERMISSION: "SET_SIGNATURE_VALIDATOR_PERMISSION",
SET_TRUSTED_FORWARDER_PERMISSION: "SET_TRUSTED_FORWARDER_PERMISSION",
ROOT_PERMISSION: "ROOT_PERMISSION",
CREATE_VERSION_PERMISSION: "CREATE_VERSION_PERMISSION",
REGISTER_PERMISSION: "REGISTER_PERMISSION",
REGISTER_DAO_PERMISSION: "REGISTER_DAO_PERMISSION",
REGISTER_ENS_SUBDOMAIN_PERMISSION: "REGISTER_ENS_SUBDOMAIN_PERMISSION",
MINT_PERMISSION: "MINT_PERMISSION",
MERKLE_MINT_PERMISSION: "MERKLE_MINT_PERMISSION",
MODIFY_ALLOWLIST_PERMISSION: "MODIFY_ALLOWLIST_PERMISSION",
SET_CONFIGURATION_PERMISSION: "SET_CONFIGURATION_PERMISSION",
};

const PermissionIds = Object.entries(Permissions).reduce(
(acc, [k, v]) => ({ ...acc, [k + "_ID"]: keccak256(toUtf8Bytes(v)) }),
{} as { [k: string]: string },
);
Object.freeze(Permissions);
export { Permissions };
Object.freeze(PermissionIds);
export { PermissionIds };
1 change: 1 addition & 0 deletions modules/client-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./context";
export * from "./constants";
export * from "./types";
export * from "./utils";
export * from "./schemas";
1 change: 1 addition & 0 deletions modules/client-common/src/internal/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ANY_ADDRESS = "0xffffffffffffffffffffffffffffffffffffffff";
99 changes: 99 additions & 0 deletions modules/client-common/src/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
InvalidAddressOrEnsError,
InvalidCidError,
InvalidContractAbiError,
InvalidParameter,
InvalidSubdomainError,
isEnsName,
isIpfsUri,
isSubdomain,
} from "@aragon/sdk-common";
import { array, mixed, number, object, string } from "yup";
import { isAddress } from "@ethersproject/address";
import { ANY_ADDRESS } from "./internal/constants";

export const BigintSchema = mixed().test(
"isBigint",
new InvalidParameter("bigint").message,
(value) => typeof value === "bigint",
);
export const AddressOrEnsSchema = string().notRequired().test(
"isAddressOrEns",
new InvalidAddressOrEnsError().message,
(value) => value ? isAddress(value) || isEnsName(value) : true,
);
export const AddressOrEnsWithoutAnySchema = string().notRequired().test(
"isAddressOrEnsWithoutAny",
new InvalidAddressOrEnsError().message,
(value) => value ? (isAddress(value) || isEnsName(value)) && value !== ANY_ADDRESS : true
);
export const VersionTagSchema = object({
build: number().moreThan(0).required(),
release: number().moreThan(0).required(),
});
export const AbiSchema = array().notRequired().test(
"isValidAbi",
new InvalidContractAbiError().message,
// TODO: validate abi
() => true,
);
export const Uint8ArraySchema = mixed().test(
"isUint8Array",
new InvalidParameter("Uint8Array").message,
(value) => value ? value instanceof Uint8Array : true,
);
export const IpfsUriSchema = string().test(
"isIpfsUri",
new InvalidCidError().message,
(value) => value ? isIpfsUri(value) : true,
);
export const SubdomainSchema = string().test(
"isSubdomain",
new InvalidSubdomainError().message,
(value) => value ? isSubdomain(value) : true,
);

export const PaginationSchema = object({
skip: number().min(0).notRequired(),
limit: number().min(1).notRequired(),
direction: string().oneOf(["asc", "desc"]).notRequired(),
});

export const PrepareUninstallationSchema = object({
daoAddressOrEns: AddressOrEnsSchema.required(),
pluginAddress: AddressOrEnsSchema.required(),
pluginInstallationIndex: number().notRequired().min(0),
uninstallationParams: array().notRequired(),
uninstallationAbi: AbiSchema.notRequired(),
});
export const MultiTargetPermissionSchema = object({
operation: number().required().oneOf([0, 1, 2]),
permissionId: string().required(),
where: AddressOrEnsWithoutAnySchema.required(),
who: AddressOrEnsWithoutAnySchema.required(),
condition: string().notRequired(),
});

export const PrepareInstallationSchema = object({
daoAddressOrEns: AddressOrEnsSchema.required(),
pluginRepo: AddressOrEnsSchema.required(),
version: VersionTagSchema.notRequired(),
installationParams: array().notRequired(),
installationAbi: AbiSchema.notRequired(),
});

export const PluginInstallItemSchema = object({
id: AddressOrEnsSchema.required(),
data: Uint8ArraySchema.required(),
});

export const ApplyUninstallationSchema = object({
pluginAddress: AddressOrEnsSchema.required(),
pluginRepo: AddressOrEnsSchema.required(),
versionTag: VersionTagSchema.required(),
permissions: array(MultiTargetPermissionSchema).required(),
});

export const ApplyInstallationSchema = ApplyUninstallationSchema.concat(object({
helpers: array(AddressOrEnsSchema).required(),
}));
21 changes: 21 additions & 0 deletions modules/client-common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ export type DecodedApplyInstallationParams = ApplyInstallationParamsBase & {
helpersHash: string;
};

/* Uninstallation */
export type PrepareUninstallationParams = {
daoAddressOrEns: string;
pluginAddress: string;
pluginInstallationIndex?: number;
uninstallationParams?: any[];
uninstallationAbi?: string[];
};
export enum PrepareUninstallationSteps {
PREPARING = "preparing",
DONE = "done",
}
export type PrepareUninstallationStepValue =
| { key: PrepareUninstallationSteps.PREPARING; txHash: string }
| {
key: PrepareUninstallationSteps.DONE;
} & ApplyUninstallationParams;

export type ApplyUninstallationParams = ApplyInstallationParamsBase;
export type DecodedApplyUninstallationParams = ApplyInstallationParamsBase;

export type VersionTag = {
build: number;
release: number;
Expand Down
21 changes: 21 additions & 0 deletions modules/client-common/test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,24 @@ export const TEST_ABI: MetadataAbiInput[] = [
],
},
];


export const TEST_ADDRESS = "0x0000000000000000000000000000000000000001";
export const TEST_INVALID_ADDRESS =
"0x000000000000000000000000000000000000000P";

export const TEST_ENS_NAME = "test.eth";
export const TEST_INVALID_ENS_NAME = "test.invalid";

export const TEST_IPFS_URI_V0 =
"ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR";
export const TEST_IPFS_URI_V1 =
"ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
export const TEST_INVALID_IPFS_URI =
"ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR-invalid";

export const TEST_HTTP_URI = "https://test.com";
export const TEST_INVALID_HTTP_URI = "https://te?st.com-invalid";

export const TEST_SUBDOMAIN = "test";
export const TEST_INVALID_SUBDOMAIN = "test.invalid";
Loading