// File: src/token/SnapshotLPStrategy.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@interfaces/IFraxFarm.sol";

/** 
@notice To enable this strategy:
- Deploy this contract with DeployStakedLPVotes.s.sol script
- Obtain the address and insert it into the script below at `<Snapshot_LP_Strategy_Contract_Address>` 
- Copy the script (including the first & last curly brackets).
- At snapshot.org, go to the space you want to enable this strategy for, and click on "Settings" -> "Strategie(s)" -> "Add Strategy"
- Find the strategy named `contract-call` and overwrite what is in there with this (with the address updated)
- Click `Add`
- Now when a new Snapshot is created, it will include this strategy & give users the ability to vote even though they're staked in LPs

The script for Snapshot strategy using "contract-call" strategy:

{
  "address": "<Snapshot_LP_Strategy_Contract_Address",
  "symbol": "FraxfarmStkPitchFxsFraxFraxswapLP",
  "decimals": 18,
  "methodABI": {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "getVotes",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
}

*/

/// @title SnapshotLPStrategy
/// @notice Get's a user's number of pitchFXS from a staked position of LP on a FraxFarm.
/// @author Pitch Foundation

contract SnapshotLPStrategy {
    /// @notice The pitch deployer address.
    address public constant OWNER = address(0x092308e1DCDeCA61bF1153Dd92E15eD08E164982);
    /// @notice The pitchFXS-FRAX LP token address.
    address public pitchFxsFraxswapPair;
    /// @notice The FraxFarm staking contract address.
    address public pitchFxsFraxFarm;

    constructor() {
        pitchFxsFraxswapPair = address(0x0a92aC70B5A187fB509947916a8F63DD31600F80);
        pitchFxsFraxFarm = address(0x24C66Ba25ca2A53bB97B452B9F45DD075b07Cf55);
    }

    /// @notice Updates the addresses of the pitchFXS-FRAX LP token and the FraxFarm staking contract.
    /// @dev Callable only by PitchDeployer (owner).
    /// @param _pitchFxsFraxswapPair The address of the pitchFXS-FRAX LP token.
    /// @param _pitchFxsFraxFarm The address of the FraxFarm staking contract.
    function setAddresses(address _pitchFxsFraxswapPair, address _pitchFxsFraxFarm) external {
        if (msg.sender != OWNER) revert("!OWNER");

        pitchFxsFraxswapPair = _pitchFxsFraxswapPair;
        pitchFxsFraxFarm = _pitchFxsFraxFarm;
    }

    /// @notice Returns `account` balance of pitchFXS in the LP pair in WEI.
    /// @param account The address of the account to look up balance of.
    /// @return votes The balance of pitchFXS in the LP pair in WEI.
    function getVotes(address account) external view returns (uint256 votes) {
        // Get the user's total locked stakes liquidity (number of LP tokens)
        uint256 liquidity = IFraxFarm(pitchFxsFraxFarm).lockedLiquidityOf(account);

        // Get the current number of FRAX per LP token
        uint256 fraxPerLp = IFraxFarm(pitchFxsFraxFarm).fraxPerLPToken();

        // Get the current reserves from fraxswap pair
        (uint112 token0Reserve, uint112 token2Reserve, ) = IFraxswap(pitchFxsFraxswapPair).getReserves();

        // convert to uint256 for calculations
        uint256 token0Amt = uint256(token0Reserve);
        uint256 token1Amt = uint256(token2Reserve);

        // votes in wei
        votes = ((liquidity * fraxPerLp) * ((1e18 * token0Amt) / token1Amt)) / 1e36; // or if we want it in ether: ((liquidity * fraxPerLp / 1e18);
    }
}

// File: src/interfaces/IFraxFarm.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IFraxswap {
    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );

    function token0() external view returns (address);

    function token1() external view returns (address);
}

/// @notice Minimalistic IFraxFarmUniV3
interface IFraxFarmUniV3TokenPositions {
    function uni_token0() external view returns (address);

    function uni_token1() external view returns (address);
}

