// File: src/PopcornLiquidityGaugeFactory.sol



// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import {IVaultRegistry, VaultMetadata} from "popcorn/src/interfaces/vault/IVaultRegistry.sol";

import {Bytes32AddressLib} from "solmate/utils/Bytes32AddressLib.sol";
import {Owned} from "solmate/auth/Owned.sol";

import {BaseGaugeFactory} from "./BaseGaugeFactory.sol";
import {ILiquidityGauge} from "./interfaces/ILiquidityGauge.sol";

contract PopcornLiquidityGaugeFactory is BaseGaugeFactory, Owned {
    using Bytes32AddressLib for address;

    error PopcornLiquidityGaugeFactory__InvalidVault();

    address public immutable gaugeAdmin;
    IVaultRegistry public immutable popcornVaultRegistry;

    constructor(
        ILiquidityGauge gaugeTemplate,
        address gaugeAdmin_,
        IVaultRegistry popcornVaultRegistry_
    ) BaseGaugeFactory(gaugeTemplate) Owned(gaugeAdmin_) {
        popcornVaultRegistry = popcornVaultRegistry_;
        gaugeAdmin = gaugeAdmin_;
    }

    /**
     * @notice Deploys a new gauge.
     * @param relativeWeightCap The relative weight cap for the created gauge
     * @return The address of the deployed gauge
     */
    function create(address vaultAddr, uint256 relativeWeightCap) onlyOwner external returns (address) {
        VaultMetadata memory vault = popcornVaultRegistry.getVault(vaultAddr);
        if (vault.vault != vaultAddr) revert PopcornLiquidityGaugeFactory__InvalidVault();

        // this will fail if there's a gauge for the given vault.
        address gauge = _create(vaultAddr.fillLast12Bytes());
        ILiquidityGauge(gauge).initialize(
            vaultAddr, relativeWeightCap, gaugeAdmin
        );
        return gauge;
    }
}

// File: lib/popcorn/src/interfaces/vault/IVaultRegistry.sol



// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.15
pragma solidity ^0.8.15;

import { IOwned } from "../IOwned.sol";

struct VaultMetadata {
  /// @notice Vault address
  address vault;
  /// @notice Staking contract for the vault
  address staking;
  /// @notice Owner and Vault creator
  address creator;
  /// @notice IPFS CID of vault metadata
  string metadataCID;
  /// @notice OPTIONAL - If the asset is an Lp Token these are its underlying assets
  address[8] swapTokenAddresses;
  /// @notice OPTIONAL - If the asset is an Lp Token its the pool address
  address swapAddress;
  /// @notice OPTIONAL - If the asset is an Lp Token this is the identifier of the exchange (1 = curve)
  uint256 exchange;
}

interface IVaultRegistry is IOwned {
  function getVault(address vault) external view returns (VaultMetadata memory);

  function getSubmitter(address vault) external view returns (address);

  function registerVault(VaultMetadata memory metadata) external;
}

// File: lib/solmate/src/utils/Bytes32AddressLib.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Library for converting between addresses and bytes32 values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Bytes32AddressLib.sol)
library Bytes32AddressLib {
    function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) {
        return address(uint160(uint256(bytesValue)));
    }

    function fillLast12Bytes(address addressValue) internal pure returns (bytes32) {
        return bytes32(bytes20(addressValue));
    }
}

// File: lib/solmate/src/auth/Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnershipTransferred(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}

// File: src/BaseGaugeFactory.sol



// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

import {ILiquidityGauge} from "./interfaces/ILiquidityGauge.sol";

abstract contract BaseGaugeFactory {
    ILiquidityGauge private immutable _gaugeImplementation;

    mapping(address => bool) private _isGaugeFromFactory;

    event GaugeCreated(address indexed gauge);

    constructor(ILiquidityGauge gaugeImplementation) {
        _gaugeImplementation = gaugeImplementation;
    }

    /**
     * @notice Returns the address of the implementation used for gauge deployments.
     */
    function getGaugeImplementation() public view returns (ILiquidityGauge) {
        return _gaugeImplementation;
    }

    /**
     * @notice Returns true if `gauge` was created by this factory.
     */
    function isGaugeFromFactory(address gauge) external view returns (bool) {
        return _isGaugeFromFactory[gauge];
    }

    /**
     * @dev Deploys a new gauge as a proxy of the implementation in storage.
     * The deployed gauge must be initialized by the caller method.
     * @return The address of the deployed gauge
     */
    function _create(bytes32 salt) internal returns (address) {
        address gauge = Clones.cloneDeterministic(address(_gaugeImplementation), salt);

        _isGaugeFromFactory[gauge] = true;
        emit GaugeCreated(gauge);

        return gauge;
    }
}

// File: src/interfaces/ILiquidityGauge.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.7.0 <0.9.0;

// For compatibility, we're keeping the same function names as in the original Curve code, including the mixed-case
// naming convention.
// solhint-disable func-name-mixedcase
// solhint-disable func-param-name-mixedcase

interface ILiquidityGauge {
    // solhint-disable-next-line var-name-mixedcase
    event RelativeWeightCapChanged(uint256 new_relative_weight_cap);

    /**
     * @notice Returns liquidity emissions calculated during checkpoints for the given user.
     * @param user User address.
     * @return uint256 token amount to issue for the address.
     */
    function integrate_fraction(address user) external view returns (uint256);

    /**
     * @notice Record a checkpoint for a given user.
     * @param user User address.
     * @return bool Always true.
     */
    function user_checkpoint(address user) external returns (bool);

    /**
     * @notice Returns true if gauge is killed; false otherwise.
     */
    function is_killed() external view returns (bool);

    /**
     * @notice Kills the gauge so it cannot mint tokens.
     */
    function killGauge() external;

    /**
     * @notice Unkills the gauge so it can mint tokens again.
     */
    function unkillGauge() external;

    /**
     * @notice Uses the Uniswap Poor oracle to decide whether a gauge is alive
     */
    function makeGaugePermissionless() external;

    /**
     * @notice Sets a new relative weight cap for the gauge.
     * The value shall be normalized to 1e18, and not greater than MAX_RELATIVE_WEIGHT_CAP.
     * @param relativeWeightCap New relative weight cap.
     */
    function setRelativeWeightCap(uint256 relativeWeightCap) external;

    /**
     * @notice Gets the relative weight cap for the gauge.
     */
    function getRelativeWeightCap() external view returns (uint256);

    /**
     * @notice Returns the gauge's relative weight for a given time, capped to its relative weight cap attribute.
     * @param time Timestamp in the past or present.
     */
    function getCappedRelativeWeight(uint256 time) external view returns (uint256);

    function initialize(
        address lpToken,
        uint256 relativeWeightCap,
        address admin
    ) external;

    function change_pending_admin(address newPendingAdmin) external;

    function claim_admin() external;

    function admin() external view returns (address);

    function deposit(uint256 amount) external;

    function deposit(uint256 amount, address recipient) external;

    function withdraw(uint256 amount) external;

    function balanceOf(address account) external view returns (uint256);

    function claim_rewards() external;

    function set_tokenless_production(uint8) external;
}

// File: lib/popcorn/src/interfaces/IOwned.sol



// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.15

pragma solidity ^0.8.15;

interface IOwned {
  function owner() external view returns (address);

  function nominatedOwner() external view returns (address);

  function nominateNewOwner(address owner) external;

  function acceptOwnership() external;
}

// File: lib/openzeppelin-contracts/contracts/proxy/Clones.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}