👮SHAKeeper

Keeper of Shareholders Agreement

API

Source Code:

SHAKeeper
// 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 "../common/access/AccessControl.sol";

import "./ISHAKeeper.sol";

contract SHAKeeper is ISHAKeeper, AccessControl {
    using RulesParser for bytes32;

    // ======== TagAlong & DragAlong ========

    function execAlongRight(
        address ia,
        uint256 seqOfDeal,
        bool dragAlong,
        uint256 seqOfShare,
        uint paid,
        uint par,
        uint256 caller,
        bytes32 sigHash
    ) external onlyDK {
        
        IRegisterOfAgreements _roa = _gk.getROA();
        IRegisterOfShares _ros = _gk.getROS();

        DealsRepo.Deal memory deal = IInvestmentAgreement(ia).getDeal(seqOfDeal);
        SharesRepo.Share memory share = _ros.getShare(seqOfShare);
        uint16 subjectVW = _ros.getShare(deal.head.seqOfShare).head.votingWeight;

        require(deal.body.state == uint8(DealsRepo.StateOfDeal.Locked), 
            "SHAK.execAlongs: state not Locked");

        require(!_roa.isFRClaimer(ia, caller), 
            "SHAK.execAlongs: caller is frClaimer");

        require(!_roa.isFRClaimer(ia, share.head.shareholder), 
            "SHAK.execAlongs: shareholder is frClaimer");

        _roa.createMockOfIA(ia);

        _checkAlongDeal(dragAlong, ia, deal, share, caller, paid, par, subjectVW);

        _roa.execAlongRight(ia, dragAlong, seqOfDeal, seqOfShare, paid, par, caller, sigHash);
    }

    function _checkAlongDeal(
        bool dragAlong,
        address ia,
        DealsRepo.Deal memory deal,
        SharesRepo.Share memory share,
        uint256 caller,
        uint paid,
        uint par,
        uint16 subjectVW
    ) private view {
        

        IAlongs _al = dragAlong
            ? IAlongs(_gk.getSHA().
                getTerm(uint8(IShareholdersAgreement.TitleOfTerm.DragAlong)))
            : IAlongs(_gk.getSHA().
                getTerm(uint8(IShareholdersAgreement.TitleOfTerm.TagAlong)));

        require(
            _al.isFollower(deal.head.seller, share.head.shareholder),
            "SHAK.checkAlongs: NOT linked"
        );

        require(_al.isTriggered(ia, deal), "SHAK.checkAlongs: not triggered");

        if (dragAlong) {
            require(caller == deal.head.seller, "SHAK.checkAlongs: not drager");
        } else {
            require(caller == share.head.shareholder,
                "SHAK.checkAlongs: not follower");
            require(!ISigPage(ia).isBuyer(true, caller),
                "SHAK.checkAlongs: caller is Buyer");
        }

        if(_al.getLinkRule(deal.head.seller).proRata) {
            IRegisterOfMembers _rom = _gk.getROM();
            
            if (_rom.basedOnPar())
                require ( par <= 
                deal.body.par * subjectVW / 100 * share.body.par / _rom.votesOfGroup(deal.head.seller), 
                "SHAKeeper.checkAlong: par overflow");
            else require ( paid <=
                deal.body.paid * subjectVW / 100 * share.body.paid / _rom.votesOfGroup(deal.head.seller),
                "SHAKeeper.checkAlong: paid overflow");            
        }
    }

    function acceptAlongDeal(
        address ia,
        uint256 seqOfDeal,
        uint256 caller,
        bytes32 sigHash
    ) external onlyDK {
        
        IRegisterOfAgreements _roa = _gk.getROA();
        IInvestmentAgreement _ia = IInvestmentAgreement(ia);

        DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);

        require(caller == deal.body.buyer, "SHAK.AAD: not buyer");

        DTClaims.Claim[] memory claims = _roa.acceptAlongClaims(ia, seqOfDeal);

        uint len = claims.length;
        while(len > 0) {
            DTClaims.Claim memory claim = claims[len - 1];

            SharesRepo.Share memory share = 
                _gk.getROS().getShare(claim.seqOfShare);

            _createAlongDeal(_ia, claim, deal, share);

            _ia.regSig(deal.head.seller, claim.sigDate, claim.sigHash);
            _ia.regSig(caller, uint48(block.timestamp), sigHash);

            len--;
        }

        // _ia.signDoc(false, caller, sigHash);

    }

    function _createAlongDeal(
        IInvestmentAgreement _ia,
        DTClaims.Claim memory claim,
        DealsRepo.Deal memory deal,
        SharesRepo.Share memory share
    ) private {
        deal.head = DealsRepo.Head({
            typeOfDeal: claim.typeOfClaim == 0 
                ? uint8(DealsRepo.TypeOfDeal.DragAlong) 
                : uint8(DealsRepo.TypeOfDeal.TagAlong),
            seqOfDeal: 0,
            preSeq: deal.head.seqOfDeal,
            classOfShare: share.head.class,
            seqOfShare: share.head.seqOfShare,
            seller: share.head.shareholder,
            priceOfPaid: deal.head.priceOfPaid,
            priceOfPar: deal.head.priceOfPar,
            closingDeadline: deal.head.closingDeadline,
            votingWeight: share.head.votingWeight
        });

        deal.body = DealsRepo.Body({
            buyer: deal.body.buyer,
            groupOfBuyer: deal.body.groupOfBuyer,
            paid: claim.paid,
            par: claim.par,
            state: uint8(DealsRepo.StateOfDeal.Locked),
            para: 0,
            argu: 0,
            flag: false
        });

        _ia.regDeal(deal);

        _gk.getROS().decreaseCleanPaid(share.head.seqOfShare, claim.paid);
    }

    // ======== AntiDilution ========

    function execAntiDilution(
        address ia,
        uint256 seqOfDeal,
        uint256 seqOfShare,
        uint256 caller,
        bytes32 sigHash
    ) external onlyDK {
        
        IRegisterOfShares _ros = _gk.getROS();
        IInvestmentAgreement _ia = IInvestmentAgreement(ia);

        SharesRepo.Share memory tShare = _ros.getShare(seqOfShare);

        require(caller == tShare.head.shareholder, "SHAK.execAD: not shareholder");
        require(!_ia.isInitSigner(caller), "SHAK.execAD: is InitSigner");

        require(_gk.getROA().getHeadOfFile(ia).state == 
            uint8(FilesRepo.StateOfFile.Circulated), "SHAK.execAD: wrong file state");

        _ia.requestPriceDiff(seqOfDeal, seqOfShare);

        IAntiDilution _ad = IAntiDilution(_gk.getSHA().getTerm(
            uint8(IShareholdersAgreement.TitleOfTerm.AntiDilution)
        ));

        uint64 giftPaid = _ad.getGiftPaid(ia, seqOfDeal, tShare.head.seqOfShare);
        uint256[] memory obligors = _ad.getObligorsOfAD(tShare.head.class);

        DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);
        
        deal.head.typeOfDeal = uint8(DealsRepo.TypeOfDeal.FreeGift);
        deal.head.preSeq = deal.head.seqOfDeal;
        deal.head.seqOfDeal = 0;
        // deal.head.priceOfPaid = 0;
        deal.head.priceOfPar = tShare.head.seqOfShare;

        deal.body.buyer = tShare.head.shareholder;
        deal.body.state = uint8(DealsRepo.StateOfDeal.Locked);

        _deductShares(obligors, _gk, _ia, deal, giftPaid, _ros, sigHash);

    }

    function _deductShares(
        uint[] memory obligors,
        IGeneralKeeper _gk,
        IInvestmentAgreement _ia,
        DealsRepo.Deal memory deal,
        uint64 giftPaid,
        IRegisterOfShares _ros,
        bytes32 sigHash
    ) private {

        IRegisterOfMembers _rom = _gk.getROM();

        deal.body.groupOfBuyer = _rom.groupRep(deal.body.buyer);

        uint i = obligors.length;

        while (i > 0) {
            
            uint[] memory sharesInHand = _rom.sharesInHand(obligors[i - 1]);

            uint j = sharesInHand.length;

            while (j > 0) {

                giftPaid = _createGift(
                    _ia,
                    deal,
                    sharesInHand[j - 1],
                    giftPaid,
                    _ros,
                    sigHash
                );

                if (giftPaid == 0) break;
                j--;
            }

            if (giftPaid == 0) break;
            i--;
        }
    }


    function _createGift(
        IInvestmentAgreement _ia,
        DealsRepo.Deal memory deal,
        uint256 seqOfShare,
        uint64 giftPaid,
        IRegisterOfShares _ros,
        bytes32 sigHash
    ) private returns (uint64 result) {        
        SharesRepo.Share memory cShare = _ros.getShare(seqOfShare);

        uint64 lockAmount;

        if (cShare.body.cleanPaid > 0) {

            lockAmount = (cShare.body.cleanPaid < giftPaid) ? cShare.body.cleanPaid : giftPaid;

            deal.head.classOfShare = cShare.head.class;
            deal.head.seqOfShare = cShare.head.seqOfShare;
            deal.head.seller = cShare.head.shareholder;

            deal.body.paid = lockAmount;
            deal.body.par = lockAmount;
            
            _ia.regDeal(deal);
            _ia.regSig(deal.head.seller, uint48(block.timestamp), bytes32(uint(deal.head.seqOfShare)));
            _ia.regSig(deal.body.buyer, uint48(block.timestamp), sigHash);

            _ros.decreaseCleanPaid(cShare.head.seqOfShare, lockAmount);
        }
        result = giftPaid - lockAmount;
    }

    function takeGiftShares(
        address ia,
        uint256 seqOfDeal,
        uint caller
    ) external onlyDK {
        
        IRegisterOfShares _ros = _gk.getROS();
        IInvestmentAgreement _ia = IInvestmentAgreement(ia);

        DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);

        require(caller == deal.body.buyer, "caller is not buyer");

        if (_ia.takeGift(seqOfDeal))
            _gk.getROA().setStateOfFile(ia, uint8(FilesRepo.StateOfFile.Closed));

        _ros.increaseCleanPaid(deal.head.seqOfShare, deal.body.paid);
        _ros.transferShare(
            deal.head.seqOfShare, 
            deal.body.paid, 
            deal.body.par, 
            deal.body.buyer, 
            deal.head.priceOfPaid, 
            deal.head.priceOfPaid
        );

        SharesRepo.Share memory tShare = _ros.getShare(deal.head.priceOfPar);
        if (tShare.head.priceOfPaid > deal.head.priceOfPaid)
            _ros.updatePriceOfPaid(tShare.head.seqOfShare, deal.head.priceOfPaid);
    }

    // ======== FirstRefusal ========

    function execFirstRefusal(
        uint256 seqOfFRRule,
        uint256 seqOfRightholder,
        address ia,
        uint256 seqOfDeal,
        uint256 caller,
        bytes32 sigHash
    ) external onlyDK {
        
        IInvestmentAgreement _ia = IInvestmentAgreement(ia);

        require(!_ia.isSeller(true, caller), 
            "SHAK.EFR: frClaimer is seller");

        DealsRepo.Head memory headOfDeal = 
            _ia.getDeal(seqOfDeal).head;
        
        RulesParser.FirstRefusalRule memory rule = 
            _gk.getSHA().getRule(seqOfFRRule).firstRefusalRuleParser();

        require(rule.typeOfDeal == headOfDeal.typeOfDeal, 
            "SHAK.EFR: rule and deal are not same type");

        require(
            (rule.membersEqual && _gk.getROM().isMember(caller)) || 
            rule.rightholders[seqOfRightholder] == caller,
            "SHAK.EFR: caller NOT rightholder"
        );

        _gk.getROA().claimFirstRefusal(ia, seqOfDeal, caller, sigHash);

        if (_ia.getDeal(seqOfDeal).body.state != 
            uint8(DealsRepo.StateOfDeal.Terminated))
                _ia.terminateDeal(seqOfDeal); 
    }

    function computeFirstRefusal(
        address ia,
        uint256 seqOfDeal,
        uint256 caller
    ) external onlyDK {
        
        IRegisterOfMembers _rom = _gk.getROM();
        IInvestmentAgreement _ia = IInvestmentAgreement(ia);

        DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);

        (,, bytes32 sigHashOfSeller) = _ia.getSigOfParty(true, deal.head.seller);

        require(_rom.isMember(caller), "SHAKeeper.computeFR: not member");

        if (deal.head.seqOfShare != 0) {
            deal.head.typeOfDeal = uint8(DealsRepo.TypeOfDeal.FirstRefusal);
        } else  deal.head.typeOfDeal = uint8(DealsRepo.TypeOfDeal.PreEmptive);

        deal.head.preSeq = deal.head.seqOfDeal;
        deal.body.state = uint8(DealsRepo.StateOfDeal.Locked);

        FRClaims.Claim[] memory cls = 
            _gk.getROA().computeFirstRefusal(ia, seqOfDeal);

        uint256 len = cls.length;
        while (len > 0) {
            _createFRDeal(_ia, deal, cls[len-1], _rom, sigHashOfSeller);
            len--;
        }

        // if (deal.head.seqOfShare > 0)
        //     _ia.signDoc(false, deal.head.seller, sigHashOfSeller);
    }

    function _createFRDeal(
        IInvestmentAgreement _ia,
        DealsRepo.Deal memory deal,
        FRClaims.Claim memory cl,
        IRegisterOfMembers _rom,
        bytes32 sigHashOfSeller
    ) private {

        uint64 orgPaid = deal.body.paid;
        uint64 orgPar = deal.body.par;
        
        deal.body.buyer = cl.claimer;
        deal.body.groupOfBuyer = _rom.groupRep(cl.claimer);
        deal.body.paid = (orgPaid * cl.ratio) / 10000;
        deal.body.par = (orgPar * cl.ratio) / 10000;

        _ia.regDeal(deal);
        _ia.regSig(cl.claimer, cl.sigDate, cl.sigHash);

        if (deal.head.seller > 0)
            _ia.regSig(deal.head.seller, uint48(block.timestamp), sigHashOfSeller);

        deal.body.paid = orgPaid;
        deal.body.par = orgPar;
    }
}
ISHAKeeper
// 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 "../books/roc/terms/IAntiDilution.sol";
import "../books/roc/terms/IAlongs.sol";

