Contract 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 10

 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0xda6ccb8665617caf153d385de9bc82d9b425cf16b61ed9fc928bd7abe6e26880Update Ssov Epoc...1650292892023-12-29 18:12:5561 days 1 hr ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00005295 0.1
0x0ddcc53794164a8179c6970719d3caab0bae5045100d437ac39afa8543a90246Kill Lp Position1536028082023-11-24 12:30:4496 days 6 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006374 0.1
0xad4a2bc10571ce647b6b0b1dea4d3895f4343cd8631f7af15bfa35afcf7a80f5Fill Lp Position1532519932023-11-23 11:20:1497 days 7 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.0001441 0.1
0x46a63a29043b3881d111618c2346663c07d6612804364ee430da5a87afc77ebbFill Lp Position1523531162023-11-20 17:54:55100 days 1 hr ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00019272 0.1
0x4074b5fd2b3c6004e2e9a2c7d41a92a33d3d587f85f32e2b2de0b83515e36752Add To Lp1512591762023-11-17 8:23:56103 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00013343 0.1
0x8f205def85026a65c3fa74e1c4e2431762570559a486dbbf61cf30b6bc223253Update Ssov Epoc...1512589852023-11-17 8:23:08103 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006021 0.1
0x57083384ad0b1decf72ed43d0a70d57a7182841de59f120ab06117cf0ab8d766Update Ssov Epoc...1512585702023-11-17 8:21:23103 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006021 0.1
0xc51a7760a8c79a738748456c8bcd6ead91ee5ff96e5774c091350b4fe441472aKill Lp Position1512563522023-11-17 8:12:09103 days 11 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006357 0.1
0x81720cabe151d9217c870a357aa73c4faeb2179f3a2e1de9130f3dfb9bf30c91Add To Lp1489924902023-11-10 9:54:34110 days 9 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00014587 0.1
0x7a721a038f6f41ed79d4c964655d96450475cf74c7e30c494a4956f6080123a2Update Ssov Epoc...1489922262023-11-10 9:53:27110 days 9 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006779 0.1
0x8f1913ab60ab3130b82c8a90702debd5d1f023ac50f99053a0ea5ac726b08ca8Update Ssov Epoc...1489921892023-11-10 9:53:17110 days 9 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00006779 0.1
0x3dfdd0fcf12182bff391219366464e4d91b423475667196bc133f7ca12ba33b3Kill Lp Position1489880582023-11-10 9:35:59110 days 9 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00007483 0.1
0xb22b5cb9a6bde1e3c0a0241eb5ffce1df9cf13083ba11ae6057841691ce3c325Fill Lp Position1488760962023-11-10 1:39:19110 days 17 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00017615 0.1
0xcb8f1587132b33b08d9dd7ae7676d3f22a69568d97df5c7f9317081a00e421caFill Lp Position1485672722023-11-09 3:50:24111 days 15 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00013067 0.1
0xd0fa616b4f461dbabbbf6673370a8374e1f702a0c347008909627f68924198d4Fill Lp Position1480794192023-11-07 16:34:20113 days 2 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00014757 0.1
0xf17e94746dd0ce2a40abc0494e56d872348551c6bf69d442c7f9bfee7b768f1aFill Lp Position1476900922023-11-06 12:09:20114 days 7 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00010877 0.1
0xccadd6394bf8b728226437b8669fdd9ba40325ccdbae0e7336d42fcba75a53d5Fill Lp Position1473883452023-11-05 13:18:49115 days 5 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00012873 0.1
0x5da32bfcec9c6148684e04f09244ea7c0a6c3c54ee5c07099f648083abefdcb9Fill Lp Position1469966012023-11-04 9:11:08116 days 10 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00014225 0.1
0x020cdc5648b832ad6893437e7faeea3c2b2d5c9cec397989aedee34db2e2c293Add To Lp1466522582023-11-03 8:23:54117 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00025905 0.1
0x78db219e3c25b18456046e11568a0058268c1067483467f276351cd4c6cced6cUpdate Ssov Epoc...1466517262023-11-03 8:21:36117 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00012923 0.1
0x08070bd4a378ab4dcffd1e5690f2539fd14797913dd4cbdec00afb4b3a6f0981Update Ssov Epoc...1466516742023-11-03 8:21:23117 days 10 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00012923 0.1
0xcadb49096dc1236c6508eb68c81ad774c945b61c9c6095c1fa34ef1f3f512c50Kill Lp Position1466504742023-11-03 8:16:11117 days 11 hrs ago0xde485812e28824e542b9c2270b6b8ed9232b7d0b IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.0001459 0.1
0x986e5e963b3296ef31281b91c0f27c72c5ad9a337f059f30cfb6d0d4a3090d59Fill Lp Position1463813322023-11-02 12:34:37118 days 6 hrs ago0x813f98977a00ca98b453e3d963294bef326c40dd IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00010671 0.1
0x67542747cb29b4d86bdc4a32599f4bdf3d502955f90a5481b7221570da878bfeFill Lp Position1461148642023-11-01 17:01:25119 days 2 hrs ago0xfb7405075f2dd0a2aaca6ca2d87d56e09577d6ef IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.00016917 0.1
0x4c2cead7cd933e438b56be1a8b57913f6812341076a682e376b537ba6fbce00cFill Lp Position1451388002023-10-29 14:22:03122 days 4 hrs ago0xfb7405075f2dd0a2aaca6ca2d87d56e09577d6ef IN  0xaf5e5ef96cda8f1dc81fcff516320fa675571a740 ETH0.000120790.1
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0xd7f172bc514c8a4e91374380efab5ec59ab6157cec039bd24c3f670a425eed77707325742023-03-17 11:39:44348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0xd7f172bc514c8a4e91374380efab5ec59ab6157cec039bd24c3f670a425eed77707325742023-03-17 11:39:44348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x4af208787f2e6d3bb2f6bfb1fe9a89e97e705a09324a4960fd19ff763928bda0707313672023-03-17 11:34:48348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0x4af208787f2e6d3bb2f6bfb1fe9a89e97e705a09324a4960fd19ff763928bda0707313672023-03-17 11:34:48348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x288732d15453719747f8f4739fdaa8d482ff21597b32a367681e363b313668a2707311892023-03-17 11:34:03348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0x288732d15453719747f8f4739fdaa8d482ff21597b32a367681e363b313668a2707311892023-03-17 11:34:03348 days 7 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x137d06585e706e91592472615c11e790488b032b9d2441c64c427b3cd1924347686267482023-03-10 19:48:04354 days 23 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0x137d06585e706e91592472615c11e790488b032b9d2441c64c427b3cd1924347686267482023-03-10 19:48:04354 days 23 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x6992bdad9c7d3c4483ba4c9efcd0cf9ef9c6e2edfb7b97b67a242c1fb873e168684764702023-03-10 8:41:19355 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0x6992bdad9c7d3c4483ba4c9efcd0cf9ef9c6e2edfb7b97b67a242c1fb873e168684764702023-03-10 8:41:19355 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x0309ce040d4fd77d0a32a0e00cb1c35497d55d632286908483984aa82e62dedb663190202023-03-03 8:30:09362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0x0309ce040d4fd77d0a32a0e00cb1c35497d55d632286908483984aa82e62dedb663190202023-03-03 8:30:09362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0xf873ca309cf5ae9c9aaf7dc7fd26a3a621db91ff044e410f021e6ec8c39f2492663169962023-03-03 8:21:51362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0xf873ca309cf5ae9c9aaf7dc7fd26a3a621db91ff044e410f021e6ec8c39f2492663169962023-03-03 8:21:51362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0xf6aac3449476b88176570fdc955e9a58cde9d68b4ab5c8c5aab2d0603253febb663169852023-03-03 8:21:48362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0xf6aac3449476b88176570fdc955e9a58cde9d68b4ab5c8c5aab2d0603253febb663169852023-03-03 8:21:48362 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0xd26e466fdb2efc2a5693901d5ae564dc4cfe5cf9a07df7c777ba988036d97d4d640567282023-02-24 9:01:19369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0xd26e466fdb2efc2a5693901d5ae564dc4cfe5cf9a07df7c777ba988036d97d4d640567282023-02-24 9:01:19369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0xae3a4ddc873181fd0336960b231c23ef9d52967b7add7ad57d44dce0b6e4dae1640563962023-02-24 9:00:01369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0xae3a4ddc873181fd0336960b231c23ef9d52967b7add7ad57d44dce0b6e4dae1640563962023-02-24 9:00:01369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0xc3798b9843f64e93b1e75e1bf88040a6b076a0d5d261593508c4e14aede03b8e640544422023-02-24 8:51:53369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
0xc3798b9843f64e93b1e75e1bf88040a6b076a0d5d261593508c4e14aede03b8e640544422023-02-24 8:51:53369 days 10 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x1b27ce7bff604ac06b951027f0704b4af379759187ff441d164e88272aa75abd617335232023-02-17 9:32:20376 days 9 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0x10fd85ec522c245a63239b9fc64434f58520bd1f0 ETH
0x1b27ce7bff604ac06b951027f0704b4af379759187ff441d164e88272aa75abd617335232023-02-17 9:32:20376 days 9 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a740x2c93b8b2085de80891da4cc9bd59956ce842a2800 ETH
0x7144b70e8f2327587a9da7e0fcdc9c809dd5c997b54eb415c48ddda405e00aff617322992023-02-17 9:27:05376 days 9 hrs ago 0xaf5e5ef96cda8f1dc81fcff516320fa675571a74 0xf71b2b6fe3c1d94863e751d6b455f750e714163c0 ETH
[ Download CSV Export 
Loading

Minimal Proxy Contract for 0x2c93b8b2085de80891da4cc9bd59956ce842a280

Contract Name:
SsovLp

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion, None license
Decompile ByteCode

Contract Source Code (Solidity)

Similar Contracts
/**
 *Submitted for verification at Arbiscan.io on 2023-02-01
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.17;

/**
                    ███████╗███████╗ ██████╗ ██╗   ██╗
                    ██╔════╝██╔════╝██╔═══██╗██║   ██║
                    ███████╗███████╗██║   ██║██║   ██║
                    ╚════██║╚════██║██║   ██║╚██╗ ██╔╝
                    ███████║███████║╚██████╔╝ ╚████╔╝ 
                    ╚══════╝╚══════╝ ╚═════╝   ╚═══╝  
                                                      
                    ██████╗ ██╗     ██████╗          
                    ██╔═══██╗██║     ██╔══██╗         
                    ██║   ██║██║     ██████╔╝         
                    ██║   ██║██║     ██╔═══╝          
                    ╚██████╔╝███████╗██║              
                    ╚═════╝ ╚══════╝╚═╝  

                      SSOV Option Liquidity Pools
                
      Allows Lps to add liquidity for select option tokens along with a discount
      to market price. Option token holders can sell their tokens to Lps at
      anytime during the option token's epoch.
*/

// Interfaces

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 * NOTE: Modified to include symbols and decimals.
 */
interface IERC20 {
    function totalSupply() external view returns (uint256);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

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

    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// Libraries

// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(token.transfer.selector, to, value)
        );
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
        );
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(token.approve.selector, spender, value)
        );
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(
            value
        );
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(
                token.approve.selector,
                spender,
                newAllowance
            )
        );
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(
            value,
            "SafeERC20: decreased allowance below zero"
        );
        _callOptionalReturn(
            token,
            abi.encodeWithSelector(
                token.approve.selector,
                spender,
                newAllowance
            )
        );
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(
            data,
            "SafeERC20: low-level call failed"
        );
        if (returndata.length > 0) {
            // Return data is optional
            // solhint-disable-next-line max-line-length
            require(
                abi.decode(returndata, (bool)),
                "SafeERC20: ERC20 operation did not succeed"
            );
        }
    }
}

