diff --git a/src/cli.ts b/src/cli.ts index b1d3e53..a4c3116 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -58,9 +58,7 @@ interface TokenDeploymentOptions extends BaseDeploymentOptions {} /** * Options for pool deployment command */ -interface PoolDeploymentOptions extends BaseDeploymentOptions { - tokenAddress: string; // Token address (required for pool deployment) -} +interface PoolDeploymentOptions extends BaseDeploymentOptions {} function createProgram(): Command { return new Command() @@ -233,9 +231,7 @@ async function handlePoolDeployment(options: PoolDeploymentOptions): Promise', 'Path to input JSON file') .requiredOption('-d, --deployer
', 'TokenPoolFactory contract address') - .requiredOption('-t, --token-address
', 'Token address') .requiredOption('--salt ', 'Salt for create2') .option('-o, --output ', 'Path to output file (defaults to stdout)') .addOption( diff --git a/src/generators/poolDeployment.ts b/src/generators/poolDeployment.ts index 2073dd3..310b3a0 100644 --- a/src/generators/poolDeployment.ts +++ b/src/generators/poolDeployment.ts @@ -1,6 +1,11 @@ import { ethers } from 'ethers'; import { BYTECODES } from '../constants/bytecodes'; -import { PoolDeploymentParams, poolDeploymentParamsSchema } from '../types/poolDeployment'; +import { + PoolDeploymentParams, + poolDeploymentParamsSchema, + ContractRemoteTokenPoolInfo, + RemoteTokenPoolInfo, +} from '../types/poolDeployment'; import { SafeMetadata, SafeTransactionDataBase, @@ -9,6 +14,7 @@ import { SafeOperationType, } from '../types/safe'; import { TokenPoolFactory__factory } from '../typechain'; +import { poolTypeToNumber } from '../utils/poolTypeConverter'; import logger from '../utils/logger'; export class PoolDeploymentError extends Error { @@ -22,24 +28,18 @@ export class PoolDeploymentError extends Error { * Generates a deployment transaction for pool only using TokenPoolFactory * @param inputJson - The input JSON string containing deployment parameters * @param factoryAddress - The address of the TokenPoolFactory contract - * @param tokenAddress - The address of the existing token * @param salt - The salt to use for create2 deployment (required) * @returns The Safe transaction data */ export async function generatePoolDeploymentTransaction( inputJson: string, factoryAddress: string, - tokenAddress: string, salt: string, ): Promise { if (!ethers.isAddress(factoryAddress)) { throw new PoolDeploymentError('Invalid factory address'); } - if (!ethers.isAddress(tokenAddress)) { - throw new PoolDeploymentError('Invalid token address'); - } - if (!salt) { throw new PoolDeploymentError('Salt is required for deployment'); } @@ -47,10 +47,12 @@ export async function generatePoolDeploymentTransaction( let parsedInput: PoolDeploymentParams; try { - parsedInput = await poolDeploymentParamsSchema.parseAsync(JSON.parse(inputJson)); + // Parse and validate the input JSON + const rawInput = JSON.parse(inputJson) as unknown; + parsedInput = await poolDeploymentParamsSchema.parseAsync(rawInput); logger.info('Successfully validated pool deployment input', { poolType: parsedInput.poolType, - token: parsedInput.poolParams.token, + token: parsedInput.token, }); } catch (error) { if (error instanceof Error) { @@ -61,22 +63,34 @@ export async function generatePoolDeploymentTransaction( } try { - // Get remote token pools from input (defaults to empty array if not provided) - const remoteTokenPools = parsedInput.remoteTokenPools; - // Get the factory interface const factoryInterface = TokenPoolFactory__factory.createInterface(); + // Convert pool type enum to contract value + const poolTypeValue = poolTypeToNumber(parsedInput.poolType); + + // Convert remote token pools' pool types to contract values + const remoteTokenPools: ContractRemoteTokenPoolInfo[] = parsedInput.remoteTokenPools.map( + (pool: RemoteTokenPoolInfo) => ({ + ...pool, + poolType: poolTypeToNumber(pool.poolType), + }), + ); + + // Get the appropriate pool bytecode + const poolBytecode = + parsedInput.poolType === 'BurnMintTokenPool' + ? BYTECODES.BURN_MINT_TOKEN_POOL + : BYTECODES.LOCK_RELEASE_TOKEN_POOL; + // Encode the function call to deployTokenPoolWithExistingToken const data = factoryInterface.encodeFunctionData('deployTokenPoolWithExistingToken', [ - tokenAddress, - parsedInput.poolParams.decimals, + parsedInput.token, + parsedInput.decimals, remoteTokenPools, - parsedInput.poolType === 'BurnMintTokenPool' - ? BYTECODES.BURN_MINT_TOKEN_POOL - : BYTECODES.LOCK_RELEASE_TOKEN_POOL, - ethers.id(salt), // Convert string salt to bytes32 - parsedInput.poolType === 'BurnMintTokenPool' ? 0 : 1, // PoolType enum + poolBytecode, + salt, + poolTypeValue, ]); logger.info('Successfully generated pool deployment transaction'); @@ -85,7 +99,7 @@ export async function generatePoolDeploymentTransaction( to: factoryAddress, value: '0', data, - operation: SafeOperationType.Call, // Regular call, not delegatecall + operation: SafeOperationType.Call, }; } catch (error) { if (error instanceof Error) { @@ -120,7 +134,7 @@ export function createPoolDeploymentJSON( createdAt: Date.now(), meta: { name: `Pool Factory Deployment - ${params.poolType}`, - description: `Deploy ${params.poolType} for token at ${params.poolParams.token} using factory`, + description: `Deploy ${params.poolType} for token at ${params.token} using factory`, txBuilderVersion: SAFE_TX_BUILDER_VERSION, createdFromSafeAddress: metadata.safeAddress, createdFromOwnerAddress: metadata.ownerAddress, @@ -140,7 +154,7 @@ export function createPoolDeploymentJSON( name: methodFragment.name, payable: methodFragment.payable, }, - contractInputsValues: null, // The data field already contains the encoded function call + contractInputsValues: null, }, ], }; diff --git a/src/types/combinedDeployment.ts b/src/types/combinedDeployment.ts deleted file mode 100644 index 74e3fce..0000000 --- a/src/types/combinedDeployment.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TokenDeploymentParams } from './tokenDeployment'; -import { PoolDeploymentParams } from './poolDeployment'; - -export interface CombinedDeploymentParams { - token: TokenDeploymentParams; - pool: Omit & { - poolParams: Omit; - }; -} diff --git a/src/types/poolDeployment.ts b/src/types/poolDeployment.ts index 3a0ffb4..3d1b832 100644 --- a/src/types/poolDeployment.ts +++ b/src/types/poolDeployment.ts @@ -1,38 +1,54 @@ import { z } from 'zod'; -import { remoteTokenPoolInfoSchema } from './tokenDeployment'; +import { ethers } from 'ethers'; -// Pool type discriminator +// Pool type discriminator (matches contract's PoolType enum) export const poolTypeSchema = z.enum(['BurnMintTokenPool', 'LockReleaseTokenPool']); -// Default pool type -export const DEFAULT_POOL_TYPE = 'BurnMintTokenPool'; - -// Common pool parameters -const basePoolParamsSchema = z.object({ - token: z.string(), - decimals: z.number(), - allowlist: z.array(z.string()), - owner: z.string(), - ccipRouter: z.string(), +// Schema for rate limiter configuration +export const rateLimiterConfigSchema = z.object({ + isEnabled: z.boolean(), + capacity: z.string(), // BigNumber as string + rate: z.string(), // BigNumber as string }); -// BurnMintTokenPool specific parameters -export const burnMintTokenPoolParamsSchema = basePoolParamsSchema; +// Schema for remote chain configuration +export const remoteChainConfigSchema = z.object({ + remotePoolFactory: z.string(), + remoteRouter: z.string(), + remoteRMNProxy: z.string(), + remoteTokenDecimals: z.number(), +}); -// LockReleaseTokenPool specific parameters -export const lockReleaseTokenPoolParamsSchema = basePoolParamsSchema.extend({ - armProxy: z.string(), - acceptLiquidity: z.boolean().optional().default(true), +// Schema for remote token pool information (user input) +export const remoteTokenPoolInfoSchema = z.object({ + remoteChainSelector: z.string(), // BigNumber as string + remotePoolAddress: z.string(), + remotePoolInitCode: z.string(), + remoteChainConfig: remoteChainConfigSchema, + poolType: poolTypeSchema, + remoteTokenAddress: z.string(), + remoteTokenInitCode: z.string(), + rateLimiterConfig: rateLimiterConfigSchema, }); -// Combined pool deployment parameters +// Schema for pool deployment parameters (matches contract function parameters) export const poolDeploymentParamsSchema = z.object({ - poolType: poolTypeSchema, - poolParams: z.union([burnMintTokenPoolParamsSchema, lockReleaseTokenPoolParamsSchema]), + token: z.string().refine( + (address) => ethers.isAddress(address), + (val) => ({ message: `Invalid token address: ${val}` }), + ), + decimals: z.number(), remoteTokenPools: z.array(remoteTokenPoolInfoSchema).optional().default([]), + poolType: poolTypeSchema, }); export type PoolType = z.infer; -export type BurnMintTokenPoolParams = z.infer; -export type LockReleaseTokenPoolParams = z.infer; +export type RateLimiterConfig = z.infer; +export type RemoteChainConfig = z.infer; +export type RemoteTokenPoolInfo = z.infer; export type PoolDeploymentParams = z.infer; + +// Contract-specific types (with numeric pool type) +export type ContractRemoteTokenPoolInfo = Omit & { + poolType: number; +}; diff --git a/src/types/tokenDeployment.ts b/src/types/tokenDeployment.ts index df66cda..53e92d9 100644 --- a/src/types/tokenDeployment.ts +++ b/src/types/tokenDeployment.ts @@ -1,31 +1,5 @@ import { z } from 'zod'; - -// Schema for rate limiter configuration -export const rateLimiterConfigSchema = z.object({ - isEnabled: z.boolean(), - capacity: z.string(), // BigNumber as string - rate: z.string(), // BigNumber as string -}); - -// Schema for remote chain configuration -export const remoteChainConfigSchema = z.object({ - remotePoolFactory: z.string(), - remoteRouter: z.string(), - remoteRMNProxy: z.string(), - remoteTokenDecimals: z.number(), -}); - -// Schema for remote token pool information -export const remoteTokenPoolInfoSchema = z.object({ - remoteChainSelector: z.string(), // BigNumber as string - remotePoolAddress: z.string(), - remotePoolInitCode: z.string(), - remoteChainConfig: remoteChainConfigSchema, - poolType: z.number(), - remoteTokenAddress: z.string(), - remoteTokenInitCode: z.string(), - rateLimiterConfig: rateLimiterConfigSchema, -}); +import { remoteTokenPoolInfoSchema } from './poolDeployment'; // Schema for BurnMintERC20 constructor parameters export const tokenDeploymentParamsSchema = z.object({ @@ -37,7 +11,4 @@ export const tokenDeploymentParamsSchema = z.object({ remoteTokenPools: z.array(remoteTokenPoolInfoSchema).optional().default([]), }); -export type RateLimiterConfig = z.infer; -export type RemoteChainConfig = z.infer; -export type RemoteTokenPoolInfo = z.infer; export type TokenDeploymentParams = z.infer; diff --git a/src/utils/poolTypeConverter.ts b/src/utils/poolTypeConverter.ts new file mode 100644 index 0000000..30ef8c0 --- /dev/null +++ b/src/utils/poolTypeConverter.ts @@ -0,0 +1,22 @@ +import { PoolType } from '../types/poolDeployment'; + +/** + * Converts a PoolType enum value to its corresponding contract numeric value + * @param poolType - The pool type enum value + * @returns The numeric value used in the contract (0 for BurnMintTokenPool, 1 for LockReleaseTokenPool) + */ +export function poolTypeToNumber(poolType: PoolType): number { + return poolType === 'BurnMintTokenPool' ? 0 : 1; +} + +/** + * Converts a contract numeric value to its corresponding PoolType enum value + * @param value - The numeric value from the contract (0 or 1) + * @returns The corresponding PoolType enum value + * @throws Error if the value is not 0 or 1 + */ +export function numberToPoolType(value: number): PoolType { + if (value === 0) return 'BurnMintTokenPool'; + if (value === 1) return 'LockReleaseTokenPool'; + throw new Error(`Invalid pool type value: ${value}. Expected 0 or 1.`); +}