import "../books/roc/IShareholdersAgreement.sol";

import "../books/roa/IInvestmentAgreement.sol";

import "../common/components/IFilesFolder.sol";
import "../common/components/ISigPage.sol";

import "../../lib/RulesParser.sol";
import "../../lib/SharesRepo.sol";
import "../../lib/FRClaims.sol";

interface ISHAKeeper {

    // ======== TagAlong & DragAlong ========

    function execAlongRight(
        address ia,
        uint256 seqOfDeal,
        bool dragAlong,
        uint256 seqOfShare,
        uint paid,
        uint par,
        uint256 caller,
        bytes32 sigHash
    ) external;

    function acceptAlongDeal(
        address ia,
        uint256 seqOfDeal,
        uint256 caller,
        bytes32 sigHash
    ) external;

    // ======== AntiDilution ========

    function execAntiDilution(
        address ia,
        uint256 seqOfDeal,
        uint256 seqOfShare,
        uint256 caller,
        bytes32 sigHash
    ) external;

    function takeGiftShares(
        address ia,
        uint256 seqOfDeal,
        uint caller
    ) external;

    // ======== FirstRefusal ========

    function execFirstRefusal(
        uint256 seqOfFRRule,
        uint256 seqOfRightholder,
        address ia,
        uint256 seqOfDeal,
        uint256 caller,
        bytes32 sigHash
    ) external;

    function computeFirstRefusal(
        address ia,
        uint256 seqOfDeal,
        uint256 caller
    ) external;
}