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