// Contracts

// Interfaces

interface IAssetSwapper {
    function swapAsset(
        address from,
        address to,
        uint256 amount,
        uint256 minAmountOut,
        uint256 swapperId
    ) external returns (uint256);
}

interface IOptionPricing {
    function getOptionPrice(
        bool isPut,
        uint256 expiry,
        uint256 strike,
        uint256 lastPrice,
        uint256 baseIv
    ) external view returns (uint256);
}

struct EpochStrikeData {
    address strikeToken;
    uint256 totalCollateral;
    uint256 activeCollateral;
    uint256 totalPremiums;
    uint256 checkpointPointer;
    uint256[] rewardStoredForPremiums;
    uint256[] rewardDistributionRatiosForPremiums;
}

struct EpochData {
    bool expired;
    uint256 startTime;
    uint256 expiry;
    uint256 settlementPrice;
    uint256 totalCollateralBalance; // Premium + Deposits from all strikes
    uint256 collateralExchangeRate; // Exchange rate for collateral to underlying (Only applicable to CALL options)
    uint256 settlementCollateralExchangeRate; // Exchange rate for collateral to underlying on settlement (Only applicable to CALL options)
    uint256[] strikes;
    uint256[] totalRewardsCollected;
    uint256[] rewardDistributionRatios;
    address[] rewardTokensToDistribute;
}

interface ISSOV {
    function getEpochStrikeData(uint256 epoch, uint256 strike)
        external
        view
        returns (EpochStrikeData memory);

    function getEpochData(uint256 epoch)
        external
        view
        returns (EpochData memory);

    function currentEpoch() external view returns (uint256);

    function collateralPrecision() external view returns (uint256);

    function getVolatility(uint256) external view returns (uint256);

    function getUnderlyingPrice() external view returns (uint256);

    function getEpochTimes(uint256 _epoch)
        external
        view
        returns (uint256, uint256);

    function isPut() external view returns (bool);

