From 0c644e5eb66791b55f6b154e72894fe63a1d6392 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 15:52:16 +0800 Subject: [PATCH 01/13] Add a Schema class to ops for value vaildation. --- packages/ops/src/utils/schema.ts | 105 +++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 packages/ops/src/utils/schema.ts diff --git a/packages/ops/src/utils/schema.ts b/packages/ops/src/utils/schema.ts new file mode 100644 index 000000000..d12e11461 --- /dev/null +++ b/packages/ops/src/utils/schema.ts @@ -0,0 +1,105 @@ +import { z } from 'zod'; + +// Component Zod Schema for validatiing configuration and parameters. +export abstract class Schema { + static readonly EMAIL = z.string().email(); + static readonly EMAIL_OPTIONAL = this.EMAIL.nullish().or(z.string().length(0)); + static readonly NON_EMPTY_STRING = z.string().trim().min(1); + static readonly ADDRESS = z.string().regex(/^(0x)?[0-9a-fA-F]{40,40}$/); + static readonly PERCENTAGE = z.number().int().positive().gt(0); + static readonly UPSIDE_VAULT_SPEC = z.union([this.ADDRESS, z.string().regex(/^self$/)]); + + static readonly CONFIG_SUPABASE_URL = z.object({ + services: z.object({ + supabase: z.object({ + url: z.string().url(), + }), + }), + }); + + static readonly CONFIG_SUPABASE_ADMIN = z.object({ + secret: z.object({ + SUPABASE_SERVICE_ROLE_KEY: z.string(), + }), + }); + + static readonly CONFIG_SUPABASE_ANONYMOUS = z.object({ + secret: z.object({ + SUPABASE_ANONYMOUS_KEY: z.string(), + }), + }); + + static readonly CONFIG_ETHERS_URL = z.object({ + services: z.object({ + ethers: z.object({ + url: z.string().url(), + }), + }), + }); + + static readonly CONFIG_API_URL = z.object({ + api: z.object({ + url: z.string().url(), + }), + }); + + static readonly CONFIG_APP_URL = z.object({ + app: z.object({ + url: z.string().url(), + }), + }); + + static readonly CONFIG_USER_ADMIN = z.object({ + users: z.object({ + admin: z.object({ + email_address: this.EMAIL, + }), + }), + secret: z.object({ + ADMIN_PASSWORD: this.NON_EMPTY_STRING, + }), + }); + + static readonly CONFIG_USER_BOB = z.object({ + users: z.object({ + bob: z.object({ + email_address: this.EMAIL, + }), + }), + secret: z.object({ + BOB_PASSWORD: this.NON_EMPTY_STRING, + }), + }); + + static readonly CONFIG_ADMIN_PRIVATE_KEY = z.object({ + secret: z.object({ + ADMIN_PRIVATE_KEY: this.NON_EMPTY_STRING, + }), + }); + + static readonly CONFIG_CRON = z.object({ + secret: z.object({ + CRON_SECRET: this.NON_EMPTY_STRING, + }), + }); + + static readonly CONFIG_EVM_ADDRESS = z.object({ + evm: z.object({ + address: z.object({ + owner: this.ADDRESS, + operator: this.ADDRESS, + custodian: this.ADDRESS, + treasury: this.ADDRESS, + activity_reward: this.ADDRESS, + }), + }), + }); + + static readonly CONFIG_OPERATION_CREATE_VAULT = z.object({ + operation: z.object({ + createVault: z.object({ + collateral_percentage: this.PERCENTAGE, + }), + }), + }); +} From 0c1e36a503252173bb4468021343cc2e5df161e0 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 15:53:05 +0800 Subject: [PATCH 02/13] Deleted the unreferenced `utils/abi.ts` file. --- packages/ops/src/utils/abi.ts | 1634 --------------------------------- 1 file changed, 1634 deletions(-) delete mode 100644 packages/ops/src/utils/abi.ts diff --git a/packages/ops/src/utils/abi.ts b/packages/ops/src/utils/abi.ts deleted file mode 100644 index 504e210b1..000000000 --- a/packages/ops/src/utils/abi.ts +++ /dev/null @@ -1,1634 +0,0 @@ -export const abi = [ - { - type: "constructor", - inputs: [ - { - name: "params", - type: "tuple", - internalType: "struct ICredbull.VaultParams", - components: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - { - name: "operator", - type: "address", - internalType: "address", - }, - { - name: "asset", - type: "address", - internalType: "contract IERC20", - }, - { - name: "token", - type: "address", - internalType: "contract IERC20", - }, - { - name: "shareName", - type: "string", - internalType: "string", - }, - { - name: "shareSymbol", - type: "string", - internalType: "string", - }, - { - name: "promisedYield", - type: "uint256", - internalType: "uint256", - }, - { - name: "depositOpensAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "depositClosesAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "redemptionOpensAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "redemptionClosesAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "custodian", - type: "address", - internalType: "address", - }, - { - name: "kycProvider", - type: "address", - internalType: "address", - }, - { - name: "maxCap", - type: "uint256", - internalType: "uint256", - }, - { - name: "depositThresholdForWhitelisting", - type: "uint256", - internalType: "uint256", - }, - ], - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "CUSTODIAN", - inputs: [], - outputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "DEFAULT_ADMIN_ROLE", - inputs: [], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "MAX_DECIMAL", - inputs: [], - outputs: [ - { - name: "", - type: "uint8", - internalType: "uint8", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "MIN_DECIMAL", - inputs: [], - outputs: [ - { - name: "", - type: "uint8", - internalType: "uint8", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "OPERATOR_ROLE", - inputs: [], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "VAULT_DECIMALS", - inputs: [], - outputs: [ - { - name: "", - type: "uint8", - internalType: "uint8", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "allowance", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - { - name: "spender", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "approve", - inputs: [ - { - name: "spender", - type: "address", - internalType: "address", - }, - { - name: "value", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "asset", - inputs: [], - outputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "balanceOf", - inputs: [ - { - name: "account", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "checkMaturity", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "checkMaxCap", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "checkWhitelist", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "checkWindow", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "convertToAssets", - inputs: [ - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "convertToShares", - inputs: [ - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "decimals", - inputs: [], - outputs: [ - { - name: "", - type: "uint8", - internalType: "uint8", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "deposit", - inputs: [ - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - { - name: "receiver", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "depositClosesAtTimestamp", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "depositOpensAtTimestamp", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "depositThresholdForWhitelisting", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "expectedAssetsOnMaturity", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "getRoleAdmin", - inputs: [ - { - name: "role", - type: "bytes32", - internalType: "bytes32", - }, - ], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "grantRole", - inputs: [ - { - name: "role", - type: "bytes32", - internalType: "bytes32", - }, - { - name: "account", - type: "address", - internalType: "address", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "hasRole", - inputs: [ - { - name: "role", - type: "bytes32", - internalType: "bytes32", - }, - { - name: "account", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "isMatured", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "kycProvider", - inputs: [], - outputs: [ - { - name: "", - type: "address", - internalType: "contract IKYCProvider", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "mature", - inputs: [], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "maxCap", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "maxDeposit", - inputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "maxMint", - inputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "maxRedeem", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "maxWithdraw", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "mint", - inputs: [ - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - { - name: "receiver", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "name", - inputs: [], - outputs: [ - { - name: "", - type: "string", - internalType: "string", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "pauseVault", - inputs: [], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "paused", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "previewDeposit", - inputs: [ - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "previewMint", - inputs: [ - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "previewRedeem", - inputs: [ - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "previewWithdraw", - inputs: [ - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "redeem", - inputs: [ - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - { - name: "receiver", - type: "address", - internalType: "address", - }, - { - name: "owner", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "redemptionClosesAtTimestamp", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "redemptionOpensAtTimestamp", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "renounceRole", - inputs: [ - { - name: "role", - type: "bytes32", - internalType: "bytes32", - }, - { - name: "callerConfirmation", - type: "address", - internalType: "address", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "revokeRole", - inputs: [ - { - name: "role", - type: "bytes32", - internalType: "bytes32", - }, - { - name: "account", - type: "address", - internalType: "address", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "supportsInterface", - inputs: [ - { - name: "interfaceId", - type: "bytes4", - internalType: "bytes4", - }, - ], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "symbol", - inputs: [], - outputs: [ - { - name: "", - type: "string", - internalType: "string", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "toggleMaturityCheck", - inputs: [ - { - name: "status", - type: "bool", - internalType: "bool", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "toggleMaxCapCheck", - inputs: [ - { - name: "status", - type: "bool", - internalType: "bool", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "toggleWhitelistCheck", - inputs: [ - { - name: "status", - type: "bool", - internalType: "bool", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "toggleWindowCheck", - inputs: [ - { - name: "status", - type: "bool", - internalType: "bool", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "totalAssetDeposited", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "totalAssets", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "totalSupply", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "transfer", - inputs: [ - { - name: "to", - type: "address", - internalType: "address", - }, - { - name: "value", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "transferFrom", - inputs: [ - { - name: "from", - type: "address", - internalType: "address", - }, - { - name: "to", - type: "address", - internalType: "address", - }, - { - name: "value", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "unpauseVault", - inputs: [], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "updateMaxCap", - inputs: [ - { - name: "_value", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "updateWindow", - inputs: [ - { - name: "_depositOpen", - type: "uint256", - internalType: "uint256", - }, - { - name: "_depositClose", - type: "uint256", - internalType: "uint256", - }, - { - name: "_withdrawOpen", - type: "uint256", - internalType: "uint256", - }, - { - name: "_withdrawClose", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "withdraw", - inputs: [ - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - { - name: "receiver", - type: "address", - internalType: "address", - }, - { - name: "owner", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "event", - name: "Approval", - inputs: [ - { - name: "owner", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "spender", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "value", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "Deposit", - inputs: [ - { - name: "sender", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "owner", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "assets", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - { - name: "shares", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "Paused", - inputs: [ - { - name: "account", - type: "address", - indexed: false, - internalType: "address", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "RoleAdminChanged", - inputs: [ - { - name: "role", - type: "bytes32", - indexed: true, - internalType: "bytes32", - }, - { - name: "previousAdminRole", - type: "bytes32", - indexed: true, - internalType: "bytes32", - }, - { - name: "newAdminRole", - type: "bytes32", - indexed: true, - internalType: "bytes32", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "RoleGranted", - inputs: [ - { - name: "role", - type: "bytes32", - indexed: true, - internalType: "bytes32", - }, - { - name: "account", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "sender", - type: "address", - indexed: true, - internalType: "address", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "RoleRevoked", - inputs: [ - { - name: "role", - type: "bytes32", - indexed: true, - internalType: "bytes32", - }, - { - name: "account", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "sender", - type: "address", - indexed: true, - internalType: "address", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "Transfer", - inputs: [ - { - name: "from", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "to", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "value", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "Unpaused", - inputs: [ - { - name: "account", - type: "address", - indexed: false, - internalType: "address", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "Withdraw", - inputs: [ - { - name: "sender", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "receiver", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "owner", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "assets", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - { - name: "shares", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - ], - anonymous: false, - }, - { - type: "error", - name: "AccessControlBadConfirmation", - inputs: [], - }, - { - type: "error", - name: "AccessControlUnauthorizedAccount", - inputs: [ - { - name: "account", - type: "address", - internalType: "address", - }, - { - name: "neededRole", - type: "bytes32", - internalType: "bytes32", - }, - ], - }, - { - type: "error", - name: "AddressEmptyCode", - inputs: [ - { - name: "target", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "AddressInsufficientBalance", - inputs: [ - { - name: "account", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "CredbullVault__InvalidAssetAmount", - inputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "CredbullVault__MaxCapReached", - inputs: [], - }, - { - type: "error", - name: "CredbullVault__NotAWhitelistedAddress", - inputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "CredbullVault__NotEnoughBalanceToMature", - inputs: [], - }, - { - type: "error", - name: "CredbullVault__NotMatured", - inputs: [], - }, - { - type: "error", - name: "CredbullVault__OperationOutsideRequiredWindow", - inputs: [ - { - name: "windowOpensAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "windowClosesAt", - type: "uint256", - internalType: "uint256", - }, - { - name: "timestamp", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "CredbullVault__TransferOutsideEcosystem", - inputs: [], - }, - { - type: "error", - name: "CredbullVault__UnsupportedDecimalValue", - inputs: [ - { - name: "", - type: "uint8", - internalType: "uint8", - }, - ], - }, - { - type: "error", - name: "ERC20InsufficientAllowance", - inputs: [ - { - name: "spender", - type: "address", - internalType: "address", - }, - { - name: "allowance", - type: "uint256", - internalType: "uint256", - }, - { - name: "needed", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ERC20InsufficientBalance", - inputs: [ - { - name: "sender", - type: "address", - internalType: "address", - }, - { - name: "balance", - type: "uint256", - internalType: "uint256", - }, - { - name: "needed", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ERC20InvalidApprover", - inputs: [ - { - name: "approver", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "ERC20InvalidReceiver", - inputs: [ - { - name: "receiver", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "ERC20InvalidSender", - inputs: [ - { - name: "sender", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "ERC20InvalidSpender", - inputs: [ - { - name: "spender", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "ERC4626ExceededMaxDeposit", - inputs: [ - { - name: "receiver", - type: "address", - internalType: "address", - }, - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - { - name: "max", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ERC4626ExceededMaxMint", - inputs: [ - { - name: "receiver", - type: "address", - internalType: "address", - }, - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - { - name: "max", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ERC4626ExceededMaxRedeem", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - { - name: "shares", - type: "uint256", - internalType: "uint256", - }, - { - name: "max", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ERC4626ExceededMaxWithdraw", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - { - name: "assets", - type: "uint256", - internalType: "uint256", - }, - { - name: "max", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "EnforcedPause", - inputs: [], - }, - { - type: "error", - name: "ExpectedPause", - inputs: [], - }, - { - type: "error", - name: "FailedInnerCall", - inputs: [], - }, - { - type: "error", - name: "MathOverflowedMulDiv", - inputs: [], - }, - { - type: "error", - name: "SafeERC20FailedOperation", - inputs: [ - { - name: "token", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "ZeroAddress", - inputs: [], - }, - ]; \ No newline at end of file From 7c2cc12d1e9c0bbf70ced009bd806af04e0e6c46 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 15:53:30 +0800 Subject: [PATCH 03/13] Updated the SDK Schema class to have readonly properties. --- packages/sdk/test/src/utils/schema.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/sdk/test/src/utils/schema.ts b/packages/sdk/test/src/utils/schema.ts index 6c180c32a..629dbe324 100644 --- a/packages/sdk/test/src/utils/schema.ts +++ b/packages/sdk/test/src/utils/schema.ts @@ -2,18 +2,18 @@ import { z } from 'zod'; // Component Zod Schema for validatiing configuration and parameters. export abstract class Schema { - static EMAIL = z.string().email(); - static NON_EMPTY_STRING = z.string().trim().min(1); - static PERCENTAGE = z.number().int().positive().gt(0); - static ADDRESS = z.string().regex(/^(0x)?[0-9a-fA-F]{40,40}$/); + static readonly EMAIL = z.string().email(); + static readonly NON_EMPTY_STRING = z.string().trim().min(1); + static readonly PERCENTAGE = z.number().int().positive().gt(0); + static readonly ADDRESS = z.string().regex(/^(0x)?[0-9a-fA-F]{40,40}$/); - static CONFIG_API_URL = z.object({ + static readonly CONFIG_API_URL = z.object({ api: z.object({ url: z.string().url(), }), }); - static CONFIG_ADMIN_USER = z.object({ + static readonly CONFIG_ADMIN_USER = z.object({ users: z.object({ admin: z.object({ email_address: this.EMAIL, @@ -24,13 +24,13 @@ export abstract class Schema { }), }); - static CONFIG_ADMIN_PRIVATE_KEY = z.object({ + static readonly CONFIG_ADMIN_PRIVATE_KEY = z.object({ secret: z.object({ ADMIN_PRIVATE_KEY: this.NON_EMPTY_STRING, }), }); - static CONFIG_CRON = z.object({ + static readonly CONFIG_CRON = z.object({ secret: z.object({ CRON_SECRET: this.NON_EMPTY_STRING, }), From 5762e2764f16dd79d95f9d7a0f13728626bba415 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 15:54:20 +0800 Subject: [PATCH 04/13] Updated `helpers` to use the Schema facility. Updated the existing tests to be more complete. --- packages/ops/src/utils/helpers.ts | 79 +++++++++------------ packages/ops/test/utils/helpers.spec.ts | 93 ++++++++++++++++--------- 2 files changed, 96 insertions(+), 76 deletions(-) diff --git a/packages/ops/src/utils/helpers.ts b/packages/ops/src/utils/helpers.ts index 68609db05..28dced8f0 100644 --- a/packages/ops/src/utils/helpers.ts +++ b/packages/ops/src/utils/helpers.ts @@ -1,25 +1,15 @@ import { Database } from '@credbull/api'; import { SupabaseClient, createClient } from '@supabase/supabase-js'; import crypto from 'crypto'; -import { Wallet, providers } from 'ethers'; +import { Wallet, ethers } from 'ethers'; import { SiweMessage, generateNonce } from 'siwe'; -import { z } from 'zod'; -export const emailSchema = z.string().email(); -export const emailSchemaOptional = z.string().email().nullish().or(z.literal('')); -export const addressSchema = z.string().regex(/^(0x)?[0-9a-fA-F]{40,40}$/); -export const upsideVaultSchema = z.union([addressSchema, z.string().regex(/^self$/)]).optional(); - -const supabaseConfigSchema = z.object({ - services: z.object({ supabase: z.object({ url: z.string().url() }) }), - secret: z.object({ - SUPABASE_SERVICE_ROLE_KEY: z.string(), - SUPABASE_ANONYMOUS_KEY: z.string(), - }), -}); +import { Schema } from './schema'; export const supabase = (config: any, opts?: { admin: boolean }) => { - supabaseConfigSchema.parse(config); + Schema.CONFIG_SUPABASE_URL.merge(opts?.admin ? Schema.CONFIG_SUPABASE_ADMIN : Schema.CONFIG_SUPABASE_ANONYMOUS).parse( + config, + ); return createClient( config.services.supabase.url, @@ -33,7 +23,21 @@ export const userByOrThrow = async (supabaseAdmin: SupabaseClient, email: string return user; }; -export const userByOrUndefined = async (supabaseAdmin: SupabaseClient, email: string): Promise => { +/** + * Searches for the `email` User, returning `undefined` if not found. + * Only throws an error in a unrecoverable scenario. + * + * @param supabaseAdmin A `SupabaseClient` with administrative access. + * @param email The `string` email address. Must be valid. + * @returns A `Promise` of a User `any` or `undefined` if not found. + * @throws ZodError if `email` is not an email address. + * @throws PostgrestError if there is an error searching for the User. + * @throws AuthError if there is an error accessing the database. + * @throws Error if there is a system error or if the result pagination mechanism is broken. + */ +export const userByOrUndefined = async (supabaseAdmin: SupabaseClient, email: string): Promise => { + Schema.EMAIL.parse(email); + const pageSize = 1_000; const { data: { users }, @@ -45,7 +49,7 @@ export const userByOrUndefined = async (supabaseAdmin: SupabaseClient, email: st }; export const deleteUserIfPresent = async (supabaseAdmin: SupabaseClient, email: string) => { - await userByOrUndefined(supabaseAdmin, email) + await userByOrThrow(supabaseAdmin, email) .then((user) => { supabaseAdmin.auth.admin.deleteUser(user.id, false); }) @@ -63,30 +67,19 @@ export const headers = (session?: Awaited>) => { }; }; -const adminLoginConfigSchema = z.object({ - api: z.object({ url: z.string().url() }), - users: z.object({ admin: z.object({ email_address: z.string().email() }) }), - secret: z.object({ ADMIN_PASSWORD: z.string() }), -}); - -const bobLoginConfigSchema = z.object({ - api: z.object({ url: z.string().url() }), - users: z.object({ bob: z.object({ email_address: z.string().email() }) }), - secret: z.object({ BOB_PASSWORD: z.string() }), -}); - export const login = async ( config: any, opts?: { admin: boolean }, ): Promise<{ access_token: string; user_id: string }> => { - let _email: string, _password: string; + Schema.CONFIG_API_URL.parse(config); + let _email: string, _password: string; if (opts?.admin) { - adminLoginConfigSchema.parse(config); + Schema.CONFIG_USER_ADMIN.parse(config); _email = config.users.admin.email_address; _password = config.secret!.ADMIN_PASSWORD!; } else { - bobLoginConfigSchema.parse(config); + Schema.CONFIG_USER_BOB.parse(config); _email = config.users.bob.email_address; _password = config.secret!.BOB_PASSWORD!; } @@ -112,10 +105,8 @@ export const login = async ( return data; }; -const linkWalletConfigSchema = z.object({ app: z.object({ url: z.string().url() }) }); - export const linkWalletMessage = async (config: any, signer: Wallet) => { - linkWalletConfigSchema.parse(config); + Schema.CONFIG_APP_URL.parse(config); let appUrl = new URL(config.app.url); const chainId = await signer.getChainId(); @@ -132,11 +123,9 @@ export const linkWalletMessage = async (config: any, signer: Wallet) => { return preMessage.prepareMessage(); }; -const signerConfigSchema = z.object({ services: z.object({ ethers: z.object({ url: z.string().url() }) }) }); - export const signer = (config: any, privateKey: string) => { - signerConfigSchema.parse(config); - return new Wallet(privateKey, new providers.JsonRpcProvider(config.services.ethers.url)); + Schema.CONFIG_ETHERS_URL.parse(config); + return new Wallet(privateKey, new ethers.providers.JsonRpcProvider(config.services.ethers.url)); }; export const generateAddress = () => { @@ -157,19 +146,19 @@ export const generatePassword = ( }; export function parseEmail(email: string) { - emailSchema.parse(email); + Schema.EMAIL.parse(email); } -export function parseEmailOptional(email?: string | undefined) { - emailSchemaOptional.nullish().optional().or(z.literal('')).parse(email); +export function parseEmailOptional(email?: string | null) { + Schema.EMAIL_OPTIONAL.parse(email); } export function parseAddress(address: string) { - emailSchema.parse(address); + Schema.ADDRESS.parse(address); } -export function parseUsideVault(upsideVaultStr: string | undefined) { - upsideVaultSchema.optional().parse(upsideVaultStr); +export function parseUpsideVault(upsideVaultSpec?: string) { + Schema.UPSIDE_VAULT_SPEC.optional().parse(upsideVaultSpec); } export function generateRandomEmail(prefix: string): string { diff --git a/packages/ops/test/utils/helpers.spec.ts b/packages/ops/test/utils/helpers.spec.ts index c8f0d2e66..0fb5ab7de 100644 --- a/packages/ops/test/utils/helpers.spec.ts +++ b/packages/ops/test/utils/helpers.spec.ts @@ -1,44 +1,75 @@ import { expect, test } from '@playwright/test'; import { ZodError } from 'zod'; -import { generateRandomEmail, parseEmail, parseEmailOptional, parseUsideVault } from '../../src/utils/helpers'; +import { generateRandomEmail, parseEmail, parseEmailOptional, parseUpsideVault } from '../../src/utils/helpers'; -// Adjust the path as necessary +test.describe('Parsing an email parameter with', async () => { + test('a valid email should pass', async () => { + expect(() => parseEmail('test@credbull.io')).toPass(); + expect(() => parseEmail('test+admin@credbull.io')).toPass(); + expect(() => parseEmail(generateRandomEmail('test'))).toPass(); + }); -test('Test parse email', async () => { - // const emailSchema = z.string().email().optional(); - - // Test valid email - // Test valid email - expect(() => parseEmail('test@credbull.io')).not.toThrow(); - expect(() => parseEmail('test+admin@credbull.io')).not.toThrow(); - expect(() => parseEmail(generateRandomEmail('test'))).not.toThrow(); - - expect(() => parseEmail(undefined)).toThrow(ZodError); - expect(() => parseEmail('prefix')).toThrow(ZodError); - expect(() => parseEmail('domain.com')).toThrow(ZodError); - - expect(() => parseEmail('')).toThrow(ZodError); - expect(() => parseEmail(' \t \n ')).toThrow(ZodError); - expect(() => parseEmail('someone@here')).toThrow(ZodError); - expect(() => parseEmail('no one@here.com')).toThrow(ZodError); + test('an invalid email should fail', async () => { + expect(() => parseEmail('prefix')).toThrow(ZodError); + expect(() => parseEmail('domain.com')).toThrow(ZodError); + expect(() => parseEmail('')).toThrow(ZodError); + expect(() => parseEmail(' \t \n ')).toThrow(ZodError); + expect(() => parseEmail('someone@here')).toThrow(ZodError); + expect(() => parseEmail('no one@here.com')).toThrow(ZodError); + }); }); -test('Test parse optional email', async () => { - expect(() => parseEmail('someone@here')).toThrow(ZodError); +test.describe('Parsing an optional email parameter with', async () => { + test('a valid email should pass', async () => { + expect(() => parseEmailOptional('test@credbull.io')).toPass(); + expect(() => parseEmailOptional('test+admin@credbull.io')).toPass(); + expect(() => parseEmailOptional(generateRandomEmail('test'))).toPass(); + }); - // Test invalid email - expect(() => parseEmailOptional(undefined)).not.toThrow(ZodError); + test('an invalid email should fail', async () => { + expect(() => parseEmailOptional('prefix')).toThrow(ZodError); + expect(() => parseEmailOptional('domain.com')).toThrow(ZodError); + expect(() => parseEmailOptional(' \t \n ')).toThrow(ZodError); + expect(() => parseEmailOptional('someone@here')).toThrow(ZodError); + expect(() => parseEmailOptional('no one@here.com')).toThrow(ZodError); + }); - expect(() => parseEmailOptional(null)).not.toThrow(ZodError); - expect(() => parseEmailOptional('')).not.toThrow(ZodError); + test('a null, undefined or empty string should pass', async () => { + expect(() => parseEmailOptional(undefined)).toPass(); + expect(() => parseEmailOptional(null)).toPass(); + expect(() => parseEmailOptional('')).toPass(); + }); }); -test('Test parse vault upsideVault params', async () => { - expect(() => parseUsideVault('self')).not.toThrow(ZodError); +test.describe('Parsing an Upside Vault Specifier with', async () => { + test('a valid value should pass', async () => { + expect(() => parseUpsideVault('self')).toPass(); + for (const chr of '1234567890abcdefABCDEF') { + const hex = chr.repeat(40); + expect(() => parseUpsideVault(hex)).toPass(); + expect(() => parseUpsideVault('0x' + hex)).toPass(); + } + }); + + test('an invalid value should fail', async () => { + expect(() => parseUpsideVault('')).toThrow(ZodError); + expect(() => parseUpsideVault(' \t \n ')).toThrow(ZodError); + expect(() => parseUpsideVault('self ')).toThrow(ZodError); + expect(() => parseUpsideVault('SELF')).toThrow(ZodError); - expect(() => parseUsideVault('')).toThrow(ZodError); - expect(() => parseUsideVault(' \t \n ')).toThrow(ZodError); - expect(() => parseUsideVault('self ')).toThrow(ZodError); - expect(() => parseUsideVault('SELF')).toThrow(ZodError); + for (const chr of 'ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ') { + const notHex = chr.repeat(40); + expect(() => parseUpsideVault(notHex)).toThrow(ZodError); + expect(() => parseUpsideVault('0x' + notHex)).toThrow(ZodError); + } + for (const chr of '1234567890abcdefABCDEF') { + const tooSmall = chr.repeat(39); + const tooBig = chr.repeat(41); + expect(() => parseUpsideVault(tooSmall)).toThrow(ZodError); + expect(() => parseUpsideVault('0x' + tooSmall)).toThrow(ZodError); + expect(() => parseUpsideVault(tooBig)).toThrow(ZodError); + expect(() => parseUpsideVault('0x' + tooBig)).toThrow(ZodError); + } + }); }); From 556d51013ded6b2b7725d34ffb902a6b740475b2 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 15:55:13 +0800 Subject: [PATCH 05/13] Converted `createVault` to use the `Schema` validator facility. --- packages/ops/src/create-vault.ts | 52 ++++++-------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/packages/ops/src/create-vault.ts b/packages/ops/src/create-vault.ts index 2aab98425..d03a213c5 100644 --- a/packages/ops/src/create-vault.ts +++ b/packages/ops/src/create-vault.ts @@ -1,48 +1,10 @@ -import { - CredbullFixedYieldVaultFactory__factory, - CredbullFixedYieldVault__factory, - CredbullVaultFactory__factory, -} from '@credbull/contracts'; +import { CredbullFixedYieldVault__factory, CredbullVaultFactory__factory } from '@credbull/contracts'; import type { ICredbull } from '@credbull/contracts/types/CredbullFixedYieldVaultFactory'; import { addYears, startOfWeek, startOfYear, subDays } from 'date-fns'; -import { z } from 'zod'; import { loadConfiguration } from './utils/config'; -import { - addressSchema, - headers, - login, - parseEmailOptional, - parseUsideVault, - signer, - supabase, - userByOrThrow, -} from './utils/helpers'; - -// Zod Schema to validate all config points in this module. - -const configSchema = z.object({ - secret: z.object({ - ADMIN_PRIVATE_KEY: z.string(), - }), - api: z.object({ - url: z.string().url(), - }), - evm: z.object({ - address: z.object({ - owner: addressSchema, - operator: addressSchema, - custodian: addressSchema, - treasury: addressSchema, - activity_reward: addressSchema, - }), - }), - operation: z.object({ - createVault: z.object({ - collateral_percentage: z.number(), - }), - }), -}); +import { headers, login, parseEmailOptional, parseUpsideVault, signer, supabase, userByOrThrow } from './utils/helpers'; +import { Schema } from './utils/schema'; type CreateVaultParams = { treasury: string | undefined; @@ -150,9 +112,13 @@ export const createVault = async ( tenantEmail?: string, override?: { treasuryAddress?: string; activityRewardAddress?: string; collateralPercentage?: number }, ): Promise => { - configSchema.parse(config); - parseUsideVault(upsideVault); + Schema.CONFIG_API_URL.parse(config); + Schema.CONFIG_ADMIN_PRIVATE_KEY.parse(config); + Schema.CONFIG_EVM_ADDRESS.parse(config); + Schema.CONFIG_OPERATION_CREATE_VAULT.parse(config); + parseUpsideVault(upsideVault); parseEmailOptional(tenantEmail); + const supabaseAdmin = supabase(config, { admin: true }); const addresses = await supabaseAdmin.from('contracts_addresses').select(); if (addresses.error) throw addresses.error; From 01cbc4675b96e6eed935366314bbbee0ba2cec4c Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 16:33:19 +0800 Subject: [PATCH 06/13] Changed the 'parse' helper functions to be 'assert' functions. Added unit tests for said functions. --- packages/ops/src/create-user.ts | 4 +- packages/ops/src/create-vault.ts | 14 ++- packages/ops/src/make-admin.ts | 4 +- packages/ops/src/make-channel.ts | 4 +- packages/ops/src/utils/helpers.ts | 16 +-- packages/ops/test/utils/helpers.spec.ts | 131 ++++++++++++++++-------- 6 files changed, 111 insertions(+), 62 deletions(-) diff --git a/packages/ops/src/create-user.ts b/packages/ops/src/create-user.ts index cbab66c86..b3b935d9b 100644 --- a/packages/ops/src/create-user.ts +++ b/packages/ops/src/create-user.ts @@ -2,7 +2,7 @@ import { ZodError, z } from 'zod'; import { makeChannel } from './make-channel'; import { loadConfiguration } from './utils/config'; -import { generatePassword, parseEmail, supabase } from './utils/helpers'; +import { assertEmail, generatePassword, supabase } from './utils/helpers'; // Zod Schemas to validate the parameters and configuration. const configSchema = z.object({ app: z.object({ url: z.string().url() }) }); @@ -29,7 +29,7 @@ export const createUser = async ( isChannel: boolean, passwordMaybe?: string, ): Promise => { - parseEmail(email); + assertEmail(email); nonEmptyStringSchema.optional().parse(passwordMaybe); configSchema.parse(config); diff --git a/packages/ops/src/create-vault.ts b/packages/ops/src/create-vault.ts index d03a213c5..0628d76fb 100644 --- a/packages/ops/src/create-vault.ts +++ b/packages/ops/src/create-vault.ts @@ -3,7 +3,15 @@ import type { ICredbull } from '@credbull/contracts/types/CredbullFixedYieldVaul import { addYears, startOfWeek, startOfYear, subDays } from 'date-fns'; import { loadConfiguration } from './utils/config'; -import { headers, login, parseEmailOptional, parseUpsideVault, signer, supabase, userByOrThrow } from './utils/helpers'; +import { + assertEmailOptional, + assertUpsideVault, + headers, + login, + signer, + supabase, + userByOrThrow, +} from './utils/helpers'; import { Schema } from './utils/schema'; type CreateVaultParams = { @@ -116,8 +124,8 @@ export const createVault = async ( Schema.CONFIG_ADMIN_PRIVATE_KEY.parse(config); Schema.CONFIG_EVM_ADDRESS.parse(config); Schema.CONFIG_OPERATION_CREATE_VAULT.parse(config); - parseUpsideVault(upsideVault); - parseEmailOptional(tenantEmail); + assertUpsideVault(upsideVault); + assertEmailOptional(tenantEmail); const supabaseAdmin = supabase(config, { admin: true }); const addresses = await supabaseAdmin.from('contracts_addresses').select(); diff --git a/packages/ops/src/make-admin.ts b/packages/ops/src/make-admin.ts index 1ecbba775..180c4c84e 100644 --- a/packages/ops/src/make-admin.ts +++ b/packages/ops/src/make-admin.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { loadConfiguration } from './utils/config'; -import { parseEmail, supabase, userByOrThrow } from './utils/helpers'; +import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; // TODO (JL,2024-06-05): Add `update-metadata` script and use for Make Admin/Channel. @@ -16,7 +16,7 @@ import { parseEmail, supabase, userByOrThrow } from './utils/helpers'; * @throws ZodError if the parameters or configuration are invalid. */ export const makeAdmin = async (config: any, email: string): Promise => { - parseEmail(email); + assertEmail(email); const supabaseAdmin = supabase(config, { admin: true }); const toUpdate = await userByOrThrow(supabaseAdmin, email); diff --git a/packages/ops/src/make-channel.ts b/packages/ops/src/make-channel.ts index 571a1472e..c1c79e88e 100644 --- a/packages/ops/src/make-channel.ts +++ b/packages/ops/src/make-channel.ts @@ -1,5 +1,5 @@ import { loadConfiguration } from './utils/config'; -import { parseEmail, supabase, userByOrThrow } from './utils/helpers'; +import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; // Zod Schemas for parameter and configuration validation. @@ -14,7 +14,7 @@ import { parseEmail, supabase, userByOrThrow } from './utils/helpers'; * @throws ZodError if the parameters or configuration are invalid. */ export const makeChannel = async (config: any, email: string): Promise => { - parseEmail(email); + assertEmail(email); const supabaseAdmin = supabase(config, { admin: true }); const toUpdate = await userByOrThrow(supabaseAdmin, email); diff --git a/packages/ops/src/utils/helpers.ts b/packages/ops/src/utils/helpers.ts index 28dced8f0..3f6aa7c15 100644 --- a/packages/ops/src/utils/helpers.ts +++ b/packages/ops/src/utils/helpers.ts @@ -145,24 +145,24 @@ export const generatePassword = ( .join(''); }; -export function parseEmail(email: string) { - Schema.EMAIL.parse(email); +export function assertAddress(address: string) { + Schema.ADDRESS.parse(address); } -export function parseEmailOptional(email?: string | null) { - Schema.EMAIL_OPTIONAL.parse(email); +export function assertEmail(email: string) { + Schema.EMAIL.parse(email); } -export function parseAddress(address: string) { - Schema.ADDRESS.parse(address); +export function assertEmailOptional(email?: string | null) { + Schema.EMAIL_OPTIONAL.parse(email); } -export function parseUpsideVault(upsideVaultSpec?: string) { +export function assertUpsideVault(upsideVaultSpec?: string) { Schema.UPSIDE_VAULT_SPEC.optional().parse(upsideVaultSpec); } export function generateRandomEmail(prefix: string): string { - const randomString = Math.random().toString(36).substring(2, 10); // Generates a random string + const randomString = Math.random().toString(36).substring(2, 10); // Generates a weak, pseudorandom string const domain = '@credbull.io'; return `${prefix}+${randomString}${domain}`; } diff --git a/packages/ops/test/utils/helpers.spec.ts b/packages/ops/test/utils/helpers.spec.ts index 0fb5ab7de..23d995721 100644 --- a/packages/ops/test/utils/helpers.spec.ts +++ b/packages/ops/test/utils/helpers.spec.ts @@ -1,75 +1,116 @@ import { expect, test } from '@playwright/test'; import { ZodError } from 'zod'; -import { generateRandomEmail, parseEmail, parseEmailOptional, parseUpsideVault } from '../../src/utils/helpers'; +import { + assertAddress, + assertEmail, + assertEmailOptional, + assertUpsideVault, + generateAddress, + generateRandomEmail, +} from '../../src/utils/helpers'; -test.describe('Parsing an email parameter with', async () => { +test.describe('Asserting an email parameter with', async () => { test('a valid email should pass', async () => { - expect(() => parseEmail('test@credbull.io')).toPass(); - expect(() => parseEmail('test+admin@credbull.io')).toPass(); - expect(() => parseEmail(generateRandomEmail('test'))).toPass(); + expect(() => assertEmail('test@credbull.io')).toPass(); + expect(() => assertEmail('test+admin@credbull.io')).toPass(); + expect(() => assertEmail(generateRandomEmail('test'))).toPass(); }); test('an invalid email should fail', async () => { - expect(() => parseEmail('prefix')).toThrow(ZodError); - expect(() => parseEmail('domain.com')).toThrow(ZodError); - expect(() => parseEmail('')).toThrow(ZodError); - expect(() => parseEmail(' \t \n ')).toThrow(ZodError); - expect(() => parseEmail('someone@here')).toThrow(ZodError); - expect(() => parseEmail('no one@here.com')).toThrow(ZodError); + expect(() => assertEmail('prefix')).toThrow(ZodError); + expect(() => assertEmail('domain.com')).toThrow(ZodError); + expect(() => assertEmail('')).toThrow(ZodError); + expect(() => assertEmail(' \t \n ')).toThrow(ZodError); + expect(() => assertEmail('someone@here')).toThrow(ZodError); + expect(() => assertEmail('no one@here.com')).toThrow(ZodError); }); }); -test.describe('Parsing an optional email parameter with', async () => { +test.describe('Asserting an optional email parameter with', async () => { test('a valid email should pass', async () => { - expect(() => parseEmailOptional('test@credbull.io')).toPass(); - expect(() => parseEmailOptional('test+admin@credbull.io')).toPass(); - expect(() => parseEmailOptional(generateRandomEmail('test'))).toPass(); + expect(() => assertEmailOptional('test@credbull.io')).toPass(); + expect(() => assertEmailOptional('test+admin@credbull.io')).toPass(); + expect(() => assertEmailOptional(generateRandomEmail('test'))).toPass(); }); test('an invalid email should fail', async () => { - expect(() => parseEmailOptional('prefix')).toThrow(ZodError); - expect(() => parseEmailOptional('domain.com')).toThrow(ZodError); - expect(() => parseEmailOptional(' \t \n ')).toThrow(ZodError); - expect(() => parseEmailOptional('someone@here')).toThrow(ZodError); - expect(() => parseEmailOptional('no one@here.com')).toThrow(ZodError); + expect(() => assertEmailOptional('prefix')).toThrow(ZodError); + expect(() => assertEmailOptional('domain.com')).toThrow(ZodError); + expect(() => assertEmailOptional(' \t \n ')).toThrow(ZodError); + expect(() => assertEmailOptional('someone@here')).toThrow(ZodError); + expect(() => assertEmailOptional('no one@here.com')).toThrow(ZodError); }); test('a null, undefined or empty string should pass', async () => { - expect(() => parseEmailOptional(undefined)).toPass(); - expect(() => parseEmailOptional(null)).toPass(); - expect(() => parseEmailOptional('')).toPass(); + expect(() => assertEmailOptional(undefined)).toPass(); + expect(() => assertEmailOptional(null)).toPass(); + expect(() => assertEmailOptional('')).toPass(); }); }); -test.describe('Parsing an Upside Vault Specifier with', async () => { +function correctlyAcceptsAddress(subject: (address: string) => void) { + for (const chr of '1234567890abcdefABCDEF') { + const hex = chr.repeat(40); + expect(() => subject(hex)).toPass(); + expect(() => subject('0x' + hex)).toPass(); + } +} + +function correctlyRejectsAddress(subject: (address: string) => void) { + expect(() => subject('')).toThrow(ZodError); + expect(() => subject(' \t \n ')).toThrow(ZodError); + + for (const chr of 'ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ') { + const notHex = chr.repeat(40); + expect(() => subject(notHex)).toThrow(ZodError); + expect(() => subject('0x' + notHex)).toThrow(ZodError); + } + for (const chr of '1234567890abcdefABCDEF') { + const tooSmall = chr.repeat(39); + const tooBig = chr.repeat(41); + expect(() => subject(tooSmall)).toThrow(ZodError); + expect(() => subject('0x' + tooSmall)).toThrow(ZodError); + expect(() => subject(tooBig)).toThrow(ZodError); + expect(() => subject('0x' + tooBig)).toThrow(ZodError); + } +} + +test.describe('Asserting an address parameter with', async () => { + test('a valid address should pass', async () => { + correctlyAcceptsAddress(assertAddress); + }); + + test('an invalid address should fail', async () => { + correctlyRejectsAddress(assertAddress); + }); +}); + +test.describe('Asserting an Upside Vault Specifier with', async () => { test('a valid value should pass', async () => { - expect(() => parseUpsideVault('self')).toPass(); - for (const chr of '1234567890abcdefABCDEF') { - const hex = chr.repeat(40); - expect(() => parseUpsideVault(hex)).toPass(); - expect(() => parseUpsideVault('0x' + hex)).toPass(); - } + expect(() => assertUpsideVault('self')).toPass(); + correctlyAcceptsAddress(assertUpsideVault); }); test('an invalid value should fail', async () => { - expect(() => parseUpsideVault('')).toThrow(ZodError); - expect(() => parseUpsideVault(' \t \n ')).toThrow(ZodError); - expect(() => parseUpsideVault('self ')).toThrow(ZodError); - expect(() => parseUpsideVault('SELF')).toThrow(ZodError); + expect(() => assertUpsideVault('self ')).toThrow(ZodError); + expect(() => assertUpsideVault('SELF')).toThrow(ZodError); + correctlyRejectsAddress(assertUpsideVault); + }); +}); - for (const chr of 'ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ') { - const notHex = chr.repeat(40); - expect(() => parseUpsideVault(notHex)).toThrow(ZodError); - expect(() => parseUpsideVault('0x' + notHex)).toThrow(ZodError); +test.describe('Generating an address should', async () => { + test('produce a valid address every iteration', async () => { + for (let i = 0; i < 20; i++) { + expect(() => assertAddress(generateAddress())).toPass(); } - for (const chr of '1234567890abcdefABCDEF') { - const tooSmall = chr.repeat(39); - const tooBig = chr.repeat(41); - expect(() => parseUpsideVault(tooSmall)).toThrow(ZodError); - expect(() => parseUpsideVault('0x' + tooSmall)).toThrow(ZodError); - expect(() => parseUpsideVault(tooBig)).toThrow(ZodError); - expect(() => parseUpsideVault('0x' + tooBig)).toThrow(ZodError); + }); +}); + +test.describe('Generating an email should', async () => { + test('produce a valid email every iteration', async () => { + for (let i = 0; i < 20; i++) { + expect(() => assertEmail(generateRandomEmail('test-' + i))).toPass(); } }); }); From 87ddd9fcc29ac3a2208aeb27ecf9f508dab8b142 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 16:34:50 +0800 Subject: [PATCH 07/13] Re-enabled the Fixed Yield With Upside Vault creation test. Added a second that links to an existing vault. --- packages/ops/test/create-vault.spec.ts | 97 +++++++++++++++++--------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/packages/ops/test/create-vault.spec.ts b/packages/ops/test/create-vault.spec.ts index 34492c82c..49e38bf75 100644 --- a/packages/ops/test/create-vault.spec.ts +++ b/packages/ops/test/create-vault.spec.ts @@ -21,9 +21,14 @@ test.beforeAll(async () => { }); /* - TODO - move param validations and tests to separate classes to simplfy createVault testing. - // e.g. helpers.parseUsideVault() (and associated tests) + TODO - move param validations and tests to separate classes to simplify createVault testing. + // e.g. helpers.parseUpsideVault() (and associated tests) */ +// NOTE (JL,2024-06-18): Given that this test suite is Black Box testing, it is proving the API Contract (no pun) of the +// function. Thus having tests for the validation helper functions (we have some) does not actually test the API +// Contract of the function under test (just a correlation of it!). So, I don't actually agree with the TODO above! +// Will discuss with Ian. + test.describe('Create Vault should fail when invoked with', async () => { test('an invalid configuration', async () => { expect(createVault(EMPTY_CONFIG, false, false, false)).rejects.toThrow(ZodError); @@ -86,15 +91,6 @@ test.describe('Create Vault', async () => { if (createdAdminUser) await deleteUserIfPresent(supabaseAdmin, config.users.admin.email_address); }); - async function numberOfVaults(): Promise { - return supabaseAdmin - .from('vaults') - .select('*', { count: 'exact', head: true }) - .then((data: any) => { - return data.count; - }); - } - async function verifyVaultContract(address: string, isMatured = false): Promise { const vaultContract = CredbullFixedYieldVault__factory.connect(address, adminSigner); expect(vaultContract.name()).resolves.toBe('Credbull Liquidity'); @@ -144,27 +140,62 @@ test.describe('Create Vault', async () => { await verifyVaultContract(created.address); }); - test.fixme( - 'a non-matured, ready, Upside Fixed Yield vault, open for deposits, pending for redemption', - async () => { - const created = await createVault(config, false, true, false, 'self'); - expect(created).toMatchObject({ type: 'fixed_yield', status: 'ready' }); - expect(isPast(created.deposits_opened_at)).toBe(true); - expect(isFuture(created.deposits_closed_at)).toBe(true); - expect(isAfter(created.deposits_closed_at, created.deposits_opened_at)).toBe(true); - expect(isFuture(created.redemptions_opened_at)).toBe(true); - expect(isAfter(created.redemptions_opened_at, created.deposits_closed_at)).toBe(true); - expect(isAfter(created.redemptions_closed_at, created.redemptions_opened_at)).toBe(true); - - const { - data: [loaded, ...rest], - } = await supabaseAdmin.from('vaults').select('id, type, status, address').eq('id', created.id); - const expected = { id: created.id, type: 'fixed_yield', status: 'ready', address: created.address }; - expect(loaded).toMatchObject(expected); - expect(rest).toEqual([]); - - await verifyVaultContract(created.address); - }, - ); + test('a non-matured, ready, Upside Fixed Yield vault, linked to itself, open for deposits, pending for redemption', async () => { + const created = await createVault(config, false, true, false, 'self'); + expect(created).toMatchObject({ type: 'fixed_yield_upside', status: 'ready' }); + expect(isPast(created.deposits_opened_at)).toBe(true); + expect(isFuture(created.deposits_closed_at)).toBe(true); + expect(isAfter(created.deposits_closed_at, created.deposits_opened_at)).toBe(true); + expect(isFuture(created.redemptions_opened_at)).toBe(true); + expect(isAfter(created.redemptions_opened_at, created.deposits_closed_at)).toBe(true); + expect(isAfter(created.redemptions_closed_at, created.redemptions_opened_at)).toBe(true); + + // Vault + const { + data: [vaults, ...restVaults], + } = await supabaseAdmin.from('vaults').select('id, type, status, address').eq('id', created.id); + let expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; + expect(vaults).toMatchObject(expected); + expect(restVaults).toEqual([]); + + // Vault Entity + const { + data: [vaultEntity, ...restVaultEntity], + } = await supabaseAdmin.from('vault_entities').select('address').eq('vault_id', created.id).eq('type', 'vault'); + expect(vaultEntity).toMatchObject({ address: created.address }); + expect(restVaultEntity).toEqual([]); + + await verifyVaultContract(created.address); + }); + + test('a non-matured, ready, Upside Fixed Yield vault, linked to another, open for deposits, pending for redemption', async () => { + const linkTo = await createVault(config, false, false, false); + + const created = await createVault(config, false, true, false, linkTo.address); + expect(created).toMatchObject({ type: 'fixed_yield_upside', status: 'ready' }); + expect(isPast(created.deposits_opened_at)).toBe(true); + expect(isFuture(created.deposits_closed_at)).toBe(true); + expect(isAfter(created.deposits_closed_at, created.deposits_opened_at)).toBe(true); + expect(isFuture(created.redemptions_opened_at)).toBe(true); + expect(isAfter(created.redemptions_opened_at, created.deposits_closed_at)).toBe(true); + expect(isAfter(created.redemptions_closed_at, created.redemptions_opened_at)).toBe(true); + + // Vault + const { + data: [vaults, ...restVaults], + } = await supabaseAdmin.from('vaults').select('id, type, status, address').eq('id', created.id); + let expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; + expect(vaults).toMatchObject(expected); + expect(restVaults).toEqual([]); + + // Vault Entity + const { + data: [vaultEntity, ...restVaultEntity], + } = await supabaseAdmin.from('vault_entities').select('address').eq('vault_id', created.id).eq('type', 'vault'); + expect(vaultEntity).toMatchObject({ address: linkTo.address }); + expect(restVaultEntity).toEqual([]); + + await verifyVaultContract(created.address); + }); }); }); From 1102be27fb9e9a642dce4efa5d14a6d2410ea1dc Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 16:53:09 +0800 Subject: [PATCH 08/13] Converted all scripts to use the `Schema` parameter validation mechanism. --- packages/ops/src/clean-vault-table.ts | 24 +++++++-------- packages/ops/src/create-default-users.ts | 37 ++++++------------------ packages/ops/src/create-user.ts | 20 +++++-------- packages/ops/src/create-vault.ts | 4 +-- packages/ops/src/make-admin.ts | 2 -- packages/ops/src/make-channel.ts | 2 -- packages/ops/src/utils/schema.ts | 13 +++++++++ 7 files changed, 41 insertions(+), 61 deletions(-) diff --git a/packages/ops/src/clean-vault-table.ts b/packages/ops/src/clean-vault-table.ts index 35ef0fb77..dd1235223 100644 --- a/packages/ops/src/clean-vault-table.ts +++ b/packages/ops/src/clean-vault-table.ts @@ -1,28 +1,22 @@ -import { z } from 'zod'; - import { CredbullFixedYieldVault__factory } from '@credbull/contracts'; import { loadConfiguration } from './utils/config'; import { signer, supabase } from './utils/helpers'; - -// Zod Schema for validating parameters and configuration. -const configParser = z.object({ secret: z.object({ ADMIN_PRIVATE_KEY: z.string() }) }); +import { Schema } from './utils/schema'; /** * Pauses all Vault contracts and truncates the Vault database table. - * - * @param config The applicable configuration object. + * + * @param config The applicable configuration object. * @throws PostgrestError if any database operation fails. * @throws ZodError if the `config` object does not satisfy all configuration needs. */ export const cleanVaultTable = async (config: any) => { - configParser.parse(config); + Schema.CONFIG_ADMIN_PRIVATE_KEY.parse(config); const supabaseAdmin = supabase(config, { admin: true }); const { data, error } = await supabaseAdmin.from('vaults').select(); - if (error) throw error; - if (data.length === 0) { console.log('No vault data to clean'); return; @@ -31,7 +25,7 @@ export const cleanVaultTable = async (config: any) => { const adminSigner = signer(config, config.secret.ADMIN_PRIVATE_KEY); console.log('='.repeat(80)); - console.log(` Pausing ${data.length} Vaults.`) + console.log(` Pausing ${data.length} Vaults.`); for (const vault of data) { const contract = CredbullFixedYieldVault__factory.connect(vault.address, adminSigner); const tx = await contract.pauseVault(); @@ -48,13 +42,15 @@ export const cleanVaultTable = async (config: any) => { }; /** - * Invoked by the command line processor, pauses all Vault contracts and truncates the Vault database table. - * + * Invoked by the command line processor, pauses all Vault contracts and truncates the Vault database table. + * * @throws AuthError if the account does not exist or the update fails. * @throws ZodError if the loaded configuration does not satisfy all configuration needs. */ export const main = () => { - setTimeout(async () => { cleanVaultTable(loadConfiguration()) }, 1000); + setTimeout(async () => { + cleanVaultTable(loadConfiguration()); + }, 1000); }; export default main; diff --git a/packages/ops/src/create-default-users.ts b/packages/ops/src/create-default-users.ts index 4cbba0bf1..58ac9f42d 100644 --- a/packages/ops/src/create-default-users.ts +++ b/packages/ops/src/create-default-users.ts @@ -1,37 +1,16 @@ -import { z } from 'zod'; - import { createUser } from './create-user'; import { makeAdmin } from './make-admin'; import { loadConfiguration } from './utils/config'; - -// Zod Schema for validating parameters and configuration. -const configSchema = z.object({ - users: z.object({ - admin: z.object({ - email_address: z.string().email(), - }), - alice: z.object({ - email_address: z.string().email(), - }), - bob: z.object({ - email_address: z.string().email(), - }), - }), - secret: z.object({ - ADMIN_PASSWORD: z.string(), - ALICE_PASSWORD: z.string(), - BOB_PASSWORD: z.string(), - }), -}); +import { Schema } from './utils/schema'; /** - * Creates the 'default' Corporate Accounts. - * - * @param config The applicable configuration object. + * Creates the 'default' Corporate Accounts. + * + * @param config The applicable configuration object. * @throws ZodError if the `config` object does not satisfy all configuration needs. */ export const createDefaultUsers = async (config: any) => { - configSchema.parse(config) + Schema.CONFIG_USERS.parse(config); console.log('='.repeat(90)); console.log(' Creating default Users.'); @@ -47,12 +26,14 @@ export const createDefaultUsers = async (config: any) => { /** * Invoked by the command line processor, creates the default User Accounts. - * + * * @throws AuthError if any account creation fails. * @throws ZodError if the loaded configuration does not satisfy all configuration needs. */ export const main = () => { - setTimeout(async () => { createDefaultUsers(loadConfiguration()); }, 1000); + setTimeout(async () => { + createDefaultUsers(loadConfiguration()); + }, 1000); }; export default { main }; diff --git a/packages/ops/src/create-user.ts b/packages/ops/src/create-user.ts index b3b935d9b..46d41aebb 100644 --- a/packages/ops/src/create-user.ts +++ b/packages/ops/src/create-user.ts @@ -1,24 +1,19 @@ -import { ZodError, z } from 'zod'; - import { makeChannel } from './make-channel'; import { loadConfiguration } from './utils/config'; import { assertEmail, generatePassword, supabase } from './utils/helpers'; - -// Zod Schemas to validate the parameters and configuration. -const configSchema = z.object({ app: z.object({ url: z.string().url() }) }); -const nonEmptyStringSchema = z.string().trim().min(1); +import { Schema } from './utils/schema'; /** - * Creates a Corporate Account User with `email` Email Address and `_password` Password, if provided. If - * `_password` is not provided, generate a password. + * Creates a Corporate Account User with `email` Email Address and `passwordMaybe` Password, if provided. If + * `passwordMaybe` is not provided, generate a password. * * The `config` object is validated to provide all configuration items required. * * @param config The applicable configuration. * @param email The `string` email address of the Corporate Account. * @param isChannel The Corporate Account is also a Channel. - * @param passwordMaybe The optional password to use for the Corporate Account. If not specified, a password is generated. - * This value is injected in the returned object with the `generated_password` property name. + * @param passwordMaybe The optional password to use for the Corporate Account. If not specified, a password is + * generated. This value is injected in the returned object with the `generated_password` property name. * @returns A `Promise` for the Supabase User object. * @throws AuthError if the account creation fails. * @throws ZodError if the parameters or config are invalid. @@ -29,11 +24,10 @@ export const createUser = async ( isChannel: boolean, passwordMaybe?: string, ): Promise => { + Schema.CONFIG_APP_URL.parse(config); + Schema.NON_EMPTY_STRING.optional().parse(passwordMaybe); assertEmail(email); - nonEmptyStringSchema.optional().parse(passwordMaybe); - configSchema.parse(config); - const supabaseAdmin = supabase(config, { admin: true }); const password = passwordMaybe || generatePassword(); const { diff --git a/packages/ops/src/create-vault.ts b/packages/ops/src/create-vault.ts index 0628d76fb..c23959065 100644 --- a/packages/ops/src/create-vault.ts +++ b/packages/ops/src/create-vault.ts @@ -103,9 +103,9 @@ function createParams( * @param config The applicable configuration. Must be valid against a schema. * @param isMatured `true` if the Vault is to be created matured, or not. * @param isUpside `true` is an Fixed Yield With Upside Vault is to be created. - * @param isTenant Don't Know. + * @param isTenant (JL,2024-06-18): Don't Know. * @param upsideVault The `string` Address of the Fixed Yield With Upside Vault. - * @param tenantEmail Don't Know. + * @param tenantEmail (JL,2024-06-18): Don't Know. * @param override Values that, if present, override the same configuration values. * @throws ZodError if any parameter or config item fails validation. * @throws PostgrestError if authentication or any database interaction fails. diff --git a/packages/ops/src/make-admin.ts b/packages/ops/src/make-admin.ts index 180c4c84e..f7647a274 100644 --- a/packages/ops/src/make-admin.ts +++ b/packages/ops/src/make-admin.ts @@ -1,5 +1,3 @@ -import { z } from 'zod'; - import { loadConfiguration } from './utils/config'; import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; diff --git a/packages/ops/src/make-channel.ts b/packages/ops/src/make-channel.ts index c1c79e88e..64645c655 100644 --- a/packages/ops/src/make-channel.ts +++ b/packages/ops/src/make-channel.ts @@ -1,8 +1,6 @@ import { loadConfiguration } from './utils/config'; import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; -// Zod Schemas for parameter and configuration validation. - /** * Updates the `email` Corporate User Account to have a Partner Type of Channel. * diff --git a/packages/ops/src/utils/schema.ts b/packages/ops/src/utils/schema.ts index d12e11461..fe9ab3d81 100644 --- a/packages/ops/src/utils/schema.ts +++ b/packages/ops/src/utils/schema.ts @@ -60,6 +60,17 @@ export abstract class Schema { }), }); + static readonly CONFIG_USER_ALICE = z.object({ + users: z.object({ + alice: z.object({ + email_address: this.EMAIL, + }), + }), + secret: z.object({ + ALICE_PASSWORD: this.NON_EMPTY_STRING, + }), + }); + static readonly CONFIG_USER_BOB = z.object({ users: z.object({ bob: z.object({ @@ -71,6 +82,8 @@ export abstract class Schema { }), }); + static readonly CONFIG_USERS = this.CONFIG_USER_ADMIN.merge(this.CONFIG_USER_ALICE).merge(this.CONFIG_USER_BOB); + static readonly CONFIG_ADMIN_PRIVATE_KEY = z.object({ secret: z.object({ ADMIN_PRIVATE_KEY: this.NON_EMPTY_STRING, From 69b63739aeee02b3b47d9741a4b82ff399d5e234 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Tue, 18 Jun 2024 17:02:19 +0800 Subject: [PATCH 09/13] Normalised `sdk` and `ops` Playwright config. --- packages/ops/playwright.config.ts | 12 ++++++++---- packages/sdk/playwright.config.ts | 20 ++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/ops/playwright.config.ts b/packages/ops/playwright.config.ts index b9993f55b..3aaf91cf7 100644 --- a/packages/ops/playwright.config.ts +++ b/packages/ops/playwright.config.ts @@ -10,11 +10,14 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 2 : 2, + retries: 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : 1, + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [['list'], ['html', { outputFolder: 'playwright-report', open: 'never' }]], + reporter: [ + ['list', { printSteps: true }], + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ @@ -22,8 +25,9 @@ export default defineConfig({ }, projects: [ { + // NOTE (JL,2024-06-18): This 'project' useful when using the Playwright Extension in vscode. name: 'ops', - testMatch: '**/*.spec.ts', // testMatch: '**/create-vault.spec.ts', + testMatch: '**/*.spec.ts', }, ], }); diff --git a/packages/sdk/playwright.config.ts b/packages/sdk/playwright.config.ts index 4003a1b6a..579d45bef 100644 --- a/packages/sdk/playwright.config.ts +++ b/packages/sdk/playwright.config.ts @@ -1,11 +1,5 @@ import { defineConfig } from '@playwright/test'; -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - /** * See https://playwright.dev/docs/test-configuration. */ @@ -16,14 +10,24 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 0 : 0, + retries: 0, /* Opt out of parallel tests on CI. */ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [['list', { printSteps: true }]], + reporter: [ + ['list', { printSteps: true }], + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', }, + projects: [ + { + // NOTE (JL,2024-06-18): This 'project' useful when using the Playwright Extension in vscode. + name: 'sdk', + testMatch: '**/*.spec.ts', + }, + ], }); From 3382dc9ba99a4d8f7d402ca5424404cbac121bd9 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Wed, 19 Jun 2024 12:17:46 +0800 Subject: [PATCH 10/13] Tests createVault with a Tenant. Some formatting, renaming etc. --- packages/ops/test/create-vault.spec.ts | 53 ++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/ops/test/create-vault.spec.ts b/packages/ops/test/create-vault.spec.ts index 49e38bf75..369eaf6ff 100644 --- a/packages/ops/test/create-vault.spec.ts +++ b/packages/ops/test/create-vault.spec.ts @@ -1,6 +1,5 @@ import { CredbullFixedYieldVault__factory } from '@credbull/contracts'; import { expect, test } from '@playwright/test'; -import Bottleneck from 'bottleneck'; import { isAfter, isFuture, isPast } from 'date-fns'; import { ZodError } from 'zod'; @@ -8,7 +7,15 @@ import { createUser } from '@/create-user'; import { createVault, main } from '@/create-vault'; import { makeAdmin } from '@/make-admin'; import { loadConfiguration } from '@/utils/config'; -import { deleteUserIfPresent, generateAddress, signer, supabase, userByOrUndefined } from '@/utils/helpers'; +import { + deleteUserIfPresent, + generateAddress, + generatePassword, + generateRandomEmail, + signer, + supabase, + userByOrUndefined, +} from '@/utils/helpers'; const EMPTY_CONFIG = {}; const VALID_ADDRESS = generateAddress(); @@ -56,7 +63,7 @@ test.describe('Create Vault should fail when invoked with', async () => { test.describe('Create Vault Main should fail when invoked with', async () => { // NOTE (JL,2024-06-04): Internal async invocation means no other impact possible. - test('Throws error if invalid parameters', async () => { + test('an invalid Tenant Email parameter', async () => { const scenarios = { matured: false, upside: true, tenant: false }; await expect(main(scenarios, { upsideVault: VALID_ADDRESS, tenantEmail: 'someone@here' })).rejects.toThrow( @@ -154,7 +161,7 @@ test.describe('Create Vault', async () => { const { data: [vaults, ...restVaults], } = await supabaseAdmin.from('vaults').select('id, type, status, address').eq('id', created.id); - let expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; + const expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; expect(vaults).toMatchObject(expected); expect(restVaults).toEqual([]); @@ -169,9 +176,9 @@ test.describe('Create Vault', async () => { }); test('a non-matured, ready, Upside Fixed Yield vault, linked to another, open for deposits, pending for redemption', async () => { - const linkTo = await createVault(config, false, false, false); + const linkToVault = await createVault(config, false, false, false); - const created = await createVault(config, false, true, false, linkTo.address); + const created = await createVault(config, false, true, false, linkToVault.address); expect(created).toMatchObject({ type: 'fixed_yield_upside', status: 'ready' }); expect(isPast(created.deposits_opened_at)).toBe(true); expect(isFuture(created.deposits_closed_at)).toBe(true); @@ -184,7 +191,7 @@ test.describe('Create Vault', async () => { const { data: [vaults, ...restVaults], } = await supabaseAdmin.from('vaults').select('id, type, status, address').eq('id', created.id); - let expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; + const expected = { id: created.id, type: 'fixed_yield_upside', status: 'ready', address: created.address }; expect(vaults).toMatchObject(expected); expect(restVaults).toEqual([]); @@ -192,10 +199,40 @@ test.describe('Create Vault', async () => { const { data: [vaultEntity, ...restVaultEntity], } = await supabaseAdmin.from('vault_entities').select('address').eq('vault_id', created.id).eq('type', 'vault'); - expect(vaultEntity).toMatchObject({ address: linkTo.address }); + expect(vaultEntity).toMatchObject({ address: linkToVault.address }); expect(restVaultEntity).toEqual([]); await verifyVaultContract(created.address); }); + + test('a non-matured, ready, Fixed Yield vault, open for deposits, pending for redemption, with tenant', async () => { + const tenantUser = await createUser(config, generateRandomEmail('tenant'), false, generatePassword()); + + const created = await createVault(config, false, false, true, undefined, tenantUser.email); + expect(created).toMatchObject({ type: 'fixed_yield', status: 'ready' }); + expect(isPast(created.deposits_opened_at)).toBe(true); + expect(isFuture(created.deposits_closed_at)).toBe(true); + expect(isAfter(created.deposits_closed_at, created.deposits_opened_at)).toBe(true); + expect(isFuture(created.redemptions_opened_at)).toBe(true); + expect(isAfter(created.redemptions_opened_at, created.deposits_closed_at)).toBe(true); + expect(isAfter(created.redemptions_closed_at, created.redemptions_opened_at)).toBe(true); + + // Vault + const { + data: [vaults, ...restVaults], + } = await supabaseAdmin.from('vaults').select('id, type, status, address, tenant').eq('id', created.id); + const expected = { + id: created.id, + type: 'fixed_yield', + status: 'ready', + address: created.address, + tenant: tenantUser.id, + }; + expect(vaults).toMatchObject(expected); + expect(restVaults).toEqual([]); + + await verifyVaultContract(created.address); + await deleteUserIfPresent(config, tenantUser.email); + }); }); }); From a4df3ab5cd9f1ae0a0738a096c12ae2b125c3eb6 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Wed, 19 Jun 2024 13:26:15 +0800 Subject: [PATCH 11/13] Split `helpers.ts` into simpler component oriented modules. Split the helper tests into component oriented test modules. --- packages/ops/src/utils/api.ts | 48 +++++ packages/ops/src/utils/assert.ts | 17 ++ packages/ops/src/utils/database.ts | 10 ++ packages/ops/src/utils/ethers.ts | 27 +++ packages/ops/src/utils/generate.ts | 25 +++ packages/ops/src/utils/helpers.ts | 168 ------------------ packages/ops/src/utils/user.ts | 46 +++++ .../utils/{helpers.spec.ts => assert.spec.ts} | 26 +-- packages/ops/test/utils/generate.spec.ts | 20 +++ 9 files changed, 195 insertions(+), 192 deletions(-) create mode 100644 packages/ops/src/utils/api.ts create mode 100644 packages/ops/src/utils/assert.ts create mode 100644 packages/ops/src/utils/database.ts create mode 100644 packages/ops/src/utils/ethers.ts create mode 100644 packages/ops/src/utils/generate.ts delete mode 100644 packages/ops/src/utils/helpers.ts create mode 100644 packages/ops/src/utils/user.ts rename packages/ops/test/utils/{helpers.spec.ts => assert.spec.ts} (84%) create mode 100644 packages/ops/test/utils/generate.spec.ts diff --git a/packages/ops/src/utils/api.ts b/packages/ops/src/utils/api.ts new file mode 100644 index 000000000..f77b9821c --- /dev/null +++ b/packages/ops/src/utils/api.ts @@ -0,0 +1,48 @@ +import { Schema } from './schema'; + +export const headers = (session?: Awaited>) => { + return { + headers: { + 'Content-Type': 'application/json', + ...(session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : {}), + }, + }; +}; + +// FIXME (JL,2024-06-19): This logs in Admin or Bob. No consideration for poor Alice! +export const login = async ( + config: any, + opts?: { admin: boolean }, +): Promise<{ access_token: string; user_id: string }> => { + Schema.CONFIG_API_URL.parse(config); + + let _email: string, _password: string; + if (opts?.admin) { + Schema.CONFIG_USER_ADMIN.parse(config); + _email = config.users.admin.email_address; + _password = config.secret!.ADMIN_PASSWORD!; + } else { + Schema.CONFIG_USER_BOB.parse(config); + _email = config.users.bob.email_address; + _password = config.secret!.BOB_PASSWORD!; + } + + const body = JSON.stringify({ email: _email, password: _password }); + + let signIn; + try { + signIn = await fetch(`${config.api.url}/auth/api/sign-in`, { method: 'POST', body, ...headers() }); + } catch (error) { + console.error('Network error or server is down:', error); + throw error; + } + + if (!signIn.ok) { + console.error(`HTTP error! status: ${signIn.status}`); + throw new Error(`Failed to login: ${signIn.statusText}`); + } + + const data = await signIn.json(); + console.log(`sign in response: ${JSON.stringify(data)}`); + return data; +}; diff --git a/packages/ops/src/utils/assert.ts b/packages/ops/src/utils/assert.ts new file mode 100644 index 000000000..4188e85bf --- /dev/null +++ b/packages/ops/src/utils/assert.ts @@ -0,0 +1,17 @@ +import { Schema } from './schema'; + +export function assertAddress(address: string) { + Schema.ADDRESS.parse(address); +} + +export function assertEmail(email: string) { + Schema.EMAIL.parse(email); +} + +export function assertEmailOptional(email?: string | null) { + Schema.EMAIL_OPTIONAL.parse(email); +} + +export function assertUpsideVault(upsideVaultSpec?: string) { + Schema.UPSIDE_VAULT_SPEC.optional().parse(upsideVaultSpec); +} diff --git a/packages/ops/src/utils/database.ts b/packages/ops/src/utils/database.ts new file mode 100644 index 000000000..2e4fa6902 --- /dev/null +++ b/packages/ops/src/utils/database.ts @@ -0,0 +1,10 @@ +import { Database } from '@credbull/api'; +import { createClient } from '@supabase/supabase-js'; + +import { Schema } from './schema'; + +export const supabaseAdminClient = (config: any) => { + Schema.CONFIG_SUPABASE_URL.merge(Schema.CONFIG_SUPABASE_ADMIN).parse(config); + + return createClient(config.services.supabase.url, config.secret!.SUPABASE_SERVICE_ROLE_KEY!); +}; diff --git a/packages/ops/src/utils/ethers.ts b/packages/ops/src/utils/ethers.ts new file mode 100644 index 000000000..e1c9574e4 --- /dev/null +++ b/packages/ops/src/utils/ethers.ts @@ -0,0 +1,27 @@ +import { Wallet, ethers } from 'ethers'; +import { SiweMessage, generateNonce } from 'siwe'; + +import { Schema } from './schema'; + +export const linkWalletMessage = async (config: any, signer: Wallet) => { + Schema.CONFIG_APP_URL.parse(config); + + let appUrl = new URL(config.app.url); + const chainId = await signer.getChainId(); + const preMessage = new SiweMessage({ + domain: appUrl.host, + address: signer.address, + statement: 'By connecting your wallet, you agree to the Terms of Service and Privacy Policy.', + uri: appUrl.href, + version: '1', + chainId, + nonce: generateNonce(), + }); + + return preMessage.prepareMessage(); +}; + +export const signerFor = (config: any, privateKey: string) => { + Schema.CONFIG_ETHERS_URL.parse(config); + return new Wallet(privateKey, new ethers.providers.JsonRpcProvider(config.services.ethers.url)); +}; diff --git a/packages/ops/src/utils/generate.ts b/packages/ops/src/utils/generate.ts new file mode 100644 index 000000000..f611563c5 --- /dev/null +++ b/packages/ops/src/utils/generate.ts @@ -0,0 +1,25 @@ +import crypto from 'crypto'; +import { Wallet } from 'ethers'; + +export const generateAddress = () => { + const id = crypto.randomBytes(32).toString('hex'); + const privateKey = '0x' + id; + + const wallet = new Wallet(privateKey); + return wallet.address; +}; + +export const generatePassword = ( + length = 15, + characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@-#$', +) => { + return Array.from(crypto.getRandomValues(new Uint32Array(length))) + .map((x) => characters[x % characters.length]) + .join(''); +}; + +export function generateRandomEmail(prefix: string): string { + const randomString = Math.random().toString(36).substring(2, 10); // Generates a weak, pseudorandom string + const domain = '@credbull.io'; + return `${prefix}+${randomString}${domain}`; +} diff --git a/packages/ops/src/utils/helpers.ts b/packages/ops/src/utils/helpers.ts deleted file mode 100644 index 3f6aa7c15..000000000 --- a/packages/ops/src/utils/helpers.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Database } from '@credbull/api'; -import { SupabaseClient, createClient } from '@supabase/supabase-js'; -import crypto from 'crypto'; -import { Wallet, ethers } from 'ethers'; -import { SiweMessage, generateNonce } from 'siwe'; - -import { Schema } from './schema'; - -export const supabase = (config: any, opts?: { admin: boolean }) => { - Schema.CONFIG_SUPABASE_URL.merge(opts?.admin ? Schema.CONFIG_SUPABASE_ADMIN : Schema.CONFIG_SUPABASE_ANONYMOUS).parse( - config, - ); - - return createClient( - config.services.supabase.url, - opts?.admin ? config.secret!.SUPABASE_SERVICE_ROLE_KEY! : config.secret!.SUPABASE_ANONYMOUS_KEY!, - ); -}; - -export const userByOrThrow = async (supabaseAdmin: SupabaseClient, email: string) => { - const user = await userByOrUndefined(supabaseAdmin, email); - if (!user) throw new Error('No User for ' + email); - return user; -}; - -/** - * Searches for the `email` User, returning `undefined` if not found. - * Only throws an error in a unrecoverable scenario. - * - * @param supabaseAdmin A `SupabaseClient` with administrative access. - * @param email The `string` email address. Must be valid. - * @returns A `Promise` of a User `any` or `undefined` if not found. - * @throws ZodError if `email` is not an email address. - * @throws PostgrestError if there is an error searching for the User. - * @throws AuthError if there is an error accessing the database. - * @throws Error if there is a system error or if the result pagination mechanism is broken. - */ -export const userByOrUndefined = async (supabaseAdmin: SupabaseClient, email: string): Promise => { - Schema.EMAIL.parse(email); - - const pageSize = 1_000; - const { - data: { users }, - error, - } = await supabaseAdmin.auth.admin.listUsers({ perPage: pageSize }); - if (error) throw error; - if (users.length === pageSize) throw Error('Implement pagination'); - return users.find((u) => u.email === email); -}; - -export const deleteUserIfPresent = async (supabaseAdmin: SupabaseClient, email: string) => { - await userByOrThrow(supabaseAdmin, email) - .then((user) => { - supabaseAdmin.auth.admin.deleteUser(user.id, false); - }) - .catch((error) => { - // Ignore. - }); -}; - -export const headers = (session?: Awaited>) => { - return { - headers: { - 'Content-Type': 'application/json', - ...(session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : {}), - }, - }; -}; - -export const login = async ( - config: any, - opts?: { admin: boolean }, -): Promise<{ access_token: string; user_id: string }> => { - Schema.CONFIG_API_URL.parse(config); - - let _email: string, _password: string; - if (opts?.admin) { - Schema.CONFIG_USER_ADMIN.parse(config); - _email = config.users.admin.email_address; - _password = config.secret!.ADMIN_PASSWORD!; - } else { - Schema.CONFIG_USER_BOB.parse(config); - _email = config.users.bob.email_address; - _password = config.secret!.BOB_PASSWORD!; - } - - const body = JSON.stringify({ email: _email, password: _password }); - - let signIn; - - try { - signIn = await fetch(`${config.api.url}/auth/api/sign-in`, { method: 'POST', body, ...headers() }); - } catch (error) { - console.error('Network error or server is down:', error); - throw error; - } - - if (!signIn.ok) { - console.error(`HTTP error! status: ${signIn.status}`); - throw new Error(`Failed to login: ${signIn.statusText}`); - } - - const data = await signIn.json(); - console.log(`sign in response: ${JSON.stringify(data)}`); - return data; -}; - -export const linkWalletMessage = async (config: any, signer: Wallet) => { - Schema.CONFIG_APP_URL.parse(config); - - let appUrl = new URL(config.app.url); - const chainId = await signer.getChainId(); - const preMessage = new SiweMessage({ - domain: appUrl.host, - address: signer.address, - statement: 'By connecting your wallet, you agree to the Terms of Service and Privacy Policy.', - uri: appUrl.href, - version: '1', - chainId, - nonce: generateNonce(), - }); - - return preMessage.prepareMessage(); -}; - -export const signer = (config: any, privateKey: string) => { - Schema.CONFIG_ETHERS_URL.parse(config); - return new Wallet(privateKey, new ethers.providers.JsonRpcProvider(config.services.ethers.url)); -}; - -export const generateAddress = () => { - const id = crypto.randomBytes(32).toString('hex'); - const privateKey = '0x' + id; - - const wallet = new Wallet(privateKey); - return wallet.address; -}; - -export const generatePassword = ( - length = 15, - characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@-#$', -) => { - return Array.from(crypto.getRandomValues(new Uint32Array(length))) - .map((x) => characters[x % characters.length]) - .join(''); -}; - -export function assertAddress(address: string) { - Schema.ADDRESS.parse(address); -} - -export function assertEmail(email: string) { - Schema.EMAIL.parse(email); -} - -export function assertEmailOptional(email?: string | null) { - Schema.EMAIL_OPTIONAL.parse(email); -} - -export function assertUpsideVault(upsideVaultSpec?: string) { - Schema.UPSIDE_VAULT_SPEC.optional().parse(upsideVaultSpec); -} - -export function generateRandomEmail(prefix: string): string { - const randomString = Math.random().toString(36).substring(2, 10); // Generates a weak, pseudorandom string - const domain = '@credbull.io'; - return `${prefix}+${randomString}${domain}`; -} diff --git a/packages/ops/src/utils/user.ts b/packages/ops/src/utils/user.ts new file mode 100644 index 000000000..ba03aa97c --- /dev/null +++ b/packages/ops/src/utils/user.ts @@ -0,0 +1,46 @@ +import { SupabaseClient } from '@supabase/supabase-js'; + +import { Schema } from './schema'; + +// An ad-hoc Query Page Size for User Queries. +const PAGE_SIZE = 1_000; + +/** + * Searches for the `email` User, returning `undefined` if not found. + * Only throws an error in a unrecoverable scenario. + * + * @param supabaseAdmin A `SupabaseClient` with administrative access. + * @param email The `string` email address. Must be valid. + * @returns A `Promise` of a User `any` or `undefined` if not found. + * @throws ZodError if `email` is not an email address. + * @throws PostgrestError if there is an error searching for the User. + * @throws AuthError if there is an error accessing the database. + * @throws Error if there is a system error or if the result pagination mechanism is broken. + */ +export const userByOrUndefined = async (supabaseAdmin: SupabaseClient, email: string): Promise => { + Schema.EMAIL.parse(email); + + const { + data: { users }, + error, + } = await supabaseAdmin.auth.admin.listUsers({ perPage: PAGE_SIZE }); + if (error) throw error; + if (users.length === PAGE_SIZE) throw Error('Implement pagination'); + return users.find((u) => u.email === email); +}; + +export const userByOrThrow = async (supabaseAdmin: SupabaseClient, email: string) => { + const user = await userByOrUndefined(supabaseAdmin, email); + if (!user) throw new Error('No User for ' + email); + return user; +}; + +export const deleteUserIfPresent = async (supabaseAdmin: SupabaseClient, email: string) => { + await userByOrThrow(supabaseAdmin, email) + .then((user) => { + supabaseAdmin.auth.admin.deleteUser(user.id, false); + }) + .catch((error) => { + // Ignore. + }); +}; diff --git a/packages/ops/test/utils/helpers.spec.ts b/packages/ops/test/utils/assert.spec.ts similarity index 84% rename from packages/ops/test/utils/helpers.spec.ts rename to packages/ops/test/utils/assert.spec.ts index 23d995721..71766efd7 100644 --- a/packages/ops/test/utils/helpers.spec.ts +++ b/packages/ops/test/utils/assert.spec.ts @@ -1,14 +1,8 @@ import { expect, test } from '@playwright/test'; import { ZodError } from 'zod'; -import { - assertAddress, - assertEmail, - assertEmailOptional, - assertUpsideVault, - generateAddress, - generateRandomEmail, -} from '../../src/utils/helpers'; +import { assertAddress, assertEmail, assertEmailOptional, assertUpsideVault } from '@/utils/assert'; +import { generateRandomEmail } from '@/utils/generate'; test.describe('Asserting an email parameter with', async () => { test('a valid email should pass', async () => { @@ -98,19 +92,3 @@ test.describe('Asserting an Upside Vault Specifier with', async () => { correctlyRejectsAddress(assertUpsideVault); }); }); - -test.describe('Generating an address should', async () => { - test('produce a valid address every iteration', async () => { - for (let i = 0; i < 20; i++) { - expect(() => assertAddress(generateAddress())).toPass(); - } - }); -}); - -test.describe('Generating an email should', async () => { - test('produce a valid email every iteration', async () => { - for (let i = 0; i < 20; i++) { - expect(() => assertEmail(generateRandomEmail('test-' + i))).toPass(); - } - }); -}); diff --git a/packages/ops/test/utils/generate.spec.ts b/packages/ops/test/utils/generate.spec.ts new file mode 100644 index 000000000..e35f3ed35 --- /dev/null +++ b/packages/ops/test/utils/generate.spec.ts @@ -0,0 +1,20 @@ +import { expect, test } from '@playwright/test'; + +import { assertAddress, assertEmail } from '@/utils/assert'; +import { generateAddress, generateRandomEmail } from '@/utils/generate'; + +test.describe('Generating an address should', async () => { + test('produce a valid address every iteration', async () => { + for (let i = 0; i < 20; i++) { + expect(() => assertAddress(generateAddress())).toPass(); + } + }); +}); + +test.describe('Generating an email should', async () => { + test('produce a valid email every iteration', async () => { + for (let i = 0; i < 20; i++) { + expect(() => assertEmail(generateRandomEmail('test-' + i))).toPass(); + } + }); +}); From e40a00dda2da18226cec3e1f50e4d03afb7df099 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Wed, 19 Jun 2024 13:26:53 +0800 Subject: [PATCH 12/13] Updated all scripts to use the appropriate utility functions. --- packages/ops/.env.sample | 2 -- packages/ops/src/clean-vault-table.ts | 7 ++++--- packages/ops/src/create-user.ts | 6 ++++-- packages/ops/src/create-vault.ts | 18 +++++++----------- packages/ops/src/deposit-with-upside.ts | 24 ++++++++++++++---------- packages/ops/src/deposit.ts | 21 ++++++++++++--------- packages/ops/src/make-admin.ts | 6 ++++-- packages/ops/src/make-channel.ts | 6 ++++-- packages/ops/src/redeem-with-upside.ts | 16 ++++++++++------ packages/ops/src/redeem.ts | 15 +++++++++------ packages/ops/src/utils/config.ts | 8 ++++---- packages/ops/test/create-user.spec.ts | 5 +++-- packages/ops/test/create-vault.spec.ts | 17 ++++++----------- packages/ops/test/make-admin.spec.ts | 6 ++++-- packages/ops/test/make-channel.spec.ts | 6 ++++-- 15 files changed, 89 insertions(+), 74 deletions(-) diff --git a/packages/ops/.env.sample b/packages/ops/.env.sample index d005e30cb..1162612cf 100644 --- a/packages/ops/.env.sample +++ b/packages/ops/.env.sample @@ -1,5 +1,3 @@ -SUPABASE_ANONYMOUS_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 - ADMIN_PASSWORD=NeverGuessThis # Dev/Anvil Wallet, PrivateKey[0] (0xac097...ff80) - okay to share ADMIN_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 diff --git a/packages/ops/src/clean-vault-table.ts b/packages/ops/src/clean-vault-table.ts index dd1235223..c98255cc7 100644 --- a/packages/ops/src/clean-vault-table.ts +++ b/packages/ops/src/clean-vault-table.ts @@ -1,7 +1,8 @@ import { CredbullFixedYieldVault__factory } from '@credbull/contracts'; import { loadConfiguration } from './utils/config'; -import { signer, supabase } from './utils/helpers'; +import { supabaseAdminClient } from './utils/database'; +import { signerFor } from './utils/ethers'; import { Schema } from './utils/schema'; /** @@ -14,7 +15,7 @@ import { Schema } from './utils/schema'; export const cleanVaultTable = async (config: any) => { Schema.CONFIG_ADMIN_PRIVATE_KEY.parse(config); - const supabaseAdmin = supabase(config, { admin: true }); + const supabaseAdmin = supabaseAdminClient(config); const { data, error } = await supabaseAdmin.from('vaults').select(); if (error) throw error; if (data.length === 0) { @@ -22,7 +23,7 @@ export const cleanVaultTable = async (config: any) => { return; } - const adminSigner = signer(config, config.secret.ADMIN_PRIVATE_KEY); + const adminSigner = signerFor(config, config.secret.ADMIN_PRIVATE_KEY); console.log('='.repeat(80)); console.log(` Pausing ${data.length} Vaults.`); diff --git a/packages/ops/src/create-user.ts b/packages/ops/src/create-user.ts index 46d41aebb..990564770 100644 --- a/packages/ops/src/create-user.ts +++ b/packages/ops/src/create-user.ts @@ -1,6 +1,8 @@ import { makeChannel } from './make-channel'; +import { assertEmail } from './utils/assert'; import { loadConfiguration } from './utils/config'; -import { assertEmail, generatePassword, supabase } from './utils/helpers'; +import { supabaseAdminClient } from './utils/database'; +import { generatePassword } from './utils/generate'; import { Schema } from './utils/schema'; /** @@ -28,7 +30,7 @@ export const createUser = async ( Schema.NON_EMPTY_STRING.optional().parse(passwordMaybe); assertEmail(email); - const supabaseAdmin = supabase(config, { admin: true }); + const supabaseAdmin = supabaseAdminClient(config); const password = passwordMaybe || generatePassword(); const { data: { user }, diff --git a/packages/ops/src/create-vault.ts b/packages/ops/src/create-vault.ts index c23959065..52e798358 100644 --- a/packages/ops/src/create-vault.ts +++ b/packages/ops/src/create-vault.ts @@ -2,17 +2,13 @@ import { CredbullFixedYieldVault__factory, CredbullVaultFactory__factory } from import type { ICredbull } from '@credbull/contracts/types/CredbullFixedYieldVaultFactory'; import { addYears, startOfWeek, startOfYear, subDays } from 'date-fns'; +import { headers, login } from './utils/api'; +import { assertEmailOptional, assertUpsideVault } from './utils/assert'; import { loadConfiguration } from './utils/config'; -import { - assertEmailOptional, - assertUpsideVault, - headers, - login, - signer, - supabase, - userByOrThrow, -} from './utils/helpers'; +import { supabaseAdminClient } from './utils/database'; +import { signerFor } from './utils/ethers'; import { Schema } from './utils/schema'; +import { userByOrThrow } from './utils/user'; type CreateVaultParams = { treasury: string | undefined; @@ -127,7 +123,7 @@ export const createVault = async ( assertUpsideVault(upsideVault); assertEmailOptional(tenantEmail); - const supabaseAdmin = supabase(config, { admin: true }); + const supabaseAdmin = supabaseAdminClient(config); const addresses = await supabaseAdmin.from('contracts_addresses').select(); if (addresses.error) throw addresses.error; @@ -135,7 +131,7 @@ export const createVault = async ( // for allowCustodian we need the Admin user. for createVault we need the Operator Key. // the only way this works is if you go into supabase and associate the admin user with the Operator wallet - const adminSigner = signer(config, config.secret.ADMIN_PRIVATE_KEY); + const adminSigner = signerFor(config, config.secret.ADMIN_PRIVATE_KEY); // TODO: ISSUE we are logging in here as the Admin User - but later we POST to the createVault owned by the OPERATOR const admin = await login(config, { admin: true }); diff --git a/packages/ops/src/deposit-with-upside.ts b/packages/ops/src/deposit-with-upside.ts index a08c8b0c7..91011cc20 100644 --- a/packages/ops/src/deposit-with-upside.ts +++ b/packages/ops/src/deposit-with-upside.ts @@ -3,32 +3,36 @@ import { MockStablecoin__factory, MockToken__factory, } from '@credbull/contracts'; -import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils'; +import { formatEther, parseUnits } from 'ethers/lib/utils'; -import { headers, linkWalletMessage, login, signer, supabase } from './utils/helpers'; +import { headers, login } from './utils/api'; +import { loadConfiguration } from './utils/config'; +import { supabaseAdminClient } from './utils/database'; +import { linkWalletMessage, signerFor } from './utils/ethers'; export const main = () => { setTimeout(async () => { console.log('\n'); console.log('====================================='); console.log('\n'); + const config = loadConfiguration(); // console.log('Bob: retrieves a session through api.'); - const bob = await login(); + const bob = await login(config); const bobHeaders = headers(bob); console.log('Bob: retrieves a session through api. - OK'); // console.log('Bob: signs a message with his wallet.'); - const bobSigner = signer(process.env.BOB_PRIVATE_KEY || ""); - const message = await linkWalletMessage(bobSigner); + const bobSigner = signerFor(config, config.secret!.BOB_PRIVATE_KEY!); + const message = await linkWalletMessage(config, bobSigner); const signature = await bobSigner.signMessage(message); console.log('Bob: signs a message with his wallet. - OK'); // console.log('Bob: sends the signed message to Credbull so that he can be KYC`ed.'); - await fetch(`${process.env.API_BASE_URL}/accounts/link-wallet`, { + await fetch(`${config.api.url}/accounts/link-wallet`, { method: 'POST', - body: JSON.stringify({ message, signature, discriminator: 'bob@partner.com' }), + body: JSON.stringify({ message, signature, discriminator: config.users.bob.email_address }), ...bobHeaders, }); console.log('Bob: sends the signed message to Credbull so that he can be KYC`ed. - OK'); @@ -37,7 +41,7 @@ export const main = () => { const admin = await login({ admin: true }); const adminHeaders = headers(admin); - await fetch(`${process.env.API_BASE_URL}/accounts/whitelist`, { + await fetch(`${config.api.url}/accounts/whitelist`, { method: 'POST', body: JSON.stringify({ user_id: bob.user_id, address: bobSigner.address }), ...adminHeaders, @@ -45,7 +49,7 @@ export const main = () => { console.log('Admin: receives the approval and KYCs Bob. - OK'); // console.log('Bob: queries for existing vaults.'); - const vaultsResponse = await fetch(`${process.env.API_BASE_URL}/vaults/current`, { + const vaultsResponse = await fetch(`${config.api.url}/vaults/current`, { method: 'GET', ...bobHeaders, }); @@ -66,7 +70,7 @@ export const main = () => { await approveTx.wait(); console.log('Bob: gives the approval to the vault to swap it`s USDC. - OK'); - const client = supabase({ admin: true }); + const client = supabaseAdminClient(config); const addresses = await client.from('contracts_addresses').select(); if (addresses.error) return addresses; diff --git a/packages/ops/src/deposit.ts b/packages/ops/src/deposit.ts index e32e10151..adbdd235f 100644 --- a/packages/ops/src/deposit.ts +++ b/packages/ops/src/deposit.ts @@ -1,28 +1,31 @@ import { CredbullFixedYieldVault__factory, MockStablecoin__factory } from '@credbull/contracts'; import { parseUnits } from 'ethers/lib/utils'; -import { headers, linkWalletMessage, login, signer } from './utils/helpers'; +import { headers, login } from './utils/api'; +import { loadConfiguration } from './utils/config'; +import { linkWalletMessage, signerFor } from './utils/ethers'; export const main = () => { setTimeout(async () => { console.log('\n'); console.log('====================================='); console.log('\n'); + const config = loadConfiguration(); // console.log('Bob: retrieves a session through api.'); - const bob = await login(); + const bob = await login(config); const bobHeaders = headers(bob); console.log('Bob: retrieves a session through api. - OK'); // console.log('Bob: signs a message with his wallet.'); - const bobSigner = signer(process.env.BOB_PRIVATE_KEY); - const message = await linkWalletMessage(bobSigner); + const bobSigner = signerFor(config, config.secret!.BOB_PRIVATE_KEY!); + const message = await linkWalletMessage(config, bobSigner); const signature = await bobSigner.signMessage(message); console.log('Bob: signs a message with his wallet. - OK'); // console.log('Bob: sends the signed message to Credbull so that he can be KYC`ed.'); - await fetch(`${process.env.API_BASE_URL}/accounts/link-wallet`, { + await fetch(`${config.api.url}/accounts/link-wallet`, { method: 'POST', body: JSON.stringify({ message, signature, discriminator: 'bob@partner.com' }), ...bobHeaders, @@ -32,9 +35,9 @@ export const main = () => { // console.log('Admin: receives the approval and KYCs Bob.'); const admin = await login({ admin: true }); const adminHeaders = headers(admin); - const adminSigner = signer(process.env.ADMIN_PRIVATE_KEY); + const adminSigner = signerFor(config, config.secret!.ADMIN_PRIVATE_KEY!); - await fetch(`${process.env.API_BASE_URL}/accounts/whitelist`, { + await fetch(`${config.api.url}/accounts/whitelist`, { method: 'POST', body: JSON.stringify({ user_id: bob.user_id, address: bobSigner.address }), ...adminHeaders, @@ -42,7 +45,7 @@ export const main = () => { console.log('Admin: receives the approval and KYCs Bob. - OK'); // console.log('Bob: queries for existing vaults.'); - const vaultsResponse = await fetch(`${process.env.API_BASE_URL}/vaults/current`, { + const vaultsResponse = await fetch(`${config.api.url}/vaults/current`, { method: 'GET', ...bobHeaders, }); @@ -67,7 +70,7 @@ export const main = () => { const toggleTx = await vault.connect(adminSigner).toggleWindowCheck(false); await toggleTx.wait(); - const depositTx = await vault.deposit(parseUnits("1000", "mwei"), bobSigner.address, { gasLimit: 10000000 }); + const depositTx = await vault.deposit(parseUnits('1000', 'mwei'), bobSigner.address, { gasLimit: 10000000 }); await depositTx.wait(); console.log('Bob: deposits his USDC in the vault. - OK'); diff --git a/packages/ops/src/make-admin.ts b/packages/ops/src/make-admin.ts index f7647a274..2fe3a4cb7 100644 --- a/packages/ops/src/make-admin.ts +++ b/packages/ops/src/make-admin.ts @@ -1,5 +1,7 @@ +import { assertEmail } from './utils/assert'; import { loadConfiguration } from './utils/config'; -import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; +import { supabaseAdminClient } from './utils/database'; +import { userByOrThrow } from './utils/user'; // TODO (JL,2024-06-05): Add `update-metadata` script and use for Make Admin/Channel. @@ -16,7 +18,7 @@ import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; export const makeAdmin = async (config: any, email: string): Promise => { assertEmail(email); - const supabaseAdmin = supabase(config, { admin: true }); + const supabaseAdmin = supabaseAdminClient(config); const toUpdate = await userByOrThrow(supabaseAdmin, email); const { data: { user }, diff --git a/packages/ops/src/make-channel.ts b/packages/ops/src/make-channel.ts index 64645c655..1ee8bc10d 100644 --- a/packages/ops/src/make-channel.ts +++ b/packages/ops/src/make-channel.ts @@ -1,5 +1,7 @@ +import { assertEmail } from './utils/assert'; import { loadConfiguration } from './utils/config'; -import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; +import { supabaseAdminClient } from './utils/database'; +import { userByOrThrow } from './utils/user'; /** * Updates the `email` Corporate User Account to have a Partner Type of Channel. @@ -14,7 +16,7 @@ import { assertEmail, supabase, userByOrThrow } from './utils/helpers'; export const makeChannel = async (config: any, email: string): Promise => { assertEmail(email); - const supabaseAdmin = supabase(config, { admin: true }); + const supabaseAdmin = supabaseAdminClient(config); const toUpdate = await userByOrThrow(supabaseAdmin, email); const { data: { user }, diff --git a/packages/ops/src/redeem-with-upside.ts b/packages/ops/src/redeem-with-upside.ts index a48005cd6..9abca1186 100644 --- a/packages/ops/src/redeem-with-upside.ts +++ b/packages/ops/src/redeem-with-upside.ts @@ -3,27 +3,31 @@ import { MockStablecoin__factory, MockToken__factory, } from '@credbull/contracts'; -import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils'; +import { formatEther, parseUnits } from 'ethers/lib/utils'; -import { headers, login, signer, supabase } from './utils/helpers'; +import { headers, login } from './utils/api'; +import { loadConfiguration } from './utils/config'; +import { supabaseAdminClient } from './utils/database'; +import { signerFor } from './utils/ethers'; export const main = () => { setTimeout(async () => { console.log('\n'); console.log('====================================='); console.log('\n'); + const config = loadConfiguration(); // console.log('Bob: retrieves a session through api.'); - const bob = await login(); + const bob = await login(config); const bobHeaders = headers(bob); console.log('Bob: retrieves a session through api. - OK'); // console.log('Bob: signs a message with his wallet.'); - const bobSigner = signer(process.env.BOB_PRIVATE_KEY); + const bobSigner = signerFor(config, config.secret!.BOB_PRIVATE_KEY!); // console.log('Bob: queries for existing vaults.'); - const vaultsResponse = await fetch(`${process.env.API_BASE_URL}/vaults/current`, { + const vaultsResponse = await fetch(`${config.api.url}/vaults/current`, { method: 'GET', ...bobHeaders, }); @@ -35,7 +39,7 @@ export const main = () => { const vaultAddress = vaults['data'][0].address; const usdcAddress = vaults['data'][0].asset_address; - const client = supabase({ admin: true }); + const client = supabaseAdminClient(config); const addresses = await client.from('contracts_addresses').select(); if (addresses.error) return addresses; diff --git a/packages/ops/src/redeem.ts b/packages/ops/src/redeem.ts index 9fa4f9ceb..3c3ab6290 100644 --- a/packages/ops/src/redeem.ts +++ b/packages/ops/src/redeem.ts @@ -1,25 +1,28 @@ import { CredbullFixedYieldVault__factory, MockStablecoin__factory } from '@credbull/contracts'; -import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils'; +import { formatEther, parseUnits } from 'ethers/lib/utils'; -import { headers, login, signer } from './utils/helpers'; +import { headers, login } from './utils/api'; +import { loadConfiguration } from './utils/config'; +import { signerFor } from './utils/ethers'; export const main = () => { setTimeout(async () => { console.log('\n'); console.log('====================================='); console.log('\n'); + const config = loadConfiguration(); // console.log('Bob: retrieves a session through api.'); - const bob = await login(); + const bob = await login(config); const bobHeaders = headers(bob); console.log('Bob: retrieves a session through api. - OK'); // console.log('Bob: signs a message with his wallet.'); - const bobSigner = signer(process.env.BOB_PRIVATE_KEY); + const bobSigner = signerFor(config, config.secret!.BOB_PRIVATE_KEY!); // console.log('Bob: queries for existing vaults.'); - const vaultsResponse = await fetch(`${process.env.API_BASE_URL}/vaults/current`, { + const vaultsResponse = await fetch(`${config.api.url}/vaults/current`, { method: 'GET', ...bobHeaders, }); @@ -33,7 +36,7 @@ export const main = () => { const usdc = MockStablecoin__factory.connect(usdcAddress, bobSigner); const vault = CredbullFixedYieldVault__factory.connect(vaultAddress, bobSigner); - const mintTx = await usdc.mint(vaultAddress, parseUnits('1000', 'mwei')); + const mintTx = await usdc.mint(vaultAddress, parseUnits('1000', 'mwei')); await mintTx.wait(); const shares = await vault.balanceOf(bobSigner.address); diff --git a/packages/ops/src/utils/config.ts b/packages/ops/src/utils/config.ts index 5a58e712a..92108f4a6 100644 --- a/packages/ops/src/utils/config.ts +++ b/packages/ops/src/utils/config.ts @@ -9,8 +9,8 @@ dotenv.config({ encoding: 'utf-8', path: [ path.resolve(__dirname, '../../../../.env'), // credbull-defi (root) - path.resolve(__dirname, '../../../.env'), // script - path.resolve(__dirname, '../../.env'), // operation + path.resolve(__dirname, '../../../.env'), // packages + path.resolve(__dirname, '../../.env'), // ops ], override: true, }); @@ -18,10 +18,10 @@ dotenv.config({ interface Config { secret?: { SUPABASE_SERVICE_ROLE_KEY?: string; - SUPABASE_ANONYMOUS_KEY?: string; ADMIN_PASSWORD?: string; ADMIN_PRIVATE_KEY?: string; ALICE_PASSWORD?: string; + ALICE_PRIVATE_KEY?: string; BOB_PASSWORD?: string; BOB_PRIVATE_KEY?: string; }; @@ -48,10 +48,10 @@ export const loadConfiguration = (): Config => { // NB - call this after the log statement to avoid logging keys! config.secret = config.secret || {}; // ensure config.env exists config.secret.SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; - config.secret.SUPABASE_ANONYMOUS_KEY = process.env.SUPABASE_ANONYMOUS_KEY; config.secret.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD; config.secret.ADMIN_PRIVATE_KEY = process.env.ADMIN_PRIVATE_KEY; config.secret.ALICE_PASSWORD = process.env.ALICE_PASSWORD; + config.secret.ALICE_PRIVATE_KEY = process.env.ALICE_PRIVATE_KEY; config.secret.BOB_PASSWORD = process.env.BOB_PASSWORD; config.secret.BOB_PRIVATE_KEY = process.env.BOB_PRIVATE_KEY; diff --git a/packages/ops/test/create-user.spec.ts b/packages/ops/test/create-user.spec.ts index ab1c324b8..d4555e54b 100644 --- a/packages/ops/test/create-user.spec.ts +++ b/packages/ops/test/create-user.spec.ts @@ -3,7 +3,8 @@ import { ZodError } from 'zod'; import { createUser, main } from '@/create-user'; import { loadConfiguration } from '@/utils/config'; -import { deleteUserIfPresent, supabase, userByOrUndefined } from '@/utils/helpers'; +import { supabaseAdminClient } from '@/utils/database'; +import { deleteUserIfPresent, userByOrUndefined } from '@/utils/user'; const EMAIL_ADDRESS = 'minion@create.user.test'; const PASSWORD = 'DoNotForget'; @@ -17,7 +18,7 @@ test.beforeAll(() => { // NOTE (JL,2024-05-31): This loads the same configuration as the operations themselves. config = loadConfiguration(); - supabaseAdmin = supabase(config, { admin: true }); + supabaseAdmin = supabaseAdminClient(config); ({ data: { subscription }, } = supabaseAdmin.auth.onAuthStateChange((event: any, session: any) => { diff --git a/packages/ops/test/create-vault.spec.ts b/packages/ops/test/create-vault.spec.ts index 369eaf6ff..c7f3c9b0d 100644 --- a/packages/ops/test/create-vault.spec.ts +++ b/packages/ops/test/create-vault.spec.ts @@ -7,15 +7,10 @@ import { createUser } from '@/create-user'; import { createVault, main } from '@/create-vault'; import { makeAdmin } from '@/make-admin'; import { loadConfiguration } from '@/utils/config'; -import { - deleteUserIfPresent, - generateAddress, - generatePassword, - generateRandomEmail, - signer, - supabase, - userByOrUndefined, -} from '@/utils/helpers'; +import { supabaseAdminClient } from '@/utils/database'; +import { signerFor } from '@/utils/ethers'; +import { generateAddress, generatePassword, generateRandomEmail } from '@/utils/generate'; +import { deleteUserIfPresent, userByOrUndefined } from '@/utils/user'; const EMPTY_CONFIG = {}; const VALID_ADDRESS = generateAddress(); @@ -83,8 +78,8 @@ test.describe('Create Vault', async () => { let adminSigner: any | undefined = undefined; test.beforeAll(async () => { - supabaseAdmin = supabase(config, { admin: true }); - adminSigner = signer(config, config.secret.ADMIN_PRIVATE_KEY); + supabaseAdmin = supabaseAdminClient(config); + adminSigner = signerFor(config, config.secret.ADMIN_PRIVATE_KEY); // Ensure the admin user exists. if (!(await userByOrUndefined(supabaseAdmin, config.users.admin.email_address))) { diff --git a/packages/ops/test/make-admin.spec.ts b/packages/ops/test/make-admin.spec.ts index 4761ac826..353074c64 100644 --- a/packages/ops/test/make-admin.spec.ts +++ b/packages/ops/test/make-admin.spec.ts @@ -4,7 +4,9 @@ import { ZodError } from 'zod'; import { createUser } from '@/create-user'; import { main, makeAdmin } from '@/make-admin'; import { loadConfiguration } from '@/utils/config'; -import { deleteUserIfPresent, generateRandomEmail, supabase, userByOrThrow } from '@/utils/helpers'; +import { supabaseAdminClient } from '@/utils/database'; +import { generateRandomEmail } from '@/utils/generate'; +import { deleteUserIfPresent, userByOrThrow } from '@/utils/user'; const PASSWORD = 'DoNotForget'; @@ -20,7 +22,7 @@ test.beforeAll(() => { email1 = generateRandomEmail('test-admin1'); email2 = generateRandomEmail('test-admin2'); - supabaseAdmin = supabase(config, { admin: true }); + supabaseAdmin = supabaseAdminClient(config); ({ data: { subscription }, } = supabaseAdmin.auth.onAuthStateChange((event: any, session: any) => { diff --git a/packages/ops/test/make-channel.spec.ts b/packages/ops/test/make-channel.spec.ts index 07b803d84..47a5f2b55 100644 --- a/packages/ops/test/make-channel.spec.ts +++ b/packages/ops/test/make-channel.spec.ts @@ -4,7 +4,9 @@ import { ZodError } from 'zod'; import { createUser } from '@/create-user'; import { main, makeChannel } from '@/make-channel'; import { loadConfiguration } from '@/utils/config'; -import { deleteUserIfPresent, generateRandomEmail, supabase, userByOrThrow } from '@/utils/helpers'; +import { supabaseAdminClient } from '@/utils/database'; +import { generateRandomEmail } from '@/utils/generate'; +import { deleteUserIfPresent, userByOrThrow } from '@/utils/user'; const PASSWORD = 'DoNotForget'; @@ -21,7 +23,7 @@ test.beforeAll(() => { email1 = generateRandomEmail('test.channel'); email2 = generateRandomEmail('test.channel'); - supabaseAdmin = supabase(config, { admin: true }); + supabaseAdmin = supabaseAdminClient(config); ({ data: { subscription }, } = supabaseAdmin.auth.onAuthStateChange((event: any, session: any) => { From d66bc50b5068a49b8927d83c1c09a3ba9805fec1 Mon Sep 17 00:00:00 2001 From: Jonathan Lodge Date: Wed, 19 Jun 2024 13:28:19 +0800 Subject: [PATCH 13/13] Removed the Supabase Anonymous Key as it was never actually used in `ops` --- .github/workflows/ci-dev-ops.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci-dev-ops.yml b/.github/workflows/ci-dev-ops.yml index e7b825feb..763a6e71c 100644 --- a/.github/workflows/ci-dev-ops.yml +++ b/.github/workflows/ci-dev-ops.yml @@ -55,12 +55,10 @@ jobs: ALICE_PRIVATE_KEY: ${{ secrets.DEVOPS_SEPOLIA_ALICE_PRIVATE_KEY }} BOB_PRIVATE_KEY: ${{ secrets.DEVOPS_SEPOLIA_BOB_PRIVATE_KEY }} SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} - SUPABASE_ANONYMOUS_KEY: ${{ vars.SUPABASE_ANONYMOUS_KEY }} ADMIN_PASSWORD: NeverGuessThis ALICE_PASSWORD: alice-1234 BOB_PASSWORD: bobword - - name: Cleanup Yarn Cache if: always() - run: yarn cache clean \ No newline at end of file + run: yarn cache clean