Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reclaim #20

Merged
merged 3 commits into from
Jun 20, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
implement reclaim
  • Loading branch information
mshrieve committed Jun 20, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit eaa700a47d33a3cbc478240f28c48bed32442622
74 changes: 68 additions & 6 deletions src/PolyLend.sol
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ interface PolyLendEE {
uint256 id, address borrower, uint256 positionId, uint256 collateralAmount, uint256 minimumDuration
);
event LoanTransferred(uint256 oldId, uint256 newId, address newLender, uint256 newRate);
event LoanReclaimed(uint256 id);

error CollateralAmountIsZero();
error InsufficientCollateralBalance();
@@ -58,6 +59,7 @@ interface PolyLendEE {
error LoanIsCalled();
error MinimumDurationHasNotPassed();
error AuctionHasEnded();
error AuctionHasNotEnded();
}

/// @title PolyLend
@@ -90,9 +92,13 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
return _calculateAmountOwed(loans[_loanId].loanAmount, loans[_loanId].rate, loanDuration);
}

/*//////////////////////////////////////////////////////////////
REQUEST
//////////////////////////////////////////////////////////////*/

/// @notice Submit a request for loan offers
function request(uint256 _positionId, uint256 _collateralAmount, uint256 _minimumDuration)
public
external
returns (uint256)
{
if (_collateralAmount == 0) {
@@ -125,8 +131,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
requests[_requestId].borrower = address(0);
}

/*//////////////////////////////////////////////////////////////
OFFER
//////////////////////////////////////////////////////////////*/

/// @notice Submit a loan offer for a request
function offer(uint256 _requestId, uint256 _loanAmount, uint256 _rate) public returns (uint256) {
function offer(uint256 _requestId, uint256 _loanAmount, uint256 _rate) external returns (uint256) {
if (requests[_requestId].borrower == address(0)) {
revert InvalidRequest();
}
@@ -162,8 +172,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
offers[_id].lender = address(0);
}

/*//////////////////////////////////////////////////////////////
ACCEPT
//////////////////////////////////////////////////////////////*/

/// @notice Accept a loan offer
function accept(uint256 _offerId) public returns (uint256) {
function accept(uint256 _offerId) external returns (uint256) {
uint256 requestId = offers[_offerId].requestId;
address borrower = requests[requestId].borrower;
address lender = offers[_offerId].lender;
@@ -213,8 +227,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
return loanId;
}

/*//////////////////////////////////////////////////////////////
CALL
//////////////////////////////////////////////////////////////*/

/// @notice Call a loan
function call(uint256 _loanId) public {
function call(uint256 _loanId) external {
if (loans[_loanId].borrower == address(0)) {
revert InvalidLoan();
}
@@ -236,11 +254,15 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
emit LoanCalled(_loanId, block.timestamp);
}

/*//////////////////////////////////////////////////////////////
REPAY
//////////////////////////////////////////////////////////////*/

/// @notice Repay a loan
/// @notice It is possible that the the block.timestamp will differ
/// @notice from the time that the transaction is submitted to the
/// @notice block when it is mined.
function repay(uint256 _loanId, uint256 _repayTimestamp) public {
function repay(uint256 _loanId, uint256 _repayTimestamp) external {
if (loans[_loanId].borrower != msg.sender) {
revert OnlyBorrower();
}
@@ -276,8 +298,12 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
emit LoanRepaid(_loanId);
}

/*//////////////////////////////////////////////////////////////
TRANSFER
//////////////////////////////////////////////////////////////*/

/// @notice Transfer a called loan to a new lender
function transfer(uint256 _loanId, uint256 _newRate) public {
function transfer(uint256 _loanId, uint256 _newRate) external {
if (loans[_loanId].borrower == address(0)) {
revert InvalidLoan();
}
@@ -329,6 +355,42 @@ contract PolyLend is PolyLendEE, ERC1155TokenReceiver {
emit LoanTransferred(_loanId, loanId, msg.sender, _newRate);
}

/*//////////////////////////////////////////////////////////////
RECLAIM
//////////////////////////////////////////////////////////////*/

function reclaim(uint256 _loanId) external {
if (loans[_loanId].borrower == address(0)) {
revert InvalidLoan();
}

if (loans[_loanId].lender != msg.sender) {
revert OnlyLender();
}

if (loans[_loanId].callTime == 0) {
revert LoanIsNotCalled();
}

if (block.timestamp <= loans[_loanId].callTime + AUCTION_DURATION) {
revert AuctionHasNotEnded();
}

// transfer the borrower's collateral to the lender
conditionalTokens.safeTransferFrom(
address(this), msg.sender, loans[_loanId].positionId, loans[_loanId].collateralAmount, ""
);

// cancel the loan
loans[_loanId].borrower = address(0);

emit LoanReclaimed(_loanId);
}

/*//////////////////////////////////////////////////////////////
INTERNAL
//////////////////////////////////////////////////////////////*/

function _calculateAmountOwed(uint256 _loanAmount, uint256 _rate, uint256 _loanDuration)
internal
pure
84 changes: 84 additions & 0 deletions src/test/Reclaim.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {PolyLendTestHelper, Loan} from "./PolyLendTestHelper.sol";

contract PolyLendReclaimTest is PolyLendTestHelper {
uint256 rate;

function setUp() public override {
super.setUp();
}

function _setUp(
uint128 _collateralAmount,
uint128 _loanAmount,
uint256 _rate,
uint256 _minimumDuration,
uint256 _duration
) internal returns (uint256) {
vm.assume(_collateralAmount > 0);

rate = bound(_rate, 10 ** 18 + 1, polyLend.MAX_INTEREST());

_mintConditionalTokens(borrower, _collateralAmount, positionId0);
usdc.mint(lender, _loanAmount);

vm.startPrank(borrower);
conditionalTokens.setApprovalForAll(address(polyLend), true);
uint256 requestId = polyLend.request(positionId0, _collateralAmount, _minimumDuration);
vm.stopPrank();

vm.startPrank(lender);
usdc.approve(address(polyLend), _loanAmount);
uint256 offerId = polyLend.offer(requestId, _loanAmount, rate);
vm.stopPrank();

vm.startPrank(borrower);
uint256 loanId = polyLend.accept(offerId);
vm.stopPrank();

vm.warp(block.timestamp + _duration);

vm.startPrank(lender);
polyLend.call(loanId);
vm.stopPrank();

return loanId;
}

function test_PolyLendTransferTest_reclaim(
uint128 _collateralAmount,
uint128 _loanAmount,
uint256 _rate,
uint32 _minimumDuration,
uint256 _duration,
uint256 _auctionLength
) public {
vm.assume(_minimumDuration <= 60 days);

uint256 loanId;
uint256 callTime;

{
uint256 duration = bound(_duration, _minimumDuration, 60 days);
uint256 auctionLength = bound(_auctionLength, polyLend.AUCTION_DURATION() + 1, type(uint32).max);
loanId = _setUp(_collateralAmount, _loanAmount, _rate, _minimumDuration, duration);

callTime = block.timestamp;
vm.warp(block.timestamp + auctionLength);
}

vm.startPrank(lender);
vm.expectEmit();
emit LoanReclaimed(loanId);
polyLend.reclaim(loanId);
vm.stopPrank();

Loan memory loan = _getLoan(loanId);

assertEq(loan.borrower, address(0));

assertEq(conditionalTokens.balanceOf(lender, positionId0), _collateralAmount);
}
}
3 changes: 3 additions & 0 deletions src/test/Transfer.t.sol
Original file line number Diff line number Diff line change
@@ -96,6 +96,9 @@ contract PolyLendTransferTest is PolyLendTestHelper {
assertEq(newLoan.startTime, block.timestamp);
assertEq(newLoan.minimumDuration, 0);
assertEq(newLoan.callTime, 0);

assertEq(usdc.balanceOf(lender), amountOwed);
assertEq(usdc.balanceOf(newLender), 0);
}

function test_revert_PolyLendTransferTest_transfer_InvalidLoan(uint256 _loanId, uint256 _newRate) public {