Skip to main content

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

ConstantValueDescription
INSURANCE_FEE_BPS100010% insurance fee
GRACE_PERIOD7 daysGrace period before default
MIN_LOAN_DURATION7 daysMinimum loan term
MAX_LOAN_DURATION90 daysMaximum loan term

Access Control

RolePermissions
LOAN_MANAGER_ROLECreate loans, mark defaults
DEFAULT_ADMIN_ROLEManage 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