ℹ️OptionsRepo

期权库

Name

OptionsRepo

Dependent Contract

API

Source Code:

OptionsRepo
// SPDX-License-Identifier: UNLICENSED

/* *
 * Copyright (c) 2021-2023 LI LI @ JINGTIAN & GONGCHENG.
 *
 * This WORK is licensed under ComBoox SoftWare License 1.0, a copy of which 
 * can be obtained at:
 *         [https://github.com/paul-lee-attorney/comboox]
 *
 * THIS WORK IS PROVIDED ON AN "AS IS" BASIS, WITHOUT 
 * WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. IN NO 
 * EVENT SHALL ANY CONTRIBUTOR BE LIABLE TO YOU FOR ANY DAMAGES.
 *
 * YOU ARE PROHIBITED FROM DEPLOYING THE SMART CONTRACTS OF THIS WORK, IN WHOLE 
 * OR IN PART, FOR WHATEVER PURPOSE, ON ANY BLOCKCHAIN NETWORK THAT HAS ONE OR 
 * MORE NODES THAT ARE OUT OF YOUR CONTROL.
 * */

pragma solidity ^0.8.8;

import "./EnumerableSet.sol";
import "./Checkpoints.sol";
import "./CondsRepo.sol";
import "./SharesRepo.sol";
import "./SwapsRepo.sol";

import "../comps/books/ros/IRegisterOfShares.sol";

