// Solidity 0.8.9
/**
 * @notice Deploys a new pool and LP token.
 * @dev Decimals is an argument as not all ERC20 tokens implement the ERC20Detailed interface.
 *      An implementation where `getUnderlying()` returns the zero address is for ETH pools.
 * @param poolName Name of the pool.
 * @param underlying Address of the pool's underlying.
 * @param lpTokenArgs Arguments to create the LP token for the pool
 * @param vaultArgs Arguments to create the vault
 * @param implementationNames Name of the implementations to use
 * @return addrs Address of the deployed pool, address of the pool's deployed LP token.
 */
function deployPool(
        string calldata poolName,
        uint256 depositCap,
        address underlying,
        LpTokenArgs calldata lpTokenArgs,
        VaultArgs calldata vaultArgs,
        ImplementationNames calldata implementationNames
    ) external onlyGovernance returns (Addresses memory addrs) {
        DeployPoolVars memory vars;

        vars.poolImplementation = implementations[_POOL_KEY][implementationNames.pool];
        require(vars.poolImplementation != address(0), Error.INVALID_POOL_IMPLEMENTATION);

        vars.lpTokenImplementation = implementations[_LP_TOKEN_KEY][implementationNames.lpToken];
        require(vars.lpTokenImplementation != address(0), Error.INVALID_LP_TOKEN_IMPLEMENTATION);

        vars.vaultImplementation = implementations[_VAULT_KEY][implementationNames.vault];
        require(vars.vaultImplementation != address(0), Error.INVALID_VAULT_IMPLEMENTATION);

        vars.stakerVaultImplementation = implementations[_STAKER_VAULT_KEY][
            implementationNames.stakerVault
        ];
        require(
            vars.stakerVaultImplementation != address(0),
            Error.INVALID_STAKER_VAULT_IMPLEMENTATION
        );

        addrs.pool = Clones.clone(vars.poolImplementation);
        addrs.vault = Clones.clone(vars.vaultImplementation);

        if (underlying == address(0)) {
            // ETH pool
            require(
                ILiquidityPool(vars.poolImplementation).getUnderlying() == address(0),
                Error.INVALID_POOL_IMPLEMENTATION
            );
            require(lpTokenArgs.decimals == 18, Error.INVALID_DECIMALS);
            IEthPool(addrs.pool).initialize(poolName, depositCap, addrs.vault);
        } else {
            IErc20Pool(addrs.pool).initialize(poolName, underlying, depositCap, addrs.vault);
        }

        addrs.lpToken = Clones.clone(vars.lpTokenImplementation);

        ILpToken(addrs.lpToken).initialize(
            lpTokenArgs.name,
            lpTokenArgs.symbol,
            lpTokenArgs.decimals,
            addrs.pool
        );

        addrs.stakerVault = Clones.clone(vars.stakerVaultImplementation);
        IStakerVault(addrs.stakerVault).initialize(addrs.lpToken);
        controller.addStakerVault(addrs.stakerVault);

        ILiquidityPool(addrs.pool).setLpToken(addrs.lpToken);
        ILiquidityPool(addrs.pool).setStaker();

        IVault(addrs.vault).initialize(
            addrs.pool,
            vaultArgs.debtLimit,
            vaultArgs.targetAllocation,
            vaultArgs.bound
        );

        addressProvider.addPool(addrs.pool);

        emit NewPool(addrs.pool, addrs.vault, addrs.lpToken, addrs.stakerVault);
        return addrs;
    }