    function underlyingSymbol() external view returns (string memory);

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

// Contracts

// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

/// @title Lighter version of the Openzeppelin Pausable contract
/// @author witherblock
/// @notice Helps pause a contract to block the execution of selected functions
/// @dev Difference from the Openzeppelin version is changing the modifiers to internal fns and requires to reverts
abstract contract Pausable {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Internal function to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _whenNotPaused() internal view {
        if (paused()) revert ContractPaused();
    }

    /**
     * @dev Internal function to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _whenPaused() internal view {
        if (!paused()) revert ContractNotPaused();
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual {
        _whenNotPaused();
        _paused = true;
        emit Paused(msg.sender);
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual {
        _whenPaused();
        _paused = false;
        emit Unpaused(msg.sender);
    }

    error ContractPaused();
    error ContractNotPaused();
}

/// @title ContractWhitelist
/// @author witherblock
/// @notice A helper contract that lets you add a list of whitelisted contracts that should be able to interact with restricited functions
abstract contract ContractWhitelist {
    /// @dev contract => whitelisted or not
    mapping(address => bool) public whitelistedContracts;

    /*==== SETTERS ====*/

    /// @dev add to the contract whitelist
    /// @param _contract the address of the contract to add to the contract whitelist
    function _addToContractWhitelist(address _contract) internal {
        require(isContract(_contract), "Address must be a contract");
        require(
            !whitelistedContracts[_contract],
            "Contract already whitelisted"
        );

        whitelistedContracts[_contract] = true;

        emit AddToContractWhitelist(_contract);
    }

    /// @dev remove from  the contract whitelist
    /// @param _contract the address of the contract to remove from the contract whitelist
    function _removeFromContractWhitelist(address _contract) internal {
        require(whitelistedContracts[_contract], "Contract not whitelisted");

        whitelistedContracts[_contract] = false;

        emit RemoveFromContractWhitelist(_contract);
    }

    // modifier is eligible sender modifier
    function _isEligibleSender() internal view {
        // the below condition checks whether the caller is a contract or not
        if (msg.sender != tx.origin)
            require(
                whitelistedContracts[msg.sender],
                "Contract must be whitelisted"
            );
    }

    /*==== VIEWS ====*/

    /// @dev checks for contract or eoa addresses
    /// @param addr the address to check
    /// @return bool whether the passed address is a contract address
    function isContract(address addr) public view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(addr)
        }
        return size > 0;
    }

    /*==== EVENTS ====*/

    event AddToContractWhitelist(address indexed _contract);

    event RemoveFromContractWhitelist(address indexed _contract);
}

// Libraries

