P2PLoan Contract
The P2PLoan contract manages the complete lifecycle of loans from creation to repayment or default.
Contract Overview
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract P2PLoan is ReentrancyGuard, AccessControl {
IERC20 public immutable usdc;
ITrustVault public immutable trustVault;
IInsurancePool public immutable insurancePool;
struct Loan {
address borrower;
uint256 principal;
uint256 interestRate;
uint256 startTime;
uint256 duration;
uint256 repaidAmount;
LoanStatus status;
}
enum LoanStatus {
Active,
Repaid,
Defaulted
}
mapping(uint256 => Loan) public loans;
uint256 public loanCounter;
}
Key Functions
Create Loan
function createLoan(
address borrower,
uint256 amount,
uint256 duration,
uint256 interestRate
) external onlyRole(LOAN_MANAGER_ROLE) returns (uint256 loanId) {
require(amount > 0, "Amount must be positive");
require(duration >= 7 days && duration <= 90 days, "Invalid duration");
// Transfer funds from Trust Vault to borrower
uint256 insuranceFee = (amount * INSURANCE_FEE_BPS) / 10000;
uint256 netAmount = amount - insuranceFee;
trustVault.withdrawForLoan(amount);
insurancePool.deposit(insuranceFee);
usdc.transfer(borrower, netAmount);
loanId = ++loanCounter;
loans[loanId] = Loan({
borrower: borrower,
principal: amount,
interestRate: interestRate,
startTime: block.timestamp,
duration: duration,
repaidAmount: 0,
status: LoanStatus.Active
});
emit LoanCreated(loanId, borrower, amount, duration);
}
Repay Loan
function repay(uint256 loanId, uint256 amount) external nonReentrant {
Loan storage loan = loans[loanId];
require(loan.status == LoanStatus.Active, "Loan not active");
require(msg.sender == loan.borrower, "Not borrower");
uint256 totalOwed = calculateTotalOwed(loanId);
uint256 paymentAmount = amount > totalOwed ? totalOwed : amount;
usdc.transferFrom(msg.sender, address(this), paymentAmount);
loan.repaidAmount += paymentAmount;
if (loan.repaidAmount >= totalOwed) {
loan.status = LoanStatus.Repaid;
// Return principal to vault
usdc.transfer(address(trustVault), loan.principal);
// Distribute interest to stakers
uint256 interest = loan.repaidAmount - loan.principal;
trustVault.distributeYield(interest);
emit LoanRepaid(loanId, loan.repaidAmount);
} else {
emit PartialRepayment(loanId, paymentAmount, totalOwed - loan.repaidAmount);
}
}
Mark Default
function markDefault(uint256 loanId) external onlyRole(LOAN_MANAGER_ROLE) {
Loan storage loan = loans[loanId];
require(loan.status == LoanStatus.Active, "Loan not active");
require(
block.timestamp > loan.startTime + loan.duration + GRACE_PERIOD,
"Grace period not ended"
);
loan.status = LoanStatus.Defaulted;
// Calculate loss
uint256 totalOwed = calculateTotalOwed(loanId);
uint256 loss = totalOwed - loan.repaidAmount;
// Insurance covers 80%
uint256 insuranceCoverage = (loss * 80) / 100;
uint256 stakerLoss = loss - insuranceCoverage;
insurancePool.claimForDefault(loanId, insuranceCoverage);
trustVault.recordLoss(stakerLoss);
emit LoanDefaulted(loanId, loss, insuranceCoverage);
}
View Functions
Calculate Total Owed
function calculateTotalOwed(uint256 loanId) public view returns (uint256) {
Loan storage loan = loans[loanId];
uint256 interest = (loan.principal * loan.interestRate * loan.duration) / (365 days * 10000);
return loan.principal + interest;
}
Get Loan Details
function getLoan(uint256 loanId) external view returns (
address borrower,
uint256 principal,
uint256 interestRate,
uint256 startTime,
uint256 duration,
uint256 repaidAmount,
uint256 totalOwed,
LoanStatus status,
bool isOverdue
) {
Loan storage loan = loans[loanId];
return (
loan.borrower,
loan.principal,
loan.interestRate,
loan.startTime,
loan.duration,
loan.repaidAmount,
calculateTotalOwed(loanId),
loan.status,
block.timestamp > loan.startTime + loan.duration
);
}
Events
event LoanCreated(
uint256 indexed loanId,
address indexed borrower,
uint256 amount,
uint256 duration
);
event LoanRepaid(
uint256 indexed loanId,
uint256 totalAmount
);
event PartialRepayment(
uint256 indexed loanId,
uint256 amount,
uint256 remainingBalance
);
event LoanDefaulted(
uint256 indexed loanId,
uint256 totalLoss,
uint256 insuranceCoverage
);
Constants
| Constant | Value | Description |
|---|
INSURANCE_FEE_BPS | 1000 | 10% insurance fee |
GRACE_PERIOD | 7 days | Grace period before default |
MIN_LOAN_DURATION | 7 days | Minimum loan term |
MAX_LOAN_DURATION | 90 days | Maximum loan term |
Access Control
| Role | Permissions |
|---|
LOAN_MANAGER_ROLE | Create loans, mark defaults |
DEFAULT_ADMIN_ROLE | Manage roles, emergency functions |
Integration Example
import { ethers } from 'ethers';
import P2PLoanABI from './abis/P2PLoan.json';
const p2pLoan = new ethers.Contract(
P2P_LOAN_ADDRESS,
P2PLoanABI,
signer
);
// Get loan details
const loan = await p2pLoan.getLoan(loanId);
console.log(`Principal: ${ethers.formatUnits(loan.principal, 6)} USDC`);
console.log(`Total Owed: ${ethers.formatUnits(loan.totalOwed, 6)} USDC`);
// Repay loan
const totalOwed = await p2pLoan.calculateTotalOwed(loanId);
await usdc.approve(P2P_LOAN_ADDRESS, totalOwed);
await p2pLoan.repay(loanId, totalOwed);
Security Considerations
This contract handles user funds. Key security measures include:
- ReentrancyGuard: Prevents reentrancy attacks
- Access Control: Role-based permissions
- Input Validation: All parameters validated
- Overflow Protection: Solidity 0.8+ built-in checks