Name: Eshant Gupta Roll No: LCB2023016
This project is a SimpleLending protocol. It allows users to deposit an ERC20 token (Token A) as lending and, in return, borrow another ERC20 token (Token B) by providing it as collateral. The contract uses OpenZeppelin libraries for security and calculates a health factor to protect against insolvency.
This project was built and tested in the Remix IDE.
This project consists of two main files:
/contracts/SimpleLending.sol: The main protocol contract. It handles all logic for deposits, borrowing, repaying, withdrawing collateral, and liquidations. It is secured with several OpenZeppelin contracts./contracts/Mocks.sol: Contains the mock contracts required for testing in a development environment. This includesMockERC20(to create test tokens) andMockPriceFeed(to simulate a Chainlink price oracle).
As per the assignment requirements, this project heavily utilizes OpenZeppelin's battle-tested libraries:
@openzeppelin/contracts/token/ERC20/IERC20.sol: The standard interface for interacting with ERC20 tokens.@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol: ProvidessafeTransferandsafeTransferFromfunctions to prevent failed token transfers.@openzeppelin/contracts/security/ReentrancyGuard.sol: Inherited by the contract, and thenonReentrantmodifier is used to protect all functions that move tokens (deposit,withdraw,borrow,repay,withdrawCollateral,liquidate).@openzeppelin/contracts/access/Ownable.sol: Inherited by the contract to provide a secure access control mechanism. Only the "owner" (deployer) can call critical admin functions.@openzeppelin/contracts/security/Pausable.sol: Inherited by the contract to allow the owner to pause all key functions (deposit,withdraw, etc.) in case of an emergency.
The security of the SimpleLending contract is centered on preventing common smart contract vulnerabilities, ensuring the correctness of financial logic, and leveraging industry-standard libraries for robust protection.
This is the most critical security feature for a lending protocol.
- Implementation: The contract inherits from OpenZeppelin's
ReentrancyGuard.sol. - How it Works: The
nonReentrantmodifier is applied to all key functions that involve token transfers and state changes:deposit(),withdraw(),borrow(),repay(),withdrawCollateral(), andliquidate(). - Effect: This modifier locks the contract, preventing a malicious user or contract from calling back into the function before the first call is finished. This directly stops an attacker from repeatedly borrowing or withdrawing funds before their balance or debt can be updated.
- Implementation: The project is built using Solidity
^0.8.20. - How it Works: All versions of Solidity from 0.8.0 onwards include built-in checks for integer overflow and underflow.
- Effect: Any calculation that results in a number wrapping around (e.g., a balance going below zero) will automatically cause the transaction to revert. This eliminates an entire class of critical bugs related to arithmetic.
The contract enforces a strict order of operations and validates the user's state at every step using require() statements.
- On
borrow(): Arequire()statement checks that the user's new position will be sufficiently collateralized (newTotalDebt <= maxBorrowable). This ensures no user can take on a loan that is immediately under-collateralized. - On
withdrawCollateral(): Arequire()statement validates that even after withdrawing, the user's remaining position is still healthy (currentDebt <= maxBorrowable). This is the core mechanism that guarantees loans remain solvent. - On
liquidate(): Arequire()statement checks that the user's health factor is below the threshold (< 100) before allowing anyone to liquidate the position.
- Implementation: The contract inherits from OpenZeppelin's
Ownable.solandPausable.sol. - Effect: This provides a robust and standard way to manage administrative privileges.
- Ownership: Critical functions that change the contract's parameters (like
setInterestRate,setCollateralizationRatio, andsetPriceFeed) are markedonlyOwner. This prevents any unauthorized user from changing the rules of the protocol. - Pausable: The contract includes
pause()andunpause()functions (alsoonlyOwner) that can stop all major interactions. This acts as an emergency "off-switch" if a vulnerability is found, protecting user funds.
- Ownership: Critical functions that change the contract's parameters (like
- Price Oracle: The contract correctly uses the
AggregatorV3Interfacebut is deployed with aMockPriceFeedfor this assignment. This is a known trust assumption. In a production environment, this mock address would be replaced with a real, decentralized Chainlink Price Feed to prevent price manipulation attacks.
- Open Remix and create two files:
SimpleLending.solandMocks.sol. - Paste the provided code into each file.
- Deploy
Mocks.sol: This will deployMockERC20(for the loan token),MockERC20(for the collateral token), andMockPriceFeed. - Deploy
MockERC20twice, giving each a name/symbol (e.g., "LoanToken" / "LOAN" and "CollateralToken" / "COL"). - Deploy
MockPriceFeed. - Deploy
SimpleLending.sol: Copy the three addresses from the previous step and paste them into the constructor fields for_loanToken,_collateralToken, and_priceFeed. - Run Test Scenario:
- Use the
mintfunction on your twoMockERC20contracts to give your test wallet (e.g.,0x5B3...) funds. - Use the
approvefunction on bothMockERC20contracts to give theSimpleLendingcontract address permission to spend your tokens. - Follow the transaction steps outlined in the "Proof of Functionality" section below.
- Use the
Here is a step-by-step test of the full contract lifecycle.
- SimpleLending Contract:
0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47 - Loan Token (Token A):
0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8 - Collateral Token (Token B):
0xf8e81D47203A594245E36C48e151709F0C19fBe8 - Mock Price Feed:
0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B - My Address (User):
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
(Note: The hashes and gas costs below are from a test environment and may vary.)