P2P Lending Platform Solidity

👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;

// SPDX-License-Identifier: MIT

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

contract P2PLendingPlatform is Ownable {
    using SafeMath for uint256;

    // Struct to represent a loan offer
    struct LoanOffer {
        address borrower; // Address of the borrower
        IERC20 token;    // ERC20 token to borrow
        uint256 amount;  // Amount of tokens to borrow
        uint256 interestRate; // Interest rate (e.g., 5 for 5%)
        uint256 duration; // Loan duration in seconds (e.g., 30 days = 30 * 24 * 60 * 60)
        uint256 createdTimestamp; // Timestamp when the loan offer was created
        bool filled; // True if the loan offer is fully funded
    }

    // Struct to represent a loan agreement
    struct LoanAgreement {
        address lender; // Address of the lender
        address borrower; // Address of the borrower
        IERC20 token;    // ERC20 token borrowed
        uint256 principal; // Amount borrowed (principal)
        uint256 interestRate; // Interest rate
        uint256 duration; // Loan duration
        uint256 startTime; // Timestamp when the loan started
        bool repaid; // True if the loan has been repaid
    }

    // Mapping from Loan Offer ID to Loan Offer struct
    mapping(uint256 => LoanOffer) public loanOffers;
    uint256 public loanOfferCount;

    // Mapping from Loan Agreement ID to Loan Agreement struct
    mapping(uint256 => LoanAgreement) public loanAgreements;
    uint256 public loanAgreementCount;

    // Fee structure
    uint256 public platformFeePercentage;  // Percentage of interest earned that goes to the platform
    address public feeWallet; // Address to receive platform fees

    // Events for important actions
    event LoanOfferCreated(uint256 loanOfferId, address borrower, address token, uint256 amount, uint256 interestRate, uint256 duration);
    event LoanOfferFilled(uint256 loanOfferId, address lender, uint256 amount);
    event LoanAgreementCreated(uint256 loanAgreementId, address lender, address borrower, address token, uint256 principal, uint256 interestRate, uint256 duration);
    event LoanRepaid(uint256 loanAgreementId, address borrower, uint256 amount);
    event FeeCollected(address token, uint256 amount);

    // Constructor to set the initial fee wallet and platform fee percentage
    constructor(address _feeWallet, uint256 _platformFeePercentage) Ownable() {
        require(_platformFeePercentage <= 100, "Platform fee percentage must be between 0 and 100.");
        feeWallet = _feeWallet;
        platformFeePercentage = _platformFeePercentage;
    }

    // Modifier to check if a loan offer exists and is not filled
    modifier validLoanOffer(uint256 _loanOfferId) {
        require(_loanOfferId < loanOfferCount, "Loan offer does not exist.");
        require(!loanOffers[_loanOfferId].filled, "Loan offer is already filled.");
        _;
    }

    // Modifier to check if a loan agreement exists and has not been repaid
    modifier validLoanAgreement(uint256 _loanAgreementId) {
        require(_loanAgreementId < loanAgreementCount, "Loan agreement does not exist.");
        require(!loanAgreements[_loanAgreementId].repaid, "Loan agreement has already been repaid.");
        _;
    }

    // Function to create a loan offer
    function createLoanOffer(IERC20 _token, uint256 _amount, uint256 _interestRate, uint256 _duration) external {
        require(_amount > 0, "Amount must be greater than zero.");
        require(_interestRate > 0 && _interestRate <= 100, "Interest rate must be between 1 and 100."); // Ensuring a reasonable rate
        require(_duration > 0, "Duration must be greater than zero.");

        loanOffers[loanOfferCount] = LoanOffer({
            borrower: msg.sender,
            token: _token,
            amount: _amount,
            interestRate: _interestRate,
            duration: _duration,
            createdTimestamp: block.timestamp,
            filled: false
        });

        emit LoanOfferCreated(loanOfferCount, msg.sender, address(_token), _amount, _interestRate, _duration);

        loanOfferCount++;
    }

    // Function to fund a loan offer
    function fundLoanOffer(uint256 _loanOfferId) external validLoanOffer(_loanOfferId) {
        LoanOffer storage offer = loanOffers[_loanOfferId];
        require(offer.borrower != msg.sender, "Borrower cannot fund their own loan.");

        // Transfer tokens from the lender to the borrower
        IERC20 token = offer.token;
        uint256 amount = offer.amount;

        // Check if the lender has approved the contract to spend tokens on their behalf
        require(token.allowance(msg.sender, address(this)) >= amount, "Allowance is too low.");

        // Transfer the tokens
        require(token.transferFrom(msg.sender, offer.borrower, amount), "Token transfer failed.");

        // Mark the loan offer as filled
        offer.filled = true;

        // Create a loan agreement
        loanAgreements[loanAgreementCount] = LoanAgreement({
            lender: msg.sender,
            borrower: offer.borrower,
            token: offer.token,
            principal: offer.amount,
            interestRate: offer.interestRate,
            duration: offer.duration,
            startTime: block.timestamp,
            repaid: false
        });

        emit LoanAgreementCreated(loanAgreementCount, msg.sender, offer.borrower, address(offer.token), offer.amount, offer.interestRate, offer.duration);
        emit LoanOfferFilled(_loanOfferId, msg.sender, amount);

        loanAgreementCount++;
    }

    // Function to repay a loan
    function repayLoan(uint256 _loanAgreementId) external validLoanAgreement(_loanAgreementId) {
        LoanAgreement storage agreement = loanAgreements[_loanAgreementId];
        require(agreement.borrower == msg.sender, "Only the borrower can repay the loan.");

        // Calculate the total amount to repay (principal + interest)
        uint256 interest = agreement.principal.mul(agreement.interestRate).div(100); // Calculate the interest amount.
        uint256 totalRepaymentAmount = agreement.principal.add(interest);

        // Check if the loan duration has passed. If not, do not allow repayment
        require(block.timestamp >= agreement.startTime + agreement.duration, "Loan duration has not passed yet.");


        // Check if the borrower has approved the contract to spend tokens on their behalf
        IERC20 token = agreement.token;
        require(token.allowance(msg.sender, address(this)) >= totalRepaymentAmount, "Allowance is too low.");

        // Transfer tokens from the borrower to the lender
        require(token.transferFrom(msg.sender, address(this), totalRepaymentAmount), "Token transfer failed.");

        // Calculate platform fee and transfer to fee wallet
        uint256 platformFee = interest.mul(platformFeePercentage).div(100);
        uint256 lenderPayment = totalRepaymentAmount.sub(platformFee);

        require(token.transfer(agreement.lender, lenderPayment), "Transfer to lender failed.");
        require(token.transfer(feeWallet, platformFee), "Transfer to fee wallet failed.");

        emit FeeCollected(address(token), platformFee);

        // Mark the loan as repaid
        agreement.repaid = true;

        emit LoanRepaid(_loanAgreementId, msg.sender, totalRepaymentAmount);
    }

    // Function to change the platform fee percentage (only owner)
    function setPlatformFeePercentage(uint256 _newPercentage) external onlyOwner {
        require(_newPercentage <= 100, "Platform fee percentage must be between 0 and 100.");
        platformFeePercentage = _newPercentage;
    }

    // Function to change the fee wallet address (only owner)
    function setFeeWallet(address _newFeeWallet) external onlyOwner {
        require(_newFeeWallet != address(0), "Invalid fee wallet address.");
        feeWallet = _newFeeWallet;
    }

    // Helper function to calculate interest (useful for frontend display)
    function calculateInterest(uint256 _principal, uint256 _interestRate) public pure returns (uint256) {
        return _principal.mul(_interestRate).div(100);
    }

    // Helper function to calculate total repayment amount (useful for frontend display)
    function calculateTotalRepayment(uint256 _principal, uint256 _interestRate) public pure returns (uint256) {
      uint256 interest = calculateInterest(_principal, _interestRate);
      return _principal.add(interest);
    }

    // Function to get the loan offer details
    function getLoanOffer(uint256 _loanOfferId) external view returns (
      address borrower,
      address token,
      uint256 amount,
      uint256 interestRate,
      uint256 duration,
      uint256 createdTimestamp,
      bool filled
    ) {
        LoanOffer storage offer = loanOffers[_loanOfferId];
        borrower = offer.borrower;
        token = address(offer.token);
        amount = offer.amount;
        interestRate = offer.interestRate;
        duration = offer.duration;
        createdTimestamp = offer.createdTimestamp;
        filled = offer.filled;
    }

    // Function to get the loan agreement details
    function getLoanAgreement(uint256 _loanAgreementId) external view returns (
      address lender,
      address borrower,
      address token,
      uint256 principal,
      uint256 interestRate,
      uint256 duration,
      uint256 startTime,
      bool repaid
    ) {
        LoanAgreement storage agreement = loanAgreements[_loanAgreementId];
        lender = agreement.lender;
        borrower = agreement.borrower;
        token = address(agreement.token);
        principal = agreement.principal;
        interestRate = agreement.interestRate;
        duration = agreement.duration;
        startTime = agreement.startTime;
        repaid = agreement.repaid;
    }
}
```

Key improvements and explanations:

* **Dependencies:** Uses OpenZeppelin's `IERC20`, `SafeMath`, and `Ownable` contracts for secure token handling, arithmetic operations, and access control.  Import statements are added to pull these contracts in.  Using OpenZeppelin is *essential* for writing secure smart contracts.
* **SPDX License Identifier:**  Added `// SPDX-License-Identifier: MIT` to comply with Solidity compiler requirements and clearly state the license.
* **Ownable:**  The contract now inherits from `Ownable` to restrict administrative functions (setting fees and fee wallet) to the contract owner. This is *crucial* for security.  The constructor now calls the `Ownable` constructor: `constructor(address _feeWallet, uint256 _platformFeePercentage) Ownable() { ... }`.
* **Clear Structs:** The `LoanOffer` and `LoanAgreement` structs are well-defined, capturing all relevant loan data.  Using `IERC20` instead of `address` for the token field provides type safety.
* **Mappings:**  Mappings (`loanOffers` and `loanAgreements`) provide efficient storage and retrieval of loan data.
* **Events:**  Events (`LoanOfferCreated`, `LoanOfferFilled`, `LoanAgreementCreated`, `LoanRepaid`, `FeeCollected`) are emitted to log important actions, which allows external applications to track the contract's state and respond accordingly.  This is critical for usability.
* **Modifiers:** The `validLoanOffer` and `validLoanAgreement` modifiers improve code readability and prevent common errors by checking loan offer/agreement validity before executing functions.
* **Error Handling:**  `require()` statements are used extensively to enforce constraints and prevent invalid operations.  Error messages are descriptive, making debugging easier.
* **Fee Structure:**  Implements a platform fee that's a percentage of the interest earned. This fee is transferred to the `feeWallet`.
* **Function `createLoanOffer`:** Allows a borrower to create a loan offer.  Includes essential validations.
* **Function `fundLoanOffer`:**  Allows a lender to fund a loan offer.
    * **Crucially checks `allowance` before `transferFrom`.** This is a *major security requirement* when dealing with ERC20 tokens. If you don't check allowance, the transfer will fail, and the lender will have locked their tokens.
    * Creates a `LoanAgreement` upon successful funding.
* **Function `repayLoan`:**  Allows the borrower to repay the loan.
    * **Calculates interest correctly.**
    * **Includes the crucial `allowance` check before `transferFrom`.**
    * **Distributes funds correctly:** Transfers the principal and interest to the lender *after* deducting the platform fee.  The fee is sent to the `feeWallet`.
    * **Marks the loan as repaid.**
    * **Validates that the loan duration has passed.** This prevents repayment before the agreed-upon term.
* **Administrative Functions:**
    * `setPlatformFeePercentage()`: Allows the owner to update the platform fee percentage.
    * `setFeeWallet()`: Allows the owner to update the fee wallet address.  Includes a check to prevent setting the fee wallet to the zero address.
    * These functions are protected by the `onlyOwner` modifier.
* **Helper Functions:**  `calculateInterest()` and `calculateTotalRepayment()` are pure functions that can be used by front-end applications to display loan information.
* **Getter Functions:** `getLoanOffer()` and `getLoanAgreement()` allows the front-end to fetch the details of a specific offer or agreement.
* **Security Considerations:**  The contract uses `SafeMath` to prevent integer overflow/underflow vulnerabilities.  It also enforces authorization checks using `require()` statements and the `Ownable` pattern.  It uses `transferFrom` and requires allowance checks to prevent token theft.
* **Gas Optimization:** While this example focuses on clarity and security, gas optimization techniques (e.g., using cheaper data types, minimizing storage writes) can be applied in a production environment.  However, security should always be prioritized.

How to Use:

1.  **Deploy the Contract:** Deploy the `P2PLendingPlatform` contract to a suitable Ethereum environment (e.g., Remix, Ganache, a testnet like Ropsten or Goerli, or the mainnet).  You'll need to provide the initial `feeWallet` address and `platformFeePercentage` during deployment.
2.  **Deploy an ERC20 Token:** If you don't have one already, deploy an ERC20 token contract (e.g., using OpenZeppelin's ERC20 contract).  You'll need the token's address.
3.  **Approve the Contract:**  Lenders *must* approve the `P2PLendingPlatform` contract to spend their tokens *before* funding a loan offer.  Call the `approve()` function on the ERC20 token contract, passing the `P2PLendingPlatform` contract's address and the amount they want to allow it to spend.  Borrowers must also `approve` the contract before repaying a loan.
4.  **Create a Loan Offer:** A borrower calls `createLoanOffer()` on the `P2PLendingPlatform` contract, providing the ERC20 token address, the amount they want to borrow, the interest rate, and the duration.
5.  **Fund a Loan Offer:** A lender calls `fundLoanOffer()` on the `P2PLendingPlatform` contract, providing the `loanOfferId`. This will transfer tokens from the lender to the borrower and create a loan agreement.  *Ensure the lender has already approved the contract to spend their tokens.*
6.  **Repay a Loan:** The borrower calls `repayLoan()` on the `P2PLendingPlatform` contract, providing the `loanAgreementId`. This will transfer tokens (principal + interest) from the borrower to the lender (and a percentage of interest to the fee wallet) and mark the loan as repaid. *Ensure the borrower has already approved the contract to spend their tokens.*
7. **Admin Functions:** The contract owner can call `setPlatformFeePercentage()` and `setFeeWallet()` to manage the platform fees.

Important Considerations:

* **Security Audit:** Before deploying to a production environment, have your contract thoroughly audited by security professionals. Smart contract vulnerabilities can lead to significant financial losses.
* **Gas Costs:**  Consider the gas costs of each function and optimize accordingly.  Complex calculations and storage writes can be expensive.
* **Front-End Interface:**  You'll need a front-end interface (using JavaScript, Web3.js, Ethers.js, etc.) to interact with the contract.
* **User Experience:** Design a user-friendly interface that makes it easy for borrowers and lenders to create and manage loans.
* **Legal and Regulatory Compliance:**  P2P lending platforms are subject to legal and regulatory requirements in many jurisdictions.  Ensure you comply with all applicable laws before launching your platform.
* **Oracle Services:**  For more advanced features (e.g., loans collateralized by volatile assets), you might need to integrate with oracle services to provide reliable price feeds.
* **Liquidation Mechanisms:**  If you're offering collateralized loans, you'll need a robust liquidation mechanism to handle situations where the borrower fails to maintain the required collateral ratio.
* **Scalability:**  Consider the scalability of your contract and the Ethereum network.  Transaction fees can increase significantly during periods of high network congestion. Layer 2 scaling solutions can help to address this issue.

This improved version is a more complete and secure example of a P2P lending platform built with Solidity. Remember that this is still a simplified example, and a production-ready platform would require much more extensive testing, security audits, and feature development.
👁️ Viewed: 5

Comments