contract BaseSsovLp is Ownable, ReentrancyGuard, Pausable, ContractWhitelist {
    using SafeERC20 for IERC20;

    uint256 internal constant MULTI_ADD_LIMIT = 5;
    uint256 internal constant PERCENT = 1e2;
    uint256 internal constant USDC_DECIMALS = 1e6;
    uint256 internal constant PRICE_DECIMALS = 1e8;
    uint256 internal constant DUST_THRESHOLD = 1e7; // $10
    uint256 internal constant TOKEN_DUST_THRESHOLD = 1e15; // 0.001 $token
    uint256 internal constant AMOUNT_PRICE_TO_USDC_DECIMALS =
        (1e18 * 1e8) / 1e6;

    struct OptionTokenInfo {
        // SSOV for option token
        address ssov;
        // Strike price
        uint256 strike;
        // USD liquidity
        uint256 usdLiquidity;
        // Underlying token liquidity
        uint256 underlyingLiquidity;
    }

    struct Addresses {
        // USD token address (1e6 precision)
        address usd;
        // Underlying token address (1e18 precision)
        address underlying;
        // OptionPricing address
        address optionPricing;
        // AssetSwapper address
        address assetSwapper;
    }

    Addresses public addresses;

    /// @dev mapping (option token => OptionTokenInfo)
    mapping(address => OptionTokenInfo) public getOptionTokenInfo;
    /// @dev mapping (ssov address =>  epochs)
    mapping(address => uint256[]) internal ssovEpochs;
    /// @dev mapping (ssov address =>  expires)
    mapping(address => uint256[]) internal ssovExpiries;
    /// @dev mapping (token address => (isPut => SSOV address))
    mapping(address => mapping(bool => address)) internal tokenVaultRegistry;

    /*==== EVENTS ====*/

    event UsdLiquidityForStrikeAdded(
        address indexed epochStrikeToken,
        address indexed buyer,
        uint256 lpId,
        uint256 usdLiquidity
    );
    event UnderlyingLiquidityForStrikeAdded(
        address indexed epochStrikeToken,
        address indexed buyer,
        uint256 lpId,
        uint256 baseLiquidity
    );
    event LpPositionFilled(
        address indexed epochStrikeToken,
        uint256 lpId,
        uint256 amount,
        uint256 usdPremium,
        uint256 underlyingPremium,
        address indexed seller
    );
    event LpPositionKilled(address indexed epochStrikeToken, uint256 index);
    event LpDustCleared(address indexed epochStrikeToken, uint256 index);
    event SsovForTokenRegistered(address token, bool isPut, address ssov);
    event SsovForTokenRemoved(address token, bool isPut, address ssov);
    event AddressesSet(Addresses _addresses);
    event SsovExpiryUpdated(address ssov, uint256 expiry);
    event EmergencyWithdrawn(address caller);

    /*==== ADMIN METHODS ====*/

    /// @notice Pauses the vault for emergency cases
    /// @dev Can only be called by the owner
    function pause() external onlyOwner {
        _pause();
    }

    /// @notice Unpauses the vault
    /// @dev Can only be called by the owner
    function unpause() external onlyOwner {
        _unpause();
    }

    /// @notice Add a contract to the whitelist
    /// @dev Can only be called by the admin
    /// @param _contract Address of the contract that needs to be added to the whitelist
    function addToContractWhitelist(address _contract) external onlyOwner {
        _addToContractWhitelist(_contract);
    }

    /// @notice Remove a contract to the whitelist
    /// @dev Can only be called by the admin
    /// @param _contract Address of the contract that needs to be removed from the whitelist
    function removeFromContractWhitelist(address _contract) external onlyOwner {
        _removeFromContractWhitelist(_contract);
    }

    /// @notice Sets (adds) a list of addresses to the address list
    /// @dev Can only be called by the owner
    /// @param _addresses addresses of contracts in the Addresses struct
    function setAddresses(Addresses calldata _addresses) external onlyOwner {
        addresses = _addresses;
        emit AddressesSet(_addresses);
    }

    /// @notice Transfers all funds to msg.sender
    /// @dev Can only be called by the owner
    /// @param tokens The list of erc20 tokens to withdraw
    /// @param transferNative Whether should transfer the native currency
    function emergencyWithdrawn(address[] calldata tokens, bool transferNative)
        external
        onlyOwner
    {
        _whenPaused();
        if (transferNative) payable(msg.sender).transfer(address(this).balance);

        IERC20 token;

        for (uint256 i = 0; i < tokens.length; i++) {
            token = IERC20(tokens[i]);
            token.safeTransfer(msg.sender, token.balanceOf(address(this)));
        }

        emit EmergencyWithdrawn(msg.sender);
    }

    /// @notice Register the vault for token
    /// @param token Token address
    /// @param vault SSOV address
    /// @param isPut isPut
    function registerSsovForToken(
        address token,
        address vault,
        bool isPut
    ) external onlyOwner {
        require(isPut == getSsov(vault).isPut(), "invalid isPut");
        require(
            token != address(0) && vault != address(0),
            "addresses cannot be null"
        );
        tokenVaultRegistry[token][isPut] = vault;
        emit SsovForTokenRegistered(token, isPut, vault);
    }

    /// @notice Unregister the vault for token
    /// @param token Token address
    /// @param isPut Is puts
    function unregisterSsovForToken(address token, bool isPut)
        external
        onlyOwner
    {
        address toRemoveVault = tokenVaultRegistry[token][isPut];
        tokenVaultRegistry[token][isPut] = address(0);
        emit SsovForTokenRemoved(token, isPut, toRemoveVault);
    }

    /*==== BOT METHODS ====*/

    /// @notice Updates the list of epoch expiries
    /// @dev Can be run by a bot
    /// @param ssov addresses of ssov
    /// @param epoch epoch to update
    function updateSsovEpoch(address ssov, uint256 epoch)
        external
        returns (bool)
    {
        uint256 expiry = getSsovExpiry(ssov, epoch);
        require(expiry > block.timestamp, "Expiry must be in the future");
        if (
            ssovExpiries[ssov].length == 0 ||
            ssovExpiries[ssov][ssovExpiries[ssov].length - 1] != expiry
        ) {
            ssovExpiries[ssov].push(expiry);
            ssovEpochs[ssov].push(epoch);
            emit SsovExpiryUpdated(ssov, expiry);
            return true;
        }
        return false;
    }

    /*==== INTERNAL METHODS ====*/

    /// @dev Internal function to swap underlying to USD
    /// @param amount Amount of underlying to swap
    /// @param minAmountOut Amount of minimum usd to receive (slippage control parameter)
    /// @param swapperId The swapperId which dictates the amm path to use while swapping
    /// @return usdReceived The amount of USD received after the swap
    function _swapUnderlyingToUsd(
        uint256 amount,
        uint256 minAmountOut,
        uint256 swapperId
    ) internal returns (uint256 usdReceived) {
        usdReceived = IAssetSwapper(addresses.assetSwapper).swapAsset(
            addresses.underlying,
            addresses.usd,
            amount,
            minAmountOut,
            swapperId
        );
    }

    /// @notice Returns SSOV contract
    /// @param vault SSOV address
    function getSsov(address vault) internal pure returns (ISSOV) {
        return ISSOV(vault);
    }

    /*==== SSOV VIEW METHODS ====*/

    /// @notice Returns the SSOV expiry
    /// @param vault SSOV address
    /// @param epoch SSOV epoch
    function getSsovExpiry(address vault, uint256 epoch)
        public
        view
        returns (uint256)
    {
        (, uint256 expiry) = getSsov(vault).getEpochTimes(epoch);
        return expiry;
    }

    /// @notice Returns the SSOV option token address
    /// @param vault SSOV address
    /// @param epoch SSOV epoch
    /// @param strike SSOV strike
    function getSsovOptionToken(
        address vault,
        uint256 epoch,
        uint256 strike
    ) public view returns (address) {
        return getSsov(vault).getEpochStrikeData(epoch, strike).strikeToken;
    }

    /// @notice Returns the SSOV epoch strikes
    /// @param vault SSOV address
    /// @param epoch SSOV epoch
    function getSsovEpochStrikes(address vault, uint256 epoch)
        public
        view
        returns (uint256[] memory strikes)
    {
        return getSsov(vault).getEpochData(epoch).strikes;
    }

    /// @notice Returns the current SSOV epoch
    /// @param vault SSOV address
    function getSsovEpoch(address vault) public view returns (uint256) {
        return getSsov(vault).currentEpoch();
    }

    /// @notice Returns the current underlying price of an SSOV
    /// @param vault SSOV address
    function getSsovUnderlyingPrice(address vault)
        public
        view
        returns (uint256)
    {
        return getSsov(vault).getUnderlyingPrice();
    }

    /// @notice Returns true if the epoch has expired for an SSOV
    /// @param vault SSOV address
    /// @param epoch SSOV epoch
    function hasEpochExpired(address vault, uint256 epoch)
        public
        view
        returns (bool)
    {
        return getSsovExpiry(vault, epoch) <= block.timestamp;
    }

    /// @notice Returns the current volatility of SSOV strike
    /// @param vault SSOV address
    /// @param strike SSOV strike
    function getSsovVolatility(address vault, uint256 strike)
        public
        view
        returns (uint256)
    {
        return getSsov(vault).getVolatility(strike);
    }

    /// @notice Calculate premium for an option
    /// @param isPut call or put options
    /// @param expiry expiry
    /// @param strike Strike price of the option
    /// @param amount Amount of options (1e18 precision)
    /// @param volatility Volatility of the option
    /// @param vault Address of ssov
    /// @return premium in USD
    function calculatePremium(
        bool isPut,
        uint256 strike,
        uint256 expiry,
        uint256 amount,
        uint256 volatility,
        address vault
    ) public view returns (uint256) {
        return
            (IOptionPricing(addresses.optionPricing).getOptionPrice(
                isPut,
                expiry,
                strike,
                getSsovUnderlyingPrice(vault),
                volatility
            ) * amount) / AMOUNT_PRICE_TO_USDC_DECIMALS;
    }

    /// @notice Calculate premium for an option
    /// @param ssov address of ssov
    /// @param usdPremium premium in USD
    /// @return premium in underlying
    function getPremiumInUnderlying(address ssov, uint256 usdPremium)
        public
        view
        returns (uint256)
    {
        return
            Math.mulDiv(
                usdPremium,
                PRICE_DECIMALS * (10**IERC20(addresses.underlying).decimals()),
                getSsovUnderlyingPrice(ssov) * USDC_DECIMALS
            );
    }

    /// @notice Returns the epochs of an ssov
    /// @param vault SSOV address
    function getSsovEpochs(address vault)
        public
        view
        returns (uint256[] memory)
    {
        return ssovEpochs[vault];
    }

    /// @notice Returns the expiries of an ssov
    /// @param vault SSOV address
    function getSsovEpochExpiries(address vault)
        external
        view
        returns (uint256[] memory)
    {
        return ssovExpiries[vault];
    }

    /// @notice Returns the token vault registry
    /// @param token Token address
    /// @param isPut isPut
    function getTokenVaultRegistry(address token, bool isPut)
        external
        view
        returns (address)
    {
        return tokenVaultRegistry[token][isPut];
    }

    /// @notice Returns the SSOV option token addresses for an epoch
    /// @param vault SSOV address
    /// @param epoch SSOV epoch
    function getSsovOptionTokens(address vault, uint256 epoch)
        external
        view
        returns (address[] memory tokens)
    {
        uint256[] memory strikes = getSsovEpochStrikes(vault, epoch);
        tokens = new address[](strikes.length);
        for (uint256 i; i < strikes.length; ++i) {
            tokens[i] = getSsovOptionToken(vault, epoch, strikes[i]);
        }
    }

    /// @notice Returns the collateral precision of an SSOV
    /// @param vault SSOV address
    function getSsovCollateralPrecision(address vault)
        external
        view
        returns (uint256)
    {
        return getSsov(vault).collateralPrecision();
    }

    /*==== ERRORS ====*/

    error AmountTooSmall();
    error InsuffientLiquidity();
    error InvalidDiscount();
    error InvalidEpochToFill();
    error InvalidLiquidity();
    error InvalidLpIndex();
    error InvalidParams();
    error InvalidStrike();
    error LpPositionDead();
    error OnlyBuyerCanKill();
    error SsovDoesNotExist();
    error SsovEpochExpired();
}

// OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized < type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