library OptionsRepo {
    using EnumerableSet for EnumerableSet.UintSet;
    using Checkpoints for Checkpoints.History;
    using CondsRepo for CondsRepo.Cond;
    using CondsRepo for bytes32;
    using SwapsRepo for SwapsRepo.Repo;

    enum TypeOfOpt {
        CallPrice,          
        PutPrice,           
        CallRoe,            
        PutRoe,             
        CallPriceWithCnds,  
        PutPriceWithCnds,   
        CallRoeWithCnds,    
        PutRoeWithCnds     
    }

    enum StateOfOpt {
        Pending,    
        Issued,         
        Executed,
        Closed
    }

    struct Head {
        uint32 seqOfOpt;
        uint8 typeOfOpt;
        uint16 classOfShare;
        uint32 rate;            
        uint48 issueDate;
        uint48 triggerDate;     
        uint16 execDays;         
        uint16 closingDays;
        uint40 obligor;      
    }

    struct Body {
        uint48 closingDeadline;
        uint40 rightholder;
        uint64 paid;
        uint64 par;
        uint8 state;
        uint16 para;
        uint16 argu;
    }

    struct Option {
        Head head;
        CondsRepo.Cond cond;
        Body body;
    }

    struct Record {
        EnumerableSet.UintSet obligors;
        SwapsRepo.Repo swaps;
        Checkpoints.History oracles;
    }

    struct Repo {
        mapping(uint256 => Option) options;
        mapping(uint256 => Record) records;
        EnumerableSet.UintSet seqList;
    }

    // ###############
    // ##  Modifier ##
    // ###############


    modifier optExist(Repo storage repo, uint seqOfOpt) {
        require (isOption(repo, seqOfOpt), "OR.optExist: not");
        _;
    }

    modifier onlyRightholder(Repo storage repo, uint seqOfOpt, uint caller) {
        require (isRightholder(repo, seqOfOpt, caller),
            "OR.mf.onlyRightholder: not");
        _;
    }

    // ###############
    // ## Write I/O ##
    // ###############

    // ==== cofify / parser ====

    function snParser(bytes32 sn) public pure returns (Head memory head) {
        uint _sn = uint(sn);

        head = Head({
            seqOfOpt: uint32(_sn >> 224),
            typeOfOpt: uint8(_sn >> 216),
            classOfShare: uint16(_sn >> 200),
            rate: uint32(_sn >> 168),
            issueDate: uint48(_sn >> 120),
            triggerDate: uint48(_sn >> 72),
            execDays: uint16(_sn >> 56),
            closingDays: uint16(_sn >> 40),
            obligor: uint40(_sn)
        });
    }

    function codifyHead(Head memory head) public pure returns (bytes32 sn) {
        bytes memory _sn = abi.encodePacked(
                            head.seqOfOpt,
                            head.typeOfOpt,
                            head.classOfShare,
                            head.rate,
                            head.issueDate,
                            head.triggerDate,
                            head.execDays,
                            head.closingDays,
                            head.obligor);
        assembly {
            sn := mload(add(_sn, 0x20))
        }
    }

    // ==== Option ====

    function createOption(
        Repo storage repo,
        bytes32 snOfOpt,
        bytes32 snOfCond,
        uint rightholder,
        uint paid,
        uint par
    ) public returns (Head memory head) 
    {
        Option memory opt;

        opt.head = snParser(snOfOpt);
        opt.cond = snOfCond.snParser();

        opt.body.closingDeadline = opt.head.triggerDate + (uint48(opt.head.execDays) + uint48(opt.head.closingDays)) * 86400;
        opt.body.rightholder = uint40(rightholder);
        opt.body.paid = uint64(paid);
        opt.body.par = uint64(par);

        // head = issueOption(repo, opt);
        head = regOption(repo, opt);
    }

    function issueOption(
        Repo storage repo,
        Option memory opt
    ) public returns(uint) {
        Option storage o = repo.options[opt.head.seqOfOpt];

        o.head.issueDate = uint48(block.timestamp);
        o.body.state = uint8(StateOfOpt.Issued);

        return o.head.issueDate;
    }

    function regOption(
        Repo storage repo,
        Option memory opt
    ) public returns(Head memory) {

        require(opt.head.rate > 0, "OR.IO: ZERO rate");

        require(opt.head.triggerDate > block.timestamp, "OR.IO: triggerDate not future");
        require(opt.head.execDays > 0, "OR.IO: ZERO execDays");
        require(opt.head.closingDays > 0, "OR.IO: ZERO closingDays");
        require(opt.head.obligor > 0, "OR.IO: ZERO obligor");

        require(opt.body.rightholder > 0, "OR.IO: ZERO rightholder");
        require(opt.body.paid > 0, "OR.IO: ZERO paid");
        require(opt.body.par >= opt.body.paid, "OR.IO: INSUFFICIENT par");

        opt.head.seqOfOpt = _increaseCounter(repo);

        repo.seqList.add(opt.head.seqOfOpt);

        repo.options[opt.head.seqOfOpt] = opt;
        repo.records[opt.head.seqOfOpt].obligors.add(opt.head.obligor);

        return opt.head;        
    }

    function removeOption(
        Repo storage repo,
        uint seqOfOpt
    ) public returns (bool flag) {

        require (
            repo.options[seqOfOpt].body.state == uint8(StateOfOpt.Pending),
            "OR.removeOption: wrong state" 
        );

        if (repo.seqList.remove(seqOfOpt)) {
            delete repo.options[seqOfOpt];
            flag = true;
        }
    }

    // ==== Record ====

    function addObligorIntoOption(Repo storage repo, uint seqOfOpt, uint256 obligor)
        public returns(bool)
    {
        require (obligor > 0, "OR.AOIO: zero obligor");        
        return repo.records[seqOfOpt].obligors.add(uint40(obligor));
    }

    function removeObligorFromOption(Repo storage repo, uint seqOfOpt, uint256 obligor)
        public returns(bool)
    {
        require (obligor > 0, "OR.ROFO: zero obligor");        
        return repo.records[seqOfOpt].obligors.remove(obligor);
    }

    function addObligorsIntoOption(Repo storage repo, uint seqOfOpt, uint256[] memory obligors)
        public
    {
        Record storage rcd = repo.records[seqOfOpt];
        uint256 len = obligors.length;

        while (len > 0) {
            rcd.obligors.add(uint40(obligors[len-1]));
            len--;
        }
    }

    // ==== ExecOption ====

    function updateOracle(
        Repo storage repo,
        uint256 seqOfOpt,
        uint d1,
        uint d2,
        uint d3
    ) public optExist(repo, seqOfOpt) {
        repo.records[seqOfOpt].oracles.push(100, d1, d2, d3);
    }

    function execOption(
        Repo storage repo,
        uint256 seqOfOpt,
        uint caller
    ) public onlyRightholder(repo, seqOfOpt, caller) {
        Option storage opt = repo.options[seqOfOpt]; 
        Record storage rcd = repo.records[seqOfOpt];

        require(
            opt.body.state == uint8(StateOfOpt.Issued),
            "OR.EO: wrong state of Opt"
        );
        require(
            block.timestamp >= opt.head.triggerDate,
            "OR.EO: NOT reached TriggerDate"
        );

        require(
            block.timestamp < opt.head.triggerDate + uint48(opt.head.execDays) * 86400,
            "OR.EO: NOT in exercise period"
        );

        if (opt.head.typeOfOpt > uint8(TypeOfOpt.PutRoe)) {
            Checkpoints.Checkpoint memory cp = rcd.oracles.latest();

            if (opt.cond.logicOpr == uint8(CondsRepo.LogOps.ZeroPoint)) { 
                require(opt.cond.checkSoleCond(cp.paid), 
                    "OR.EO: conds not satisfied");
            } else if (opt.cond.logicOpr <= uint8(CondsRepo.LogOps.NotEqual)) {
                require(opt.cond.checkCondsOfTwo(cp.paid, cp.par), 
                    "OR.EO: conds not satisfied");                
            } else if (opt.cond.logicOpr <= uint8(CondsRepo.LogOps.NeOr)) {
                require(opt.cond.checkCondsOfThree(cp.paid, cp.par, cp.cleanPaid), 
                    "OR.EO: conds not satisfied");   
            } else revert("OR.EO: logical operator overflow");
        }

        opt.body.closingDeadline = uint48(block.timestamp) + uint48(opt.head.closingDays) * 86400;
        opt.body.state = uint8(StateOfOpt.Executed);
    }

    // ==== Brief ====

    function createSwap(
        Repo storage repo,
        uint256 seqOfOpt,
        uint seqOfTarget,
        uint paidOfTarget,
        uint seqOfPledge,
        uint caller,
        IRegisterOfShares _ros
    ) public onlyRightholder(repo, seqOfOpt, caller) returns(SwapsRepo.Swap memory swap) {

        Option storage opt = repo.options[seqOfOpt];

        require(opt.body.state == uint8(StateOfOpt.Executed), "OR.createSwap: wrong state");
        require(block.timestamp < opt.body.closingDeadline, "OR.createSwap: option expired");

        swap.seqOfTarget = uint32(seqOfTarget);
        swap.paidOfTarget = uint64(paidOfTarget);
        swap.seqOfPledge = uint32(seqOfPledge);
        swap.state = uint8(SwapsRepo.StateOfSwap.Issued);

        Record storage rcd = repo.records[opt.head.seqOfOpt];

        SharesRepo.Head memory headOfTarget = _ros.getShare(swap.seqOfTarget).head;
        SharesRepo.Head memory headOfPledge = _ros.getShare(swap.seqOfPledge).head;

        require(opt.head.classOfShare == headOfTarget.class, 
            "OR.createSwap: wrong target class");

        require (opt.body.paid >= rcd.swaps.sumPaidOfTarget() + swap.paidOfTarget, 
            "OR.PS: paidOfTarget overflow");

        if (opt.head.typeOfOpt % 2 == 1) { // Put Option

            require(opt.body.rightholder == headOfTarget.shareholder, 
                "OR.createSwap: rightholder not targetholder");
            require(rcd.obligors.contains(headOfPledge.shareholder), 
                "OR.createSwap: pledge shareholder not obligor");

            swap.isPutOpt = true;

        } else { // Call Opt
            require(opt.body.rightholder == headOfPledge.shareholder, 
                "OR.createSwap: pledge shareholder not rightholder");

            require(rcd.obligors.contains(headOfTarget.shareholder), 
                "OR.createSwap: target shareholder not obligor");
        }

        if (opt.head.typeOfOpt % 4 < 2) 
            swap.priceOfDeal = opt.head.rate;
        else {
            uint32 ds = uint32(((block.timestamp - headOfTarget.issueDate) + 43200) / 86400);
            swap.priceOfDeal = headOfTarget.priceOfPaid * (opt.head.rate * ds + 3650000) / 3650000;  
        }

        if (opt.head.typeOfOpt % 2 == 1) {            
            swap.paidOfPledge = (swap.priceOfDeal - headOfTarget.priceOfPaid) * 
                swap.paidOfTarget / headOfPledge.priceOfPaid;
        }

        return rcd.swaps.regSwap(swap);

    }

    function payOffSwap(
        Repo storage repo,
        uint seqOfOpt,
        uint seqOfSwap,
        uint msgValue,
        uint centPrice
    ) public returns (SwapsRepo.Swap memory ) {

        Option storage opt = repo.options[seqOfOpt];

        require(opt.body.state == uint8(StateOfOpt.Executed), 
            "OR.payOffSwap: wrong state of Opt");
        require(block.timestamp < opt.body.closingDeadline, 
            "OR.payOffSwap: option expired");

        return repo.records[seqOfOpt].swaps.payOffSwap(seqOfSwap, msgValue, centPrice);
    }

    function terminateSwap(
        Repo storage repo,
        uint seqOfOpt,
        uint seqOfSwap
    ) public returns (SwapsRepo.Swap memory){

        Option storage opt = repo.options[seqOfOpt];

        require(opt.body.state == uint8(StateOfOpt.Executed), 
            "OR.terminateSwap: wrong state of Opt");
        require(block.timestamp >= opt.body.closingDeadline, 
            "OR.terminateSwap: still in closing period");

        return repo.records[seqOfOpt].swaps.terminateSwap(seqOfSwap);
    }

    // ==== Counter ====

    function _increaseCounter(Repo storage repo) private returns(uint32 seqOfOpt) {
        repo.options[0].head.seqOfOpt++;
        seqOfOpt = repo.options[0].head.seqOfOpt;
    } 

    // ################
    // ##  查询接口   ##
    // ################

    // ==== Repo ====

    function counterOfOptions(Repo storage repo)
        public view returns (uint32)
    {
        return repo.options[0].head.seqOfOpt;
    }

    function qtyOfOptions(Repo storage repo)
        public view returns (uint)
    {
        return repo.seqList.length();
    }

    function isOption(Repo storage repo, uint256 seqOfOpt) 
        public view returns (bool) 
    {
        return repo.seqList.contains(seqOfOpt);
    }

    function getOption(Repo storage repo, uint256 seqOfOpt) public view 
        optExist(repo, seqOfOpt) returns (OptionsRepo.Option memory option)   
    {
        option = repo.options[seqOfOpt];
    }

    function getAllOptions(Repo storage repo) 
        public view returns (Option[] memory) 
    {
        uint[] memory ls = repo.seqList.values();
        uint256 len = ls.length;
        Option[] memory output = new Option[](len);
        
        while (len > 0) {
            output[len-1] = repo.options[ls[len-1]];
            len--;
        }
        return output;
    }

    function isRightholder(Repo storage repo, uint256 seqOfOpt, uint256 acct) 
        public view optExist(repo, seqOfOpt) returns (bool)
    {
        return repo.options[seqOfOpt].body.rightholder == acct;
    }

    function isObligor(Repo storage repo, uint256 seqOfOpt, uint256 acct) public 
        view optExist(repo, seqOfOpt) returns (bool) 
    {
        return repo.records[seqOfOpt].obligors.contains(acct);
    }

    function getObligorsOfOption(Repo storage repo, uint256 seqOfOpt) public 
        view optExist(repo, seqOfOpt) returns (uint256[] memory)
    {
        return repo.records[seqOfOpt].obligors.values();
    }

    function getSeqList(Repo storage repo) public view returns(uint[] memory) {
        return repo.seqList.values();
    }

    // ==== Order ====

    function counterOfSwaps(Repo storage repo, uint256 seqOfOpt)
        public view returns (uint16)
    {
        return repo.records[seqOfOpt].swaps.counterOfSwaps();
    }

    function sumPaidOfTarget(Repo storage repo, uint256 seqOfOpt)
        public view returns (uint64)
    {
        return repo.records[seqOfOpt].swaps.sumPaidOfTarget();
    }

    function isSwap(Repo storage repo, uint256 seqOfOpt, uint256 seqOfOrder)
        public view returns (bool)
    {
        return repo.records[seqOfOpt].swaps.isSwap(seqOfOrder);
    }

    function getSwap(Repo storage repo, uint256 seqOfOpt, uint256 seqOfSwap)
        public view returns (SwapsRepo.Swap memory)
    {
        return repo.records[seqOfOpt].swaps.getSwap(seqOfSwap);
    }

    function getAllSwapsOfOption(Repo storage repo, uint256 seqOfOpt)
        public view returns (SwapsRepo.Swap[] memory )
    {
        return repo.records[seqOfOpt].swaps.getAllSwaps();
    }

    function allSwapsClosed(Repo storage repo, uint256 seqOfOpt)
        public view returns (bool)
    {
        return repo.records[seqOfOpt].swaps.allSwapsClosed();
    }

    // ==== Oracles ====

    function getOracleAtDate(
        Repo storage repo, 
        uint256 seqOfOpt, 
        uint date
    ) public view optExist(repo, seqOfOpt) 
        returns (Checkpoints.Checkpoint memory)
    {
        return repo.records[seqOfOpt].oracles.getAtDate(date);
    }

    function getLatestOracle(Repo storage repo, uint256 seqOfOpt) 
        public view optExist(repo, seqOfOpt) 
        returns(Checkpoints.Checkpoint memory)
    {
        return repo.records[seqOfOpt].oracles.latest();
    }

    function getAllOraclesOfOption(Repo storage repo, uint256 seqOfOpt)
        public view optExist(repo, seqOfOpt)
        returns (Checkpoints.Checkpoint[] memory) 
    {
        return repo.records[seqOfOpt].oracles.pointsOfHistory();
    }

    function checkValueOfSwap(
        Repo storage repo, 
        uint seqOfOpt, 
        uint seqOfSwap, 
        uint centPrice
    ) public view optExist(repo, seqOfOpt) returns (uint) {
        return repo.records[seqOfOpt].swaps.checkValueOfSwap(seqOfSwap, centPrice);
    }

}