NebeusLending.sol (Main contract)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract LendingProtocol is Ownable {
    using SafeERC20 for IERC20;

    // Structs
    struct Loan {
        address borrower;
        uint256 loanAmount;
        bytes32 loanAmountCurrency; // Optimized to use bytes32
        uint256 collateralAmount;
        bytes32 collateralAmountCurrency; // Optimized to use bytes32
        uint256 interestRate;
        uint256 originationFee;
        uint256 totalRepaymentAmount;
        uint256 loanAmountRateUSD;
        uint256 collateralAmountRateUSD;
        uint256 startDate;
        uint256 endDate;
        LoanStatus status;
    }

    // Enums
    enum LoanStatus { New, Active, MarginCall, Liquidation, Completed }

    // Events
    event LoanCreated(uint256 indexed loanId, address indexed borrower, uint256 loanAmount, bytes32 loanAmountCurrency, uint256 collateralAmount);
    event LoanLiquidated(uint256 indexed loanId, address indexed liquidator, uint256 clrAtLiquidation, uint256 collateralSent);
    event MarginCallIssued(uint256 indexed loanId, uint256 clr);

    // State Variables
    uint256 public loanIdCounter;
    uint256 public liquidationThreshold = 110; // Percentage
    uint256 public marginCallThreshold = 115; // Percentage
    uint256 public optimalUtilization = 80 * 1e16; // 80% (scaled by 1e18)
    uint256 public baseRate = 2 * 1e16; // 2% (scaled by 1e18)
    uint256 public slope1 = 10 * 1e16; // 10% (scaled by 1e18)
    uint256 public slope2 = 30 * 1e16; // 30% (scaled by 1e18)

    mapping(uint256 => Loan) public loans;
    mapping(bytes32 => address) public tokenAddressMapping;

    // External Contracts
    address public liquidityPool;

    constructor(address _liquidityPool) {
        liquidityPool = _liquidityPool;
    }

    // Functions

    function createLoan(
        uint256 _loanAmount,
        bytes32 _loanAmountCurrency,
        uint256 _collateralAmount,
        bytes32 _collateralAmountCurrency,
        uint256 _interestRate,
        uint256 _term
    ) external payable {
        require(msg.value == _collateralAmount, "Incorrect collateral amount sent");

        loanIdCounter++;
        uint256 loanId = loanIdCounter;

        uint256 originationFee = (_loanAmount * 1) / 100; // Example: 1% origination fee
        uint256 totalRepaymentAmount = _loanAmount + ((_loanAmount * _interestRate * _term) / (100 * 365 * 86400));

        loans[loanId] = Loan({
            borrower: msg.sender,
            loanAmount: _loanAmount,
            loanAmountCurrency: _loanAmountCurrency,
            collateralAmount: _collateralAmount,
            collateralAmountCurrency: _collateralAmountCurrency,
            interestRate: _interestRate,
            originationFee: originationFee,
            totalRepaymentAmount: totalRepaymentAmount,
            loanAmountRateUSD: 0, // To be updated with the real exchange rate
            collateralAmountRateUSD: 0, // To be updated with the real exchange rate
            startDate: block.timestamp,
            endDate: block.timestamp + _term,
            status: LoanStatus.New
        });

        emit LoanCreated(loanId, msg.sender, _loanAmount, _loanAmountCurrency, _collateralAmount);
    }

    function activateLoan(uint256 loanId, uint256 loanRateUSD, uint256 collateralRateUSD) external onlyOwner {
        Loan storage loan = loans[loanId];
        require(loan.status == LoanStatus.New, "Loan is not in New status");

        loan.loanAmountRateUSD = loanRateUSD;
        loan.collateralAmountRateUSD = collateralRateUSD;
        loan.status = LoanStatus.Active;
    }

    function repayLoan(uint256 loanId) external payable {
        Loan storage loan = loans[loanId];
        require(loan.status == LoanStatus.Active, "Loan is not active");
        require(msg.value == loan.totalRepaymentAmount, "Incorrect repayment amount");

        loan.status = LoanStatus.Completed; // Update state before transfer

        // Return collateral
        if (loan.collateralAmountCurrency == keccak256("ETH")) {
            payable(loan.borrower).transfer(loan.collateralAmount);
        } else {
            IERC20(tokenAddressMapping[loan.collateralAmountCurrency]).safeTransfer(loan.borrower, loan.collateralAmount);
        }
    }

    function liquidateLoan(uint256 loanId) external payable {
        Loan storage loan = loans[loanId];
        require(loan.status == LoanStatus.Liquidation, "Loan is not in liquidation");
        require(msg.value == loan.totalRepaymentAmount, "Incorrect repayment amount");

        uint256 clr = (loan.collateralAmount * loan.collateralAmountRateUSD * 100) / (loan.loanAmount * loan.loanAmountRateUSD);
        require(clr > 0, "Invalid CLR calculation");

        uint256 collateralToSend;
        if (clr < 110) {
            collateralToSend = loan.collateralAmount;
        } else if (clr <= 130) {
            collateralToSend = (loan.collateralAmount * 95) / 100;
        } else {
            collateralToSend = (loan.collateralAmount * 90) / 100;
        }

        loan.status = LoanStatus.Completed; // Update state before transfer

        if (loan.collateralAmountCurrency == keccak256("ETH")) {
            payable(msg.sender).transfer(collateralToSend);
        } else {
            IERC20(tokenAddressMapping[loan.collateralAmountCurrency]).safeTransfer(msg.sender, collateralToSend);
        }

        emit LoanLiquidated(loanId, msg.sender, clr, collateralToSend);
    }

    function calculateBorrowingInterestRate(uint256 utilizationRate) external view returns (uint256) {
        if (utilizationRate <= optimalUtilization) {
            return baseRate + (slope1 * utilizationRate) / 1e18;
        } else {
            uint256 excessUtilization = utilizationRate - optimalUtilization;
            return baseRate + (slope1 * optimalUtilization) / 1e18 + (slope2 * excessUtilization) / 1e18;
        }
    }

    function calculateUtilizationRate(uint256 borrowedFunds, uint256 availableLiquidity) external view returns (uint256) {
        require(borrowedFunds + availableLiquidity > 0, "No liquidity in pool");

        return (borrowedFunds * 1e18) / (borrowedFunds + availableLiquidity);
    }

    function setTokenAddress(bytes32 symbol, address tokenAddress) external onlyOwner {
        tokenAddressMapping[symbol] = tokenAddress;
    }

    function setLiquidityPool(address _liquidityPool) external onlyOwner {
        liquidityPool = _liquidityPool;
    }
}

Last updated