contract SsovLp is BaseSsovLp, Initializable {
    using SafeERC20 for IERC20;

    struct LpPosition {
        // Id for LP position
        uint256 lpId;
        // Epoch for Lp position
        uint256 epoch;
        // Strike price
        uint256 strike;
        // Available usd liquidity in Lp position
        uint256 usdLiquidity;
        // Available underlying liquidity in Lp position
        uint256 underlyingLiquidity;
        // Amount of usd liquidity used to purchase options
        uint256 usdLiquidityUsed;
        // Amount of underlying liquidity used to purchase options
        uint256 underlyingLiquidityUsed;
        // Discount in % to market price
        uint256 discount;
        // Amount of options purchased
        uint256 purchased;
        // maxTokensToFill Max tokens to buy
        uint256 maxTokensToFill;
        // Buyer address
        address buyer;
        // Is position killed
        bool killed;
    }

    string public name;
    string public underlyingSymbol;

    /// @dev mapping (epoch strike token address) => LpPosition[])
    mapping(address => LpPosition[]) internal allLpPositions;
    /// @dev mapping (user => striken token => lpId[])
    mapping(address => mapping(address => uint256[])) internal userLpPositions;

    /*==== INITIALIZE ====*/

    /// @dev An SsovLp contract maps to the same underlying and duration.
    /// @param _name Name of contract, i.e., ETH-MONTHLY, RDPX-WEEKLY
    /// @param _underlyingSymbol The symbol of the contract
    /// @param _addresses Addresses of the contract
    /// @param _sender Deployer address
    function initialize(
        string memory _name,
        string memory _underlyingSymbol,
        Addresses memory _addresses,
        address _sender
    ) external initializer {
        name = _name;
        underlyingSymbol = _underlyingSymbol;
        addresses = _addresses;

        IERC20(addresses.underlying).safeIncreaseAllowance(
            addresses.assetSwapper,
            type(uint256).max
        );

        _transferOwnership(_sender);
    }

    /*==== USER METHODS ====*/

    /**
     * Adds multiple new Lp positions for a token
     * @param token underlying token address
     * @param isUsd Is the liquidity USD or underlying
     * @param isPut Is the Lp for puts
     * @param strike Strike to purchase at
     * @param liquidity Liquidity per strike.
     *  Must be in 6 decimals if isUsd = true else 18 decimals
     * @param discount Discount on volatility
     * @param maxTokensToFill Max tokens to buy
     * @param to Address to send option tokens to if purchase succeeds
     * @return Whether new Lp position was created
     */
    function addToLp(
        address token,
        bool isUsd,
        bool isPut,
        uint256 strike,
        uint256 liquidity,
        uint256 discount,
        uint256 maxTokensToFill,
        address to
    ) external nonReentrant returns (bool) {
        _isEligibleSender();
        _whenNotPaused();

        address ssov = tokenVaultRegistry[token][isPut];
        if (ssov == address(0)) {
            revert SsovDoesNotExist();
        }

        if (isUsd) {
            return
                _addUsd(ssov, strike, liquidity, discount, maxTokensToFill, to);
        }
        return
            _addUnderlying(
                ssov,
                strike,
                liquidity,
                discount,
                maxTokensToFill,
                to
            );
    }

    /**
     * Adds multiple new Lp positions for a token
     * @param token underlying token address
     * @param isUsd Is the liquidity USD or underlying
     * @param isPut Is the Lp for puts
     * @param strikes Strikes to purchase at
     * @param liquidity Liquidity per strike.
     *  Must be in 6 decimals if isUsd = true else 18 decimals
     * @param discount Discount on volatility
     * @param maxTokensToFill Max tokens to buy
     * @param to Address to send option tokens to if purchase succeeds
     * @return Whether new Lp position was created
     */
    function multiAddToLp(
        address token,
        bool isUsd,
        bool isPut,
        uint256[] memory strikes,
        uint256[] memory liquidity,
        uint256[] memory discount,
        uint256[] memory maxTokensToFill,
        address to
    ) external nonReentrant returns (bool) {
        _isEligibleSender();
        _whenNotPaused();

        address ssov = tokenVaultRegistry[token][isPut];
        if (ssov == address(0)) {
            revert SsovDoesNotExist();
        }
        if (
            strikes.length == 0 ||
            strikes.length > MULTI_ADD_LIMIT ||
            strikes.length != liquidity.length ||
            liquidity.length != discount.length
        ) {
            revert InvalidParams();
        }

        for (uint256 i; i < strikes.length; ++i) {
            if (isUsd) {
                _addUsd(
                    ssov,
                    strikes[i],
                    liquidity[i],
                    discount[i],
                    maxTokensToFill[i],
                    to
                );
            } else {
                _addUnderlying(
                    ssov,
                    strikes[i],
                    liquidity[i],
                    discount[i],
                    maxTokensToFill[i],
                    to
                );
            }
        }
        return true;
    }

    /// @dev helper function to check if params are valid before adding
    function _canAddToLp(
        bool isUsd,
        address ssov,
        address strikeToken,
        uint256 liquidity,
        uint256 discount,
        uint256 currentEpoch
    ) internal view returns (bool) {
        if (isUsd && liquidity < DUST_THRESHOLD) {
            revert InvalidLiquidity();
        }
        if (!isUsd && liquidity < TOKEN_DUST_THRESHOLD) {
            revert InvalidLiquidity();
        }
        if (discount == 0 || discount > 100) {
            revert InvalidDiscount();
        }
        if (hasEpochExpired(ssov, currentEpoch)) {
            revert SsovEpochExpired();
        }
        if (strikeToken == address(0)) {
            revert InvalidStrike();
        }
        return true;
    }

    /// @dev helper function to cache unseen tokens
    function _cacheStrikeToken(
        address ssov,
        address strikeToken,
        uint256 strike
    ) internal returns (bool) {
        if (getOptionTokenInfo[strikeToken].ssov == address(0)) {
            getOptionTokenInfo[strikeToken].ssov = address(ssov);
            getOptionTokenInfo[strikeToken].strike = strike;
        }
        return true;
    }

    /// @dev helper function to create an Lp
    function _createLp(
        address strikeToken,
        uint256 strike,
        uint256 usdLiquidity,
        uint256 underlyingLiquidity,
        uint256 discount,
        uint256 currentEpoch,
        uint256 maxTokensToFill,
        address buyer
    ) internal returns (uint256 lpId, LpPosition memory lp) {
        lpId = allLpPositions[strikeToken].length;

        lp.lpId = lpId;
        lp.epoch = currentEpoch;
        lp.strike = strike;
        lp.usdLiquidity = usdLiquidity;
        lp.underlyingLiquidity = underlyingLiquidity;
        lp.discount = discount;
        lp.maxTokensToFill = maxTokensToFill;
        lp.buyer = buyer;

        allLpPositions[strikeToken].push(lp);
        userLpPositions[buyer][strikeToken].push(lpId);
    }

    /// @dev helper function to add usd liquidity
    function _addUsd(
        address ssov,
        uint256 strike,
        uint256 liquidity,
        uint256 discount,
        uint256 maxTokensToFill,
        address buyer
    ) internal returns (bool) {
        uint256 currentEpoch = getSsovEpoch(ssov);
        address strikeToken = getSsovOptionToken(ssov, currentEpoch, strike);

        _canAddToLp({
            isUsd: true,
            ssov: ssov,
            strikeToken: strikeToken,
            liquidity: liquidity,
            discount: discount,
            currentEpoch: currentEpoch
        });
        _cacheStrikeToken({
            ssov: ssov,
            strikeToken: strikeToken,
            strike: strike
        });
        (uint256 lpId, ) = _createLp({
            strikeToken: strikeToken,
            strike: strike,
            usdLiquidity: liquidity,
            underlyingLiquidity: 0,
            discount: discount,
            currentEpoch: currentEpoch,
            maxTokensToFill: maxTokensToFill,
            buyer: buyer
        });

        getOptionTokenInfo[strikeToken].usdLiquidity += liquidity;

        IERC20(addresses.usd).safeTransferFrom(
            msg.sender,
            address(this),
            liquidity
        );

        emit UsdLiquidityForStrikeAdded(strikeToken, buyer, lpId, liquidity);
        return true;
    }

    /// @dev helper function to add underlying liquidity
    function _addUnderlying(
        address ssov,
        uint256 strike,
        uint256 liquidity,
        uint256 discount,
        uint256 maxTokensToFill,
        address buyer
    ) internal returns (bool) {
        uint256 currentEpoch = getSsovEpoch(ssov);
        address strikeToken = getSsovOptionToken(ssov, currentEpoch, strike);

        _canAddToLp({
            isUsd: false,
            ssov: ssov,
            strikeToken: strikeToken,
            liquidity: liquidity,
            discount: discount,
            currentEpoch: currentEpoch
        });
        _cacheStrikeToken({
            ssov: ssov,
            strikeToken: strikeToken,
            strike: strike
        });
        (uint256 lpId, ) = _createLp({
            strikeToken: strikeToken,
            strike: strike,
            usdLiquidity: 0,
            underlyingLiquidity: liquidity,
            discount: discount,
            currentEpoch: currentEpoch,
            maxTokensToFill: maxTokensToFill,
            buyer: buyer
        });

        getOptionTokenInfo[strikeToken].underlyingLiquidity += liquidity;

        IERC20(addresses.underlying).safeTransferFrom(
            msg.sender,
            address(this),
            liquidity
        );

        emit UnderlyingLiquidityForStrikeAdded(
            strikeToken,
            buyer,
            lpId,
            liquidity
        );
        return true;
    }

    /**
     * Fills an Lp position with available liquidity
     * @param isPut is put option
     * @param outUsd give user the option to receive USDC for liquidity in ETH,
     * @param strikeToken epoch strike token address
     * @param lpIndex Index of Lp position
     * @param amount Amount of options to buy from each Lp position
     * @param minAmountOut The minimum amount of usd to receive if outUsd is true
     * @param swapperId The swapperId which dictates which amm path will be used
     * @return Whether Lp positions were filled
     */
    function fillLpPosition(
        bool isPut,
        bool outUsd,
        address strikeToken,
        uint256 lpIndex,
        uint256 amount,
        uint256 minAmountOut,
        uint256 swapperId
    ) external nonReentrant returns (bool) {
        _isEligibleSender();
        _whenNotPaused();

        address ssov = getOptionTokenInfo[strikeToken].ssov;
        if (ssov == address(0)) {
            revert SsovDoesNotExist();
        }
        _fillLpPosition(
            isPut,
            outUsd,
            ssov,
            strikeToken,
            lpIndex,
            amount,
            minAmountOut,
            swapperId
        );
        return true;
    }

    /**
     * Fills multiple Lp positions with available liquidity
     * @param isPut is put option
     * @param outUsd give user the option to receive USDC for liquidity in ETH,
     * @param strikeToken epoch strike token address
     * @param lpIndices Index of Lp position
     * @param amount Amount of options to buy from each Lp position
     * @param minAmountOuts The minimum amount out of usd to receive for each LP position
     * @param swapperId The swapperId
     * @return Whether Lp positions were filled
     */
    function multiFillLpPosition(
        bool isPut,
        bool outUsd,
        address strikeToken,
        uint256[] memory lpIndices,
        uint256[] memory amount,
        uint256[] memory minAmountOuts,
        uint256 swapperId
    ) external nonReentrant returns (bool) {
        _isEligibleSender();
        _whenNotPaused();

        address ssov = getOptionTokenInfo[strikeToken].ssov;
        if (ssov == address(0)) {
            revert SsovDoesNotExist();
        }
        if (lpIndices.length == 0 || lpIndices.length != amount.length) {
            revert InvalidParams();
        }

        for (uint256 i; i < lpIndices.length; ++i) {
            _fillLpPosition(
                isPut,
                outUsd,
                ssov,
                strikeToken,
                lpIndices[i],
                amount[i],
                minAmountOuts[i],
                swapperId
            );
        }
        return true;
    }

    /// @dev helper function to fill an Lp position at index
    function _fillLpPosition(
        bool isPut,
        bool outUsd,
        address ssov,
        address strikeToken,
        uint256 lpIndex,
        uint256 amount,
        uint256 minAmountOut,
        uint256 swapperId
    ) internal returns (bool) {
        if (amount <= TOKEN_DUST_THRESHOLD) {
            revert AmountTooSmall();
        }
        if (lpIndex >= allLpPositions[strikeToken].length) {
            revert InvalidLpIndex();
        }

        LpPosition memory lpPosition = allLpPositions[strikeToken][lpIndex];
        if (lpPosition.killed) {
            revert LpPositionDead();
        }
        if (hasEpochExpired(ssov, getSsovEpoch(ssov))) {
            revert InvalidEpochToFill();
        }

        amount = Math.min(amount, lpPosition.maxTokensToFill);

        uint256 volatility = getSsovVolatility(ssov, lpPosition.strike);

        // volatility -= discount
        volatility -= Math.mulDiv(volatility, lpPosition.discount, PERCENT);

        uint256 usdPremium = calculatePremium({
            isPut: isPut,
            strike: lpPosition.strike,
            expiry: getSsovExpiry(ssov, getSsovEpoch(ssov)),
            amount: amount,
            volatility: volatility,
            vault: ssov
        });
        uint256 underlyingPremium;

        if (lpPosition.usdLiquidity != 0) {
            _fillUsd({
                usdPremium: usdPremium,
                usdLiquidity: lpPosition.usdLiquidity,
                strikeToken: strikeToken,
                lpIndex: lpIndex
            });
        } else {
            underlyingPremium = getPremiumInUnderlying(ssov, usdPremium);
            _fillUnderlying({
                outUsd: outUsd,
                strikeToken: strikeToken,
                premium: underlyingPremium,
                underlyingLiquidity: lpPosition.underlyingLiquidity,
                lpIndex: lpIndex,
                minAmountOut: minAmountOut,
                swapperId: swapperId
            });
        }

        allLpPositions[strikeToken][lpIndex].purchased += amount;
        IERC20(strikeToken).safeTransferFrom(
            msg.sender,
            lpPosition.buyer,
            amount
        );

        emit LpPositionFilled(
            strikeToken,
            lpIndex,
            amount,
            usdPremium,
            underlyingPremium,
            msg.sender
        );
        return true;
    }

    /// @dev helper function to fill an Lp position's usd liquidity
    /// @notice premium is in usd
    function _fillUsd(
        uint256 usdPremium,
        uint256 usdLiquidity,
        address strikeToken,
        uint256 lpIndex
    ) internal returns (bool) {
        if (usdPremium > usdLiquidity) {
            revert InsuffientLiquidity();
        }
        allLpPositions[strikeToken][lpIndex].usdLiquidity -= usdPremium;
        allLpPositions[strikeToken][lpIndex].usdLiquidityUsed += usdPremium;
        getOptionTokenInfo[strikeToken].usdLiquidity -= usdPremium;

        if (
            allLpPositions[strikeToken][lpIndex].usdLiquidity < DUST_THRESHOLD
        ) {
            _clearLpDust(strikeToken, lpIndex);
        }
        IERC20(addresses.usd).safeTransfer(msg.sender, usdPremium);
        return true;
    }

    /// @dev helper function to fill an Lp position's underlying liquidity
    /// @notice premium is in underlying
    function _fillUnderlying(
        bool outUsd,
        address strikeToken,
        uint256 premium,
        uint256 underlyingLiquidity,
        uint256 lpIndex,
        uint256 minAmountOut,
        uint256 swapperId
    ) internal returns (bool) {
        if (premium > underlyingLiquidity) {
            revert InsuffientLiquidity();
        }
        allLpPositions[strikeToken][lpIndex].underlyingLiquidity -= premium;
        allLpPositions[strikeToken][lpIndex].underlyingLiquidityUsed += premium;
        getOptionTokenInfo[strikeToken].underlyingLiquidity -= premium;

        if (
            allLpPositions[strikeToken][lpIndex].underlyingLiquidity <
            TOKEN_DUST_THRESHOLD
        ) {
            _clearLpDust(strikeToken, lpIndex);
        }

        if (outUsd) {
            uint256 usdPremium = _swapUnderlyingToUsd(
                premium,
                minAmountOut,
                swapperId
            );
            IERC20(addresses.usd).safeTransfer(msg.sender, usdPremium);
        } else {
            IERC20(addresses.underlying).safeTransfer(msg.sender, premium);
        }

        return true;
    }

    /// @dev helper function to clear Lp dust position at index
    function _clearLpDust(address strikeToken, uint256 lpIndex)
        internal
        returns (bool)
    {
        LpPosition memory lpPosition = allLpPositions[strikeToken][lpIndex];

        _killAndTransfer(
            strikeToken,
            lpIndex,
            lpPosition.buyer,
            lpPosition.usdLiquidity,
            lpPosition.underlyingLiquidity
        );

        emit LpDustCleared(strikeToken, lpIndex);
        return true;
    }

    /**
     * Kills an active Lp position
     * @param strikeToken epoch strike token address
     * @param lpIndex Index of Lp position
     * @return Whether Lp position is killed
     */
    function killLpPosition(address strikeToken, uint256 lpIndex)
        external
        nonReentrant
        returns (bool)
    {
        _isEligibleSender();
        _whenNotPaused();

        if (getOptionTokenInfo[strikeToken].ssov == address(0)) {
            revert SsovDoesNotExist();
        }
        _killLpPosition(strikeToken, lpIndex);
        return true;
    }

    /**
     * Kills multiple active Lp positions
     * @param strikeToken epoch strike token address
     * @param lpIndices Indices of Lp position
     * @return Whether Lp positions are killed
     */
    function multiKillLpPosition(
        address strikeToken,
        uint256[] memory lpIndices
    ) external nonReentrant returns (bool) {
        _isEligibleSender();
        _whenNotPaused();

        if (getOptionTokenInfo[strikeToken].ssov == address(0)) {
            revert SsovDoesNotExist();
        }
        if (lpIndices.length == 0) {
            revert InvalidParams();
        }
        for (uint256 i; i < lpIndices.length; ++i) {
            _killLpPosition(strikeToken, lpIndices[i]);
        }
        return true;
    }

    /// @dev helper function to kill an Lp position at index
    function _killLpPosition(address strikeToken, uint256 lpIndex)
        internal
        returns (bool)
    {
        if (lpIndex >= allLpPositions[strikeToken].length) {
            revert InvalidLpIndex();
        }

        LpPosition memory lpPosition = allLpPositions[strikeToken][lpIndex];
        if (lpPosition.buyer != msg.sender) {
            revert OnlyBuyerCanKill();
        }
        if (lpPosition.killed) {
            revert LpPositionDead();
        }
        _killAndTransfer(
            strikeToken,
            lpIndex,
            lpPosition.buyer,
            lpPosition.usdLiquidity,
            lpPosition.underlyingLiquidity
        );
        emit LpPositionKilled(strikeToken, lpIndex);
        return true;
    }

    /// @dev helper function to kill position and transfer liquidity left in position back to Lp. This does not update liquidity in Lp position.
    function _killAndTransfer(
        address strikeToken,
        uint256 lpIndex,
        address buyer,
        uint256 usdLiquidity,
        uint256 underlyingLiquidity
    ) internal returns (bool) {
        allLpPositions[strikeToken][lpIndex].killed = true;

        if (usdLiquidity != 0) {
            getOptionTokenInfo[strikeToken].usdLiquidity -= usdLiquidity;
            IERC20(addresses.usd).safeTransfer(buyer, usdLiquidity);
        } else if (underlyingLiquidity != 0) {
            getOptionTokenInfo[strikeToken]
                .underlyingLiquidity -= underlyingLiquidity;
            IERC20(addresses.underlying).safeTransfer(
                buyer,
                underlyingLiquidity
            );
        }
        return true;
    }

    /*==== VIEW METHODS ====*/

    /**
     * @notice Returns all Lp positions for a given user
     * @param user address of user
     * @param strikeToken epoch strike token address
     * @return positions the user's Lp positions
     */
    function getUserLpPositions(address user, address strikeToken)
        external
        view
        returns (LpPosition[] memory positions)
    {
        uint256[] memory userPositionsId = userLpPositions[user][strikeToken];
        uint256 numPositions = userPositionsId.length;
        positions = new LpPosition[](numPositions);
        for (uint256 i; i < numPositions; ) {
            positions[i] = allLpPositions[strikeToken][userPositionsId[i]];
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Returns all Lp positions for a given strikeToken
     * @param strikeToken epoch strike token address
     * @return all Lp positions
     */
    function getAllLpPositions(address strikeToken)
        external
        view
        returns (LpPosition[] memory)
    {
        return allLpPositions[strikeToken];
    }
}

Contract ABI

[{"inputs":[],"name":"AmountTooSmall","type":"error"},{"inputs":[],"name":"ContractNotPaused","type":"error"},{"inputs":[],"name":"ContractPaused","type":"error"},{"inputs":[],"name":"InsuffientLiquidity","type":"error"},{"inputs":[],"name":"InvalidDiscount","type":"error"},{"inputs":[],"name":"InvalidEpochToFill","type":"error"},{"inputs":[],"name":"InvalidLiquidity","type":"error"},{"inputs":[],"name":"InvalidLpIndex","type":"error"},{"inputs":[],"name":"InvalidParams","type":"error"},{"inputs":[],"name":"InvalidStrike","type":"error"},{"inputs":[],"name":"LpPositionDead","type":"error"},{"inputs":[],"name":"OnlyBuyerCanKill","type":"error"},{"inputs":[],"name":"SsovDoesNotExist","type":"error"},{"inputs":[],"name":"SsovEpochExpired","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_contract","type":"address"}],"name":"AddToContractWhitelist","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"usd","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"optionPricing","type":"address"},{"internalType":"address","name":"assetSwapper","type":"address"}],"indexed":false,"internalType":"struct BaseSsovLp.Addresses","name":"_addresses","type":"tuple"}],"name":"AddressesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"EmergencyWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"epochStrikeToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"LpDustCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"epochStrikeToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"usdPremium","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"underlyingPremium","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"}],"name":"LpPositionFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"epochStrikeToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"LpPositionKilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_contract","type":"address"}],"name":"RemoveFromContractWhitelist","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"ssov","type":"address"},{"indexed":false,"internalType":"uint256","name":"expiry","type":"uint256"}],"name":"SsovExpiryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"bool","name":"isPut","type":"bool"},{"indexed":false,"internalType":"address","name":"ssov","type":"address"}],"name":"SsovForTokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"bool","name":"isPut","type":"bool"},{"indexed":false,"internalType":"address","name":"ssov","type":"address"}],"name":"SsovForTokenRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"epochStrikeToken","type":"address"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseLiquidity","type":"uint256"}],"name":"UnderlyingLiquidityForStrikeAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"epochStrikeToken","type":"address"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"usdLiquidity","type":"uint256"}],"name":"UsdLiquidityForStrikeAdded","type":"event"},{"inputs":[{"internalType":"address","name":"_contract","type":"address"}],"name":"addToContractWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"isUsd","type":"bool"},{"internalType":"bool","name":"isPut","type":"bool"},{"internalType":"uint256","name":"strike","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"discount","type":"uint256"},{"internalType":"uint256","name":"maxTokensToFill","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"addToLp","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"addresses","outputs":[{"internalType":"address","name":"usd","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"optionPricing","type":"address"},{"internalType":"address","name":"assetSwapper","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"isPut","type":"bool"},{"internalType":"uint256","name":"strike","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"volatility","type":"uint256"},{"internalType":"address","name":"vault","type":"address"}],"name":"calculatePremium","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"bool","name":"transferNative","type":"bool"}],"name":"emergencyWithdrawn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isPut","type":"bool"},{"internalType":"bool","name":"outUsd","type":"bool"},{"internalType":"address","name":"strikeToken","type":"address"},{"internalType":"uint256","name":"lpIndex","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"uint256","name":"swapperId","type":"uint256"}],"name":"fillLpPosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"strikeToken","type":"address"}],"name":"getAllLpPositions","outputs":[{"components":[{"internalType":"uint256","name":"lpId","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"strike","type":"uint256"},{"internalType":"uint256","name":"usdLiquidity","type":"uint256"},{"internalType":"uint256","name":"underlyingLiquidity","type":"uint256"},{"internalType":"uint256","name":"usdLiquidityUsed","type":"uint256"},{"internalType":"uint256","name":"underlyingLiquidityUsed","type":"uint256"},{"internalType":"uint256","name":"discount","type":"uint256"},{"internalType":"uint256","name":"purchased","type":"uint256"},{"internalType":"uint256","name":"maxTokensToFill","type":"uint256"},{"internalType":"address","name":"buyer","type":"address"},{"internalType":"bool","name":"killed","type":"bool"}],"internalType":"struct SsovLp.LpPosition[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getOptionTokenInfo","outputs":[{"internalType":"address","name":"ssov","type":"address"},{"internalType":"uint256","name":"strike","type":"uint256"},{"internalType":"uint256","name":"usdLiquidity","type":"uint256"},{"internalType":"uint256","name":"underlyingLiquidity","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ssov","type":"address"},{"internalType":"uint256","name":"usdPremium","type":"uint256"}],"name":"getPremiumInUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getSsovCollateralPrecision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getSsovEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getSsovEpochExpiries","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"getSsovEpochStrikes","outputs":[{"internalType":"uint256[]","name":"strikes","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getSsovEpochs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"getSsovExpiry","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"strike","type":"uint256"}],"name":"getSsovOptionToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"getSsovOptionTokens","outputs":[{"internalType":"address[]","name":"tokens","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getSsovUnderlyingPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"strike","type":"uint256"}],"name":"getSsovVolatility","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"isPut","type":"bool"}],"name":"getTokenVaultRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"strikeToken","type":"address"}],"name":"getUserLpPositions","outputs":[{"components":[{"internalType":"uint256","name":"lpId","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"strike","type":"uint256"},{"internalType":"uint256","name":"usdLiquidity","type":"uint256"},{"internalType":"uint256","name":"underlyingLiquidity","type":"uint256"},{"internalType":"uint256","name":"usdLiquidityUsed","type":"uint256"},{"internalType":"uint256","name":"underlyingLiquidityUsed","type":"uint256"},{"internalType":"uint256","name":"discount","type":"uint256"},{"internalType":"uint256","name":"purchased","type":"uint256"},{"internalType":"uint256","name":"maxTokensToFill","type":"uint256"},{"internalType":"address","name":"buyer","type":"address"},{"internalType":"bool","name":"killed","type":"bool"}],"internalType":"struct SsovLp.LpPosition[]","name":"positions","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"hasEpochExpired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_underlyingSymbol","type":"string"},{"components":[{"internalType":"address","name":"usd","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"optionPricing","type":"address"},{"internalType":"address","name":"assetSwapper","type":"address"}],"internalType":"struct BaseSsovLp.Addresses","name":"_addresses","type":"tuple"},{"internalType":"address","name":"_sender","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isContract","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"strikeToken","type":"address"},{"internalType":"uint256","name":"lpIndex","type":"uint256"}],"name":"killLpPosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"isUsd","type":"bool"},{"internalType":"bool","name":"isPut","type":"bool"},{"internalType":"uint256[]","name":"strikes","type":"uint256[]"},{"internalType":"uint256[]","name":"liquidity","type":"uint256[]"},{"internalType":"uint256[]","name":"discount","type":"uint256[]"},{"internalType":"uint256[]","name":"maxTokensToFill","type":"uint256[]"},{"internalType":"address","name":"to","type":"address"}],"name":"multiAddToLp","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isPut","type":"bool"},{"internalType":"bool","name":"outUsd","type":"bool"},{"internalType":"address","name":"strikeToken","type":"address"},{"internalType":"uint256[]","name":"lpIndices","type":"uint256[]"},{"internalType":"uint256[]","name":"amount","type":"uint256[]"},{"internalType":"uint256[]","name":"minAmountOuts","type":"uint256[]"},{"internalType":"uint256","name":"swapperId","type":"uint256"}],"name":"multiFillLpPosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"strikeToken","type":"address"},{"internalType":"uint256[]","name":"lpIndices","type":"uint256[]"}],"name":"multiKillLpPosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"bool","name":"isPut","type":"bool"}],"name":"registerSsovForToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_contract","type":"address"}],"name":"removeFromContractWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"usd","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"address","name":"optionPricing","type":"address"},{"internalType":"address","name":"assetSwapper","type":"address"}],"internalType":"struct BaseSsovLp.Addresses","name":"_addresses","type":"tuple"}],"name":"setAddresses","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlyingSymbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"isPut","type":"bool"}],"name":"unregisterSsovForToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"ssov","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"updateSsovEpoch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistedContracts","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]

Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.