Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -233,9 +231,7 @@ async function handlePoolDeployment(options: PoolDeploymentOptions): Promise<voi
if (!ethers.isAddress(options.deployer)) {
throw new Error(`Invalid deployer address: ${String(options.deployer)}`);
}
if (!ethers.isAddress(options.tokenAddress)) {
throw new Error(`Invalid token address: ${String(options.tokenAddress)}`);
}

if (!options.salt) {
throw new Error('Salt is required');
}
Expand All @@ -248,7 +244,6 @@ async function handlePoolDeployment(options: PoolDeploymentOptions): Promise<voi
const transaction = await generatePoolDeploymentTransaction(
inputJson,
options.deployer,
options.tokenAddress,
options.salt,
);

Expand Down Expand Up @@ -346,7 +341,6 @@ program
.description('Generate deployment transaction for TokenPool')
.requiredOption('-i, --input <path>', 'Path to input JSON file')
.requiredOption('-d, --deployer <address>', 'TokenPoolFactory contract address')
.requiredOption('-t, --token-address <address>', 'Token address')
.requiredOption('--salt <bytes32>', 'Salt for create2')
.option('-o, --output <path>', 'Path to output file (defaults to stdout)')
.addOption(
Expand Down
58 changes: 36 additions & 22 deletions src/generators/poolDeployment.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand All @@ -22,35 +28,31 @@ 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<SafeTransactionDataBase> {
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');
}

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) {
Expand All @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
},
],
};
Expand Down
9 changes: 0 additions & 9 deletions src/types/combinedDeployment.ts

This file was deleted.

62 changes: 39 additions & 23 deletions src/types/poolDeployment.ts
Original file line number Diff line number Diff line change
@@ -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<typeof poolTypeSchema>;
export type BurnMintTokenPoolParams = z.infer<typeof burnMintTokenPoolParamsSchema>;
export type LockReleaseTokenPoolParams = z.infer<typeof lockReleaseTokenPoolParamsSchema>;
export type RateLimiterConfig = z.infer<typeof rateLimiterConfigSchema>;
export type RemoteChainConfig = z.infer<typeof remoteChainConfigSchema>;
export type RemoteTokenPoolInfo = z.infer<typeof remoteTokenPoolInfoSchema>;
export type PoolDeploymentParams = z.infer<typeof poolDeploymentParamsSchema>;

// Contract-specific types (with numeric pool type)
export type ContractRemoteTokenPoolInfo = Omit<RemoteTokenPoolInfo, 'poolType'> & {
poolType: number;
};
31 changes: 1 addition & 30 deletions src/types/tokenDeployment.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -37,7 +11,4 @@ export const tokenDeploymentParamsSchema = z.object({
remoteTokenPools: z.array(remoteTokenPoolInfoSchema).optional().default([]),
});

export type RateLimiterConfig = z.infer<typeof rateLimiterConfigSchema>;
export type RemoteChainConfig = z.infer<typeof remoteChainConfigSchema>;
export type RemoteTokenPoolInfo = z.infer<typeof remoteTokenPoolInfoSchema>;
export type TokenDeploymentParams = z.infer<typeof tokenDeploymentParamsSchema>;
22 changes: 22 additions & 0 deletions src/utils/poolTypeConverter.ts
Original file line number Diff line number Diff line change
@@ -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.`);
}
Loading