interface IFraxswapERC20 {
    function decimals() external view returns (uint8);
}

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

    function stakingToken() external view returns (address);

    function fraxPerLPToken() external view returns (uint256);

    function calcCurCombinedWeight(address account)
        external
        view
        returns (
            uint256 old_combined_weight,
            uint256 new_vefxs_multiplier,
            uint256 new_combined_weight
        );

    function periodFinish() external view returns (uint256);

    function getAllRewardTokens() external view returns (address[] memory);

    function earned(address account) external view returns (uint256[] memory new_earned);

    function totalLiquidityLocked() external view returns (uint256);

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

    function totalCombinedWeight() external view returns (uint256);

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

    function lockMultiplier(uint256 secs) external view returns (uint256);

    function rewardRates(uint256 token_idx) external view returns (uint256 rwd_rate);

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

    function proxyStakedFrax(address proxy_address) external view returns (uint256);

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

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

    function minVeFXSForMaxBoostProxy(address proxy_address) external view returns (uint256);

    function veFXSMultiplier(address account) external view returns (uint256 vefxs_multiplier);

    function toggleValidVeFXSProxy(address proxy_address) external;

    function proxyToggleStaker(address staker_address) external;

    function stakerSetVeFXSProxy(address proxy_address) external;

    function getReward(address destination_address) external returns (uint256[] memory);

    function getReward(address destination_address, bool also_claim_extra) external returns (uint256[] memory);

    function vefxs_max_multiplier() external view returns (uint256);

    function vefxs_boost_scale_factor() external view returns (uint256);

    function vefxs_per_frax_for_max_boost() external view returns (uint256);

    function getProxyFor(address addr) external view returns (address);

    function sync() external;

    function nominateNewOwner(address _owner) external;

    function acceptOwnership() external;

    function updateRewardAndBalance(address acct, bool sync) external;

    function setRewardVars(
        address reward_token_address,
        uint256 _new_rate,
        address _gauge_controller_address,
        address _rewards_distributor_address
    ) external;

    function calcCurrLockMultiplier(address account, uint256 stake_idx)
        external
        view
        returns (uint256 midpoint_lock_multiplier);

    function staker_designated_proxies(address staker_address) external view returns (address);
}

interface IFraxFarmERC20 is IFraxFarm {
    struct LockedStake {
        bytes32 kek_id;
        uint256 start_timestamp;
        uint256 liquidity;
        uint256 ending_timestamp;
        uint256 lock_multiplier; // 6 decimals of precision. 1x = 1000000
    }

    /// TODO this references the public getter for `lockedStakes` in the contract
    function lockedStakes(address account, uint256 stake_idx) external view returns (LockedStake memory);

    function lockedStakesOf(address account) external view returns (LockedStake[] memory);

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

    function lockAdditional(bytes32 kek_id, uint256 addl_liq) external;

    function lockLonger(bytes32 kek_id, uint256 _newUnlockTimestamp) external;

    function stakeLocked(uint256 liquidity, uint256 secs) external returns (bytes32);

    function withdrawLocked(bytes32 kek_id, address destination_address) external returns (uint256);
}

interface IFraxFarmUniV3 is IFraxFarm, IFraxFarmUniV3TokenPositions {
    struct LockedNFT {
        uint256 token_id; // for Uniswap V3 LPs
        uint256 liquidity;
        uint256 start_timestamp;
        uint256 ending_timestamp;
        uint256 lock_multiplier; // 6 decimals of precision. 1x = 1000000
        int24 tick_lower;
        int24 tick_upper;
    }

    function uni_tick_lower() external view returns (int24);

    function uni_tick_upper() external view returns (int24);

    function uni_required_fee() external view returns (uint24);

    function lockedNFTsOf(address account) external view returns (LockedNFT[] memory);

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

    function lockAdditional(
        uint256 token_id,
        uint256 token0_amt,
        uint256 token1_amt,
        uint256 token0_min_in,
        uint256 token1_min_in,
        bool use_balof_override
    ) external;

    function stakeLocked(uint256 token_id, uint256 secs) external;

    function withdrawLocked(uint256 token_id, address destination_address) external;
}