// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import './EscrowLoan.sol'; contract FastLoan { // consider using access control from openzepplin for Escrow part. struct LoanRequest { address payable borrower; uint256 projectID; string projectTitle; uint projectDuration; uint256 amount; } struct Loan { uint256 projectID; address payable lender; address payable borrower; uint256 amount; uint256 currentBalance; uint256 interestAmount; uint256 paymentEndTime; uint numberOfInstallments; bool isClosed; } EscrowLoan escrowContract; mapping(address => uint256) public lenderDeposits; // Current implementation: nonce as state variable counter uint256 private nonce; // Request info bytes32[] private requestIDs; mapping(bytes32 => LoanRequest) private requests; // Loan info. // one loan per user. mapping(address => bytes8) private userLoan; mapping(bytes32 => Loan) private loanDB; //private fixedInterestRate = (5 / 100); //uint private fixedInterestRate = (5 / 100); // Aggregate Lenders info address[] private allLenders; // Aggregate borrowers' info address[] private allBorrowers; // User-specific information mapping(address => uint) private debt; // All projects ever started (excludes requests) bytes8[] private allProjects; // mapping(address => bytes8[]) private userProjects; // Super validator address public superValidator; constructor (address _escrowAddress){ superValidator = msg.sender; nonce = 0; escrowContract = EscrowLoan(_escrowAddress); } event Request(address, uint256, uint); //NOTE: can add more even emitter for other functions function registerLender(address _lender) public { require(_lender != superValidator, "the current lender address is superValidator"); allLenders.push(_lender); } /** create and submit request. */ function submitLoanRequest( address payable _borrower, uint256 _amount, uint256 _projectId, string memory _projectTitle, uint _projectDuration) public { require(_borrower != superValidator, "the borower needs the different address from the superValidator"); // task: validate that borrower does not submit multiple loan request. //require(!allBorrowers[msg.sender], "borrower exit"); bytes32 _requestId = keccak256(abi.encodePacked(_borrower, _amount, nonce, address(this))); nonce += 1; //NOTE: _amount need to be in big decimal corresponding to wei uint256 _amount_ = _amount * 1 ether; requests[_requestId] = LoanRequest( _borrower, _projectId, _projectTitle, _projectDuration, _amount_); //update allBorrowers allBorrowers.push(msg.sender); requestIDs.push(_requestId); emit Request(msg.sender, _amount_, _projectId); } /** approve submitted request. Should be done by the superValidator or delegated validator **/ function approveLoanRequest(bytes32 _requestId, address payable _lender, uint256 _numberOfInstallments) public payable { require(superValidator == msg.sender, "Approval can only be set by superValidator"); //NOTE: check whether _lender is existed in the mapping for (uint256 i = 0; i < allLenders.length; i++) { if(allLenders[i] == _lender) { loanDB[_requestId].lender = _lender; loanDB[_requestId].amount = requests[_requestId].amount; loanDB[_requestId].projectID = requests[_requestId].projectID; loanDB[_requestId].borrower = requests[_requestId].borrower; uint256 loanAmount = loanDB[_requestId].amount; //TASK: need to revise w/ SafeMath //uint256 interestAmount_ = loanAmount * fixedInterestRate; uint256 interestAmount_ = loanAmount * 5 / 100 ; loanDB[_requestId].interestAmount = interestAmount_; loanDB[_requestId].currentBalance = loanAmount + interestAmount_; //NOTE: fixed duration set at 6 months uint256 fixedDuration = 15552000; loanDB[_requestId].paymentEndTime = block.timestamp + fixedDuration; loanDB[_requestId].numberOfInstallments = _numberOfInstallments; //transfer amount to borrower's account loanDB[_requestId].borrower.transfer(loanDB[_requestId].amount); //loanDB[_requestId].borrower.transfer(10 ether); loanDB[_requestId].isClosed = false; } } } /** * terminate Loan only when the loan is completely paid */ function terminateLoan(bytes32 _requestId) private returns(bool){ require(superValidator == msg.sender, "Approval can only be set by superValidator"); loanDB[_requestId].isClosed = true; return true; } /** record the payments of the loans by checking whether the payment is fully paid * * compare _installmentAmount to loan-amount/ numberOfInstallments & numberOfInstallments == 0 * if condition is passed, do update: - currentBalance - numberOfInstallments decrease by 1 - transfer eth amount to lender - implemented by Escrow */ function recordPayment(bytes32 _requestId, uint256 _installmentAmount) public payable { //check _borrower is existed address _borrower_ = loanDB[_requestId].borrower; require(msg.sender == _borrower_, "_borrower passed in is not existed and do transaction"); uint256 _numberOfInstallments = loanDB[_requestId].numberOfInstallments; //TASK: need to revise w/ SafeMath uint256 _expectedInstallmentAmount__ = loanDB[_requestId].currentBalance / _numberOfInstallments; // loanDB[_requestId].currentBalance -= _installmentAmount; loanDB[_requestId].numberOfInstallments -= 1; // Move money into escrow account. //Task: see how to pass msg.value into this lender account //escrowContract.depositPayment(loanDB[_requestId].lender, _installmentAmount); lenderDeposits[loanDB[_requestId].lender] += _installmentAmount; /** * minor fix - if _expectedInstallmentAmount__ > _installmentAmount */ (bool compareCheck) = (_expectedInstallmentAmount__ == _installmentAmount) && (_numberOfInstallments == 0); if (compareCheck) { //call Escrow's transfer? //escrowContract.completeRefund(loanDB[_requestId].lender, true); uint payment = lenderDeposits[loanDB[_requestId].lender]; lenderDeposits[loanDB[_requestId].lender] = 0; loanDB[_requestId].lender.transfer(payment); terminateLoan(_requestId); } } //getters function getRequestIDs() public view returns (bytes32[] memory){ return requestIDs; } function getBorrowers() public view returns (address[] memory) { return allBorrowers; } function getLenders() public view returns (address[] memory) { return allLenders; } // returns the borrower's debt function getDebt(bytes32 _requestId) public view returns (uint256) { return loanDB[_requestId].currentBalance; } //NOTE: just for testing while deploying contract function getProjectID(bytes32 _requestId) public view returns (uint256) { return loanDB[_requestId].projectID; } function getLoanAmount(bytes32 _requestId) public view returns (uint256) { return loanDB[_requestId].amount; } function getInterestAmount(bytes32 _requestId) public view returns (uint256) { return loanDB[_requestId].interestAmount; } }
0.7.1