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