👮ROAKeeper
Keepers of Register of Agreements
Name
ROAKeeper
Dependent Contract
IROAKeeper, AccessControl, DocsRepo, RulesParser, SharesRepo, IInvestmentAgreement, IFilesFolder, ISigPage
API:
Source Code:
ROAKeeper
// 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 "./IROAKeeper.sol";
import "../common/access/AccessControl.sol";
contract ROAKeeper is IROAKeeper, AccessControl {
using RulesParser for bytes32;
// ##################
// ## Modifier ##
// ##################
modifier onlyPartyOf(address ia, uint256 caller) {
require(ISigPage(ia).isParty(caller), "BOIK.md.OPO: NOT Party");
_;
}
// #############################
// ## InvestmentAgreement ##
// #############################
function createIA(uint version, address primeKeyOfCaller, uint caller) external onlyDK {
require(_gk.getROM().isMember(caller), "not MEMBER");
bytes32 snOfDoc = bytes32((uint(uint8(IRegCenter.TypeOfDoc.IA)) << 224) +
uint224(version << 192));
DocsRepo.Doc memory doc = _rc.createDoc(
snOfDoc,
primeKeyOfCaller
);
IAccessControl(doc.body).init(
primeKeyOfCaller,
address(this),
address(_rc),
address(_gk)
);
_gk.getROA().regFile(DocsRepo.codifyHead(doc.head), doc.body);
}
// ======== Circulate IA ========
function circulateIA(
address ia,
bytes32 docUrl,
bytes32 docHash,
uint256 caller
) external onlyDK onlyPartyOf(ia, caller){
require(IAccessControl(ia).isFinalized(),
"BOIK.CIA: IA not finalized");
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
_ia.circulateDoc();
uint16 signingDays = _ia.getSigningDays();
uint16 closingDays = _ia.getClosingDays();
RulesParser.VotingRule memory vr =
_gk.getSHA().getRule(_ia.getTypeOfIA()).votingRuleParser();
_ia.setTiming(false, signingDays + vr.frExecDays + vr.dtExecDays + vr.dtConfirmDays, closingDays);
_gk.getROA().circulateFile(ia, signingDays, closingDays, vr, docUrl, docHash);
}
// ======== Sign IA ========
function signIA(
address ia,
uint256 caller,
bytes32 sigHash
) external onlyDK onlyPartyOf(ia, caller) {
IRegisterOfAgreements _roa = _gk.getROA();
require(
_roa.getHeadOfFile(ia).state == uint8(FilesRepo.StateOfFile.Circulated),
"BOIK.signIA: wrong state"
);
_lockDealsOfParty(ia, caller);
ISigPage(ia).signDoc(true, caller, sigHash);
}
function _lockDealsOfParty(address ia, uint256 caller) private {
uint[] memory list = IInvestmentAgreement(ia).getSeqList();
uint256 len = list.length;
while (len > 0) {
uint seq = list[len - 1];
len--;
DealsRepo.Deal memory deal =
IInvestmentAgreement(ia).getDeal(seq);
if (deal.head.seller == caller) {
if (IInvestmentAgreement(ia).lockDealSubject(seq)) {
_gk.getROS().decreaseCleanPaid(deal.head.seqOfShare, deal.body.paid);
}
} else if (
deal.body.buyer == caller &&
deal.head.typeOfDeal ==
uint8(DealsRepo.TypeOfDeal.CapitalIncrease)
) IInvestmentAgreement(ia).lockDealSubject(seq);
}
}
// ======== Deal Closing ========
function pushToCoffer(
address ia,
uint256 seqOfDeal,
bytes32 hashLock,
uint closingDeadline,
uint256 caller
) external onlyDK {
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
DealsRepo.Head memory head =
_ia.getDeal(seqOfDeal).head;
bool isST = (head.seqOfShare != 0);
if (isST) require(caller == head.seller, "BOIK.PTC: not seller");
else require(_gk.getROD().isDirector(caller), "BOIK.PTC: not director");
_vrAndSHACheck(_ia, seqOfDeal, isST);
_ia.clearDealCP(seqOfDeal, hashLock, closingDeadline);
}
function _vrAndSHACheck(IInvestmentAgreement _ia, uint256 seqOfDeal, bool isST) private view {
IMeetingMinutes _bmm = _gk.getBMM();
IMeetingMinutes _gmm = _gk.getGMM();
IRegisterOfAgreements _roa = _gk.getROA();
require(
_roa.getHeadOfFile(address(_ia)).state == uint8(FilesRepo.StateOfFile.Approved),
"BOAK.vrAndSHACheck: wrong state"
);
uint256 typeOfIA = _ia.getTypeOfIA();
IShareholdersAgreement _sha = _gk.getSHA();
RulesParser.VotingRule memory vr =
_sha.getRule(typeOfIA).votingRuleParser();
uint seqOfMotion = _roa.getHeadOfFile(address(_ia)).seqOfMotion;
if (vr.amountRatio > 0 || vr.headRatio > 0) {
if (vr.authority == 1)
require(_gmm.isPassed(seqOfMotion),
"BOIK.vrCheck: rejected by GM");
else if (vr.authority == 2)
require(_bmm.isPassed(seqOfMotion),
"BOIK.vrCheck: rejected by Board");
else if (vr.authority == 3)
require(_gmm.isPassed(seqOfMotion) &&
_bmm.isPassed(seqOfMotion),
"BOIK.vrCheck: rejected by GM or Board");
else revert("BOIK.vrCheck: authority overflow");
}
if (isST && _sha.hasTitle(uint8(IShareholdersAgreement.TitleOfTerm.LockUp))) {
address lu = _sha.getTerm(uint8(IShareholdersAgreement.TitleOfTerm.LockUp));
require(
ILockUp(lu).isExempted(address(_ia), _ia.getDeal(seqOfDeal)),
"ROAKeeper.lockUpCheck: not exempted");
}
}
function closeDeal(
address ia,
uint256 seqOfDeal,
string memory hashKey
) external onlyDK {
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);
if (_ia.closeDeal(deal.head.seqOfDeal, hashKey))
_gk.getROA().execFile(ia);
if (deal.head.seqOfShare > 0)
_shareTransfer(_ia, deal.head.seqOfDeal);
else _issueNewShare(_ia, deal.head.seqOfDeal);
}
function _shareTransfer(IInvestmentAgreement _ia, uint256 seqOfDeal) private {
IRegisterOfShares _ros = _gk.getROS();
IRegisterOfMembers _rom = _gk.getROM();
DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);
_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.priceOfPar);
if (deal.body.buyer != deal.body.groupOfBuyer &&
deal.body.groupOfBuyer != _rom.groupRep(deal.body.buyer))
_rom.addMemberToGroup(deal.body.buyer, deal.body.groupOfBuyer);
}
function issueNewShare(address ia, uint256 seqOfDeal, uint caller) public onlyDK {
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
require(_gk.getROD().isDirector(caller) ||
_gk.getROM().controllor() == caller,
"ROAK.issueNewShare: not director or controllor");
_vrAndSHACheck(_ia, seqOfDeal, false);
if (_ia.directCloseDeal(seqOfDeal)) _gk.getROA().execFile(ia);
_issueNewShare(_ia, seqOfDeal);
}
function _issueNewShare(IInvestmentAgreement _ia, uint seqOfDeal) private {
IRegisterOfShares _ros = _gk.getROS();
IRegisterOfMembers _rom = _gk.getROM();
DealsRepo.Deal memory deal = _ia.getDeal(seqOfDeal);
SharesRepo.Share memory share;
share.head = SharesRepo.Head({
seqOfShare: 0,
preSeq: 0,
class: deal.head.classOfShare,
issueDate: uint48(block.timestamp),
shareholder: deal.body.buyer,
priceOfPaid: deal.head.priceOfPaid,
priceOfPar: deal.head.priceOfPar,
votingWeight: deal.head.votingWeight,
argu: 0
});
share.body = SharesRepo.Body({
payInDeadline: uint48(block.timestamp) + 43200,
paid: deal.body.paid,
par: deal.body.par,
cleanPaid: deal.body.paid,
state: 0,
para: 0
});
_ros.addShare(share);
if (deal.body.buyer != deal.body.groupOfBuyer &&
deal.body.groupOfBuyer != _rom.groupRep(deal.body.buyer))
_rom.addMemberToGroup(deal.body.buyer, deal.body.groupOfBuyer);
}
function transferTargetShare(
address ia,
uint256 seqOfDeal,
uint256 caller
) public onlyDK {
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
require(
caller == _ia.getDeal(seqOfDeal).head.seller,
"BOIK.TTS: not seller"
);
_vrAndSHACheck(_ia, seqOfDeal, true);
if (_ia.directCloseDeal(seqOfDeal))
_gk.getROA().execFile(ia);
_shareTransfer(_ia, seqOfDeal);
}
function terminateDeal(
address ia,
uint256 seqOfDeal,
uint256 caller
) external onlyDK {
IRegisterOfAgreements _roa = _gk.getROA();
DealsRepo.Deal memory deal = IInvestmentAgreement(ia).getDeal(seqOfDeal);
require(
caller == deal.head.seller,
"BOIK.TD: NOT seller"
);
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
uint8 state = _roa.getHeadOfFile(ia).state;
if ((state < uint8(FilesRepo.StateOfFile.Proposed) &&
block.timestamp >= _roa.terminateStartpoint(ia)) ||
(state == uint8(FilesRepo.StateOfFile.Rejected)) ||
(state == uint8(FilesRepo.StateOfFile.Approved) &&
block.timestamp >= _ia.getDeal(seqOfDeal).head.closingDeadline)
) {
if (_ia.terminateDeal(seqOfDeal))
_roa.terminateFile(ia);
if (_ia.releaseDealSubject(seqOfDeal))
_gk.getROS().increaseCleanPaid(deal.head.seqOfShare, deal.body.paid);
} else revert("BOIK.TD: wrong state");
}
function payOffApprovedDeal(
address ia,
uint seqOfDeal,
uint msgValue,
uint caller
) external onlyDK {
IInvestmentAgreement _ia = IInvestmentAgreement(ia);
DealsRepo.Deal memory deal =
_ia.getDeal(seqOfDeal);
_vrAndSHACheck(_ia, deal.head.seqOfDeal, deal.head.seqOfShare != 0);
uint centPrice = _gk.getCentPrice();
uint valueOfDeal = (deal.body.paid * deal.head.priceOfPaid +
(deal.body.par - deal.body.paid) * deal.head.priceOfPar) *
centPrice / 100;
require( valueOfDeal <= msgValue, "ROAK.payApprDeal: insufficient msgValue");
if (_ia.payOffApprovedDeal(seqOfDeal, msgValue, caller))
_gk.getROA().execFile(ia);
if (deal.head.seqOfShare > 0) {
_gk.saveToCoffer(deal.head.seller, valueOfDeal);
_shareTransfer(_ia, deal.head.seqOfDeal);
} else {
_issueNewShare(_ia, deal.head.seqOfDeal);
}
msgValue -= valueOfDeal;
if (msgValue > 0)
_gk.saveToCoffer(caller, msgValue);
}
}
IROAKeeper
// 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/roa/IInvestmentAgreement.sol";
import "../common/components/IFilesFolder.sol";
import "../common/components/ISigPage.sol";
import "../../lib/DocsRepo.sol";
import "../../lib/RulesParser.sol";
import "../../lib/SharesRepo.sol";
interface IROAKeeper {
// #################
// ## Write IO ##
// #################
function createIA(uint256 version, address primeKeyOfCaller, uint caller) external;
function circulateIA(
address ia,
bytes32 docUrl,
bytes32 docHash,
uint256 caller
) external;
function signIA(
address ia,
uint256 caller,
bytes32 sigHash
) external;
// ==== Deal & IA ====
function pushToCoffer(
address ia,
uint256 seqOfDeal,
bytes32 hashLock,
uint closingDeadline,
uint256 caller
) external;
function closeDeal(
address ia,
uint256 seqOfDeal,
string memory hashKey
) external;
function transferTargetShare(
address ia,
uint256 seqOfDeal,
uint256 caller
) external;
function issueNewShare(address ia, uint256 seqOfDeal, uint caller) external;
function terminateDeal(
address ia,
uint256 seqOfDeal,
uint256 caller
) external;
function payOffApprovedDeal(
address ia,
uint seqOfDeal,
uint msgValue,
uint caller
) external;
}