Lending Pool Contract
Contract Details
This is the verified source code for the main Zakaas Lending Pool.
Contract Address0x4e8c63cc36ab2258b395193a67085a16844c07d4
View on PolygonscanSolidity Source Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title LendingPool
* @author Zakaas Protocol
* @notice A smart contract for a lending pool where users can deposit ETH or ERC20 tokens
* to earn yield based on selected lending opportunities and lock-in periods.
*/
contract LendingPool is Ownable, ReentrancyGuard {
// =================================================================
// State Variables
// =================================================================
// Struct to store the details of a user's deposit.
struct Deposit {
address user; // The address of the depositor.
address assetAddress; // The address of the deposited token (address(0) for ETH).
uint256 amount; // The amount of the asset deposited.
uint256 opportunityId; // The ID representing the chosen lending opportunity.
uint256 lockInPeriod; // The duration of the lock-in in months.
uint256 depositTime; // The timestamp when the deposit was made.
uint256 unlockTime; // The timestamp when the funds can be withdrawn.
}
// A mapping from a user's address to an array of their deposits.
mapping(address => Deposit[]) public userDeposits;
// A counter to track the total number of lending opportunities.
// This can be used to validate opportunityId on deposit.
uint256 public totalOpportunities;
// =================================================================
// Events
// =================================================================
/**
* @notice Emitted when a user successfully deposits funds into the pool.
* @param user The address of the user who made the deposit.
* @param assetAddress The address of the token deposited (address(0) for ETH).
* @param amount The amount deposited.
* @param opportunityId The ID of the chosen lending opportunity.
* @param unlockTime The timestamp when the deposit will be unlocked.
*/
event NewDeposit(
address indexed user,
address indexed assetAddress,
uint256 amount,
uint256 indexed opportunityId,
uint256 unlockTime
);
/**
* @notice Emitted when a user successfully withdraws their funds.
* @param user The address of the user who withdrew.
* @param depositIndex The index of the deposit in the user's deposit array.
* @param amount The amount of the asset withdrawn.
*/
event Withdrawal(
address indexed user,
uint256 indexed depositIndex,
uint256 amount
);
// =================================================================
// Constructor
// =================================================================
/**
* @notice Initializes the contract.
* @param _initialOwner The address that will own the contract.
* @param _totalOpportunities The initial number of available lending opportunities.
*/
constructor(address _initialOwner, uint256 _totalOpportunities) Ownable(_initialOwner) {
totalOpportunities = _totalOpportunities;
}
// =================================================================
// Core Functions
// =================================================================
/**
* @notice Allows a user to deposit either ETH or an ERC20 token into the pool.
* @dev This function handles both asset types to provide a unified deposit experience.
* For ETH deposits, `_assetAddress` must be `address(0)` and ETH must be sent with the transaction (`msg.value`).
* For ERC20 deposits, `_assetAddress` is the token's address and `msg.value` must be 0.
* @param _assetAddress The address of the ERC20 token, or address(0) for native ETH.
* @param _amount The amount of the asset to deposit (for ERC20s only, ignored for ETH).
* @param _opportunityId The ID for the selected lending opportunity.
* @param _lockInPeriod The lock-in duration in months (e.g., 3, 6, 12).
*/
function deposit(
address _assetAddress,
uint256 _amount,
uint256 _opportunityId,
uint256 _lockInPeriod
) external payable nonReentrant {
// --- Input Validation ---
require(_opportunityId < totalOpportunities, "LendingPool: Invalid opportunity ID.");
require(_lockInPeriod == 3 || _lockInPeriod == 6 || _lockInPeriod == 12, "LendingPool: Invalid lock-in period.");
uint256 depositAmount;
// --- Asset Handling ---
if (_assetAddress == address(0)) {
// This is a native ETH deposit.
require(msg.value > 0, "LendingPool: ETH amount must be greater than zero.");
require(_amount == 0, "LendingPool: Amount must be 0 for ETH deposits.");
depositAmount = msg.value;
} else {
// This is an ERC20 token deposit.
require(msg.value == 0, "LendingPool: msg.value must be 0 for ERC20 deposits.");
require(_amount > 0, "LendingPool: ERC20 amount must be greater than zero.");
depositAmount = _amount;
// Transfer the ERC20 tokens from the user to this contract.
// The user must have approved this contract to spend the tokens beforehand.
bool success = IERC20(_assetAddress).transferFrom(msg.sender, address(this), depositAmount);
require(success, "LendingPool: ERC20 transfer failed.");
}
// --- Create and Store Deposit ---
uint256 unlockTime = block.timestamp + (_lockInPeriod * 30 days); // Approximation: 30 days per month
Deposit memory newDeposit = Deposit({
user: msg.sender,
assetAddress: _assetAddress,
amount: depositAmount,
opportunityId: _opportunityId,
lockInPeriod: _lockInPeriod,
depositTime: block.timestamp,
unlockTime: unlockTime
});
userDeposits[msg.sender].push(newDeposit);
// --- Emit Event ---
emit NewDeposit(msg.sender, _assetAddress, depositAmount, _opportunityId, unlockTime);
}
/**
* @notice Allows a user to withdraw a specific deposit after its lock-in period has ended.
* @param _depositIndex The index of the deposit in the user's `userDeposits` array.
*/
function withdraw(uint256 _depositIndex) external nonReentrant {
// --- Validation ---
require(_depositIndex < userDeposits[msg.sender].length, "LendingPool: Invalid deposit index.");
Deposit storage depositToWithdraw = userDeposits[msg.sender][_depositIndex];
require(block.timestamp >= depositToWithdraw.unlockTime, "LendingPool: Deposit is still locked.");
require(depositToWithdraw.amount > 0, "LendingPool: Deposit has already been withdrawn.");
uint256 amountToWithdraw = depositToWithdraw.amount;
// Mark the deposit as withdrawn by setting its amount to 0 to prevent re-withdrawal.
depositToWithdraw.amount = 0;
// --- Asset Transfer ---
if (depositToWithdraw.assetAddress == address(0)) {
// Withdraw native ETH.
(bool success, ) = msg.sender.call{value: amountToWithdraw}("");
require(success, "LendingPool: ETH withdrawal failed.");
} else {
// Withdraw ERC20 token.
bool success = IERC20(depositToWithdraw.assetAddress).transfer(msg.sender, amountToWithdraw);
require(success, "LendingPool: ERC20 withdrawal failed.");
}
// --- Emit Event ---
emit Withdrawal(msg.sender, _depositIndex, amountToWithdraw);
}
// =================================================================
// Owner-Only Functions
// =================================================================
/**
* @notice Allows the owner to update the total number of lending opportunities.
* @param _newTotal The new total number of opportunities.
*/
function setTotalOpportunities(uint256 _newTotal) external onlyOwner {
totalOpportunities = _newTotal;
}
/**
* @notice In case funds get stuck, allows the owner to withdraw any ETH from the contract.
*/
function emergencyWithdrawETH() external onlyOwner {
(bool success, ) = owner().call{value: address(this).balance}("");
require(success, "LendingPool: Emergency ETH withdrawal failed.");
}
/**
* @notice In case funds get stuck, allows the owner to withdraw any specified ERC20 token.
* @param _tokenAddress The address of the ERC20 token to withdraw.
*/
function emergencyWithdrawERC20(address _tokenAddress) external onlyOwner {
IERC20 token = IERC20(_tokenAddress);
uint256 balance = token.balanceOf(address(this));
require(balance > 0, "LendingPool: No tokens to withdraw.");
bool success = token.transfer(owner(), balance);
require(success, "LendingPool: Emergency ERC20 withdrawal failed.");
}
}