Skip to content

Commit

Permalink
fix: add factory check in extensions + sweep into sDAI (#66)
Browse files Browse the repository at this point in the history
* fix: add factory check to extensions
* fix: sweep extensions into DSR or defund if deactivated
  • Loading branch information
0xrusowsky authored Sep 27, 2023
1 parent 5ee7a89 commit 0da529d
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 20 deletions.
10 changes: 9 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
FOUNDRY_FUZZ_RUNS=2000
FOUNDRY_FUZZ_RUNS=2000
CHAIN=5
GUARDIAN_ADDRESS=0x0000000000000000000000000000000000000000
POLICY_ADDRESS=0x0000000000000000000000000000000000000000
EMERGENCY_ADDRESS=0x0000000000000000000000000000000000000000
RPC_URL=YOUR_RPC_URL
ETHERSCAN_KEY=YOUR_API_KEY
DEPLOYER=0x0000000000000000000000000000000000000000
PRIVATE_KEY=YOUR_DEPLOYER_PK
31 changes: 17 additions & 14 deletions src/Clearinghouse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ contract Clearinghouse is Policy, RolesConsumer, CoolerCallback {

// --- PARAMETER BOUNDS ------------------------------------------

uint256 public constant INTEREST_RATE = 5e15; // 0.5% anually
uint256 public constant LOAN_TO_COLLATERAL = 3000e18; // 3,000 DAI/gOHM
uint256 public constant DURATION = 121 days; // Four months
uint256 public constant FUND_CADENCE = 7 days; // One week
uint256 public constant FUND_AMOUNT = 18_000_000e18; // 18 million
uint256 public constant MAX_REWARD = 1e17; // 0.1 gOHM
uint256 public constant INTEREST_RATE = 5e15; // 0.5% anually
uint256 public constant LOAN_TO_COLLATERAL = 289292e16; // 2,892.92 DAI/gOHM
uint256 public constant DURATION = 121 days; // Four months
uint256 public constant FUND_CADENCE = 7 days; // One week
uint256 public constant FUND_AMOUNT = 18_000_000e18; // 18 million
uint256 public constant MAX_REWARD = 1e17; // 0.1 gOHM

// --- STATE VARIABLES -------------------------------------------

Expand Down Expand Up @@ -142,7 +142,7 @@ contract Clearinghouse is Policy, RolesConsumer, CoolerCallback {
/// @param amount_ of DAI to lend.
/// @return the id of the granted loan.
function lendToCooler(Cooler cooler_, uint256 amount_) external returns (uint256) {
// Attempt a clearinghouse <> treasury rebalance.
// Attempt a Clearinghouse <> Treasury rebalance.
rebalance();

// Validate that cooler was deployed by the trusted factory.
Expand Down Expand Up @@ -179,20 +179,23 @@ contract Clearinghouse is Policy, RolesConsumer, CoolerCallback {
/// @param loanID_ index of loan in loans[].
/// @param times_ Amount of times that the fixed-term loan duration is extended.
function extendLoan(Cooler cooler_, uint256 loanID_, uint8 times_) external {
// Attempt a clearinghouse <> treasury rebalance.
rebalance();

Cooler.Loan memory loan = cooler_.getLoan(loanID_);

// Ensure Clearinghouse is the lender.
if (loan.lender != address(this)) revert NotLender();
// Validate that cooler was deployed by the trusted factory.
if (!factory.created(address(cooler_))) revert OnlyFromFactory();

// Calculate extension interest based on the remaining principal.
uint256 interestBase = interestForLoan(loan.principal, loan.request.duration);

// Transfer in extension interest from the caller.
dai.transferFrom(msg.sender, loan.recipient, interestBase * times_);
dai.transferFrom(msg.sender, address(this), interestBase * times_);
if (active) {
_sweepIntoDSR(interestBase * times_);
} else {
_defund(dai, interestBase * times_);
}

// Signal to cooler that loan can be extended.
// Signal to cooler that loan should be extended.
cooler_.extendLoanTerms(loanID_, times_);
}

Expand Down
24 changes: 19 additions & 5 deletions src/test/Clearinghouse.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,21 @@ contract ClearinghouseTest is Test {

// Move forward without defaulting
_skip(elapsed_);
// Rebalance to ensure the extension funds will stay in the CH
for (uint256 i = 0; i <= elapsed_ / clearinghouse.FUND_CADENCE(); i++) {
clearinghouse.rebalance();
}

vm.startPrank(user);
// Cache DAI balance and interest to be paid in the future
uint256 initDaiUser = dai.balanceOf(user);
uint256 initDaiCH = dai.balanceOf(address(clearinghouse));
uint256 initSdaiCH = sdai.balanceOf(address(clearinghouse));
uint256 initInterest = clearinghouse.interestReceivables();
uint256 initPrincipal = clearinghouse.principalReceivables();
// Approve the interest of the extensions
uint256 interestOwed = clearinghouse.interestForLoan(initLoan.principal, initLoan.request.duration) * times_;
uint256 interestOwedInSdai = sdai.previewDeposit(interestOwed);
dai.approve(address(clearinghouse), interestOwed);
// Extend loan
clearinghouse.extendLoan(cooler, loanID, times_);
Expand All @@ -335,7 +341,8 @@ contract ClearinghouseTest is Test {

// Check: balances
assertEq(dai.balanceOf(user), initDaiUser - interestOwed, "DAI user");
assertEq(dai.balanceOf(address(clearinghouse)), initDaiCH + interestOwed, "DAI CH");
assertEq(dai.balanceOf(address(clearinghouse)), initDaiCH, "DAI CH");
assertEq(sdai.balanceOf(address(clearinghouse)), initSdaiCH + interestOwedInSdai, "sDAI CH");
// Check: cooler storage
assertEq(extendedLoan.principal, initLoan.principal, "principal");
assertEq(extendedLoan.interestDue, initLoan.interestDue, "interest");
Expand All @@ -357,6 +364,10 @@ contract ClearinghouseTest is Test {

// Move forward without defaulting
_skip(elapsed_);
// Rebalance to ensure the extension funds will stay in the CH
for (uint256 i = 0; i <= elapsed_ / clearinghouse.FUND_CADENCE(); i++) {
clearinghouse.rebalance();
}

// Bound repayment
repay_ = bound(elapsed_, 1, initLoan.interestDue);
Expand All @@ -370,10 +381,12 @@ contract ClearinghouseTest is Test {
Cooler.Loan memory repaidLoan = cooler.getLoan(loanID);
uint256 initDaiUser = dai.balanceOf(user);
uint256 initDaiCH = dai.balanceOf(address(clearinghouse));
uint256 initSdaiCH = sdai.balanceOf(address(clearinghouse));
uint256 initInterest = clearinghouse.interestReceivables();
uint256 initPrincipal = clearinghouse.principalReceivables();
// Approve the interest of the followup extensions
uint256 interestOwed = clearinghouse.interestForLoan(initLoan.principal, initLoan.request.duration) * times_;
uint256 interestOwedInSdai = sdai.previewDeposit(interestOwed);
dai.approve(address(clearinghouse), interestOwed);

// Extend loan
Expand All @@ -384,7 +397,8 @@ contract ClearinghouseTest is Test {

// Check: balances
assertEq(dai.balanceOf(user), initDaiUser - interestOwed, "DAI user");
assertEq(dai.balanceOf(address(clearinghouse)), initDaiCH + interestOwed, "DAI CH");
assertEq(dai.balanceOf(address(clearinghouse)), initDaiCH, "DAI CH");
assertEq(sdai.balanceOf(address(clearinghouse)), initSdaiCH + interestOwedInSdai, "sDAI CH");
// Check: cooler storage
assertEq(extendedLoan.principal, repaidLoan.principal, "principal");
assertEq(extendedLoan.interestDue, repaidLoan.interestDue, "interest");
Expand All @@ -395,16 +409,16 @@ contract ClearinghouseTest is Test {
assertEq(clearinghouse.principalReceivables(), initPrincipal);
}

function testRevert_extendLoan_NotLender() public {
function testRevert_extendLoan_NotFromFactory() public {
CoolerFactory maliciousFactory = new CoolerFactory();
Cooler maliciousCooler = Cooler(maliciousFactory.generateCooler(gohm, dai));

vm.startPrank(others);
uint256 reqID = maliciousCooler.requestLoan(0, 0, 1, 1);
uint256 loanID = maliciousCooler.clearRequest(reqID, others, false);

// Only extendable for loans where the CH is the lender.
vm.expectRevert(Clearinghouse.NotLender.selector);
// Only extendable for loans that come from the trusted Cooler Factory
vm.expectRevert(CoolerCallback.OnlyFromFactory.selector);
clearinghouse.extendLoan(maliciousCooler, loanID, 1);
}

Expand Down

0 comments on commit 0da529d

Please sign in to comment.