Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[profile.default]
evm_version = "cancun"
evm_version = "prague"
libs = ['lib']
optimizer = true
optimizer_runs = 10000
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
85 changes: 69 additions & 16 deletions scripts/borrowControllerSetup.s.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import {Market} from "src/Market.sol";
import {Market, IERC20} from "src/Market.sol";
import {IMarket} from "src/interfaces/IMarket.sol";
import {BorrowController} from "src/BorrowController.sol";
import "src/DBR.sol";

Expand All @@ -11,44 +12,96 @@ interface IBorrowController {
function setOperator(address gov) external;
}

interface IBC is IBorrowController {
function minDebts(address market) external returns(uint);
}

interface IErc20 is IERC20 {
function name() external view returns(string memory);
}

contract borrowControllerSetup is Script {
address gov = 0x926dF14a23BE491164dCF93f4c468A50ef659D5B;
address deployerAddress = 0x11EC78492D53c9276dD7a184B1dbfB34E50B710D;
IBorrowController oldBorrowController = IBorrowController(0x20C7349f6D6A746a25e66f7c235E96DAC880bc0D);
BorrowController newBorrowController; // = BorrowController(0x81ff13c46f363D13fC25FB801a4335c6097B7862);
//IBC oldBorrowController = IBC(0x2DbAd53A647A86b8988E007a33FE78bd55e9Dd6f);
BorrowController newBorrowController;
DolaBorrowingRights DBR = DolaBorrowingRights(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);
address[] markets =
[
0x93685185666c8D34ad4c574B3DBF41231bbfB31b, //cvxFxs
0x3474ad0e3a9775c9F68B415A7a9880B0CAB9397a, //cvxCrv
0x63fAd99705a255fE2D500e498dbb3A9aE5AA1Ee8, //crv
0x7Cd3ab8354289BEF52c84c2BF0A54E3608e66b37, //gohm
0xb516247596Ca36bf32876199FBdCaD6B3322330B, //inv
0x743A502cf0e213F6FEE56cD9C6B03dE7Fa951dCf, //steth
0x63Df5e23Db45a2066508318f172bA45B9CD37035, //weth
0x27b6c301Fd441f3345d61B7a4245E1F823c3F9c4 //stycrv
0x27b6c301Fd441f3345d61B7a4245E1F823c3F9c4, //stycrv
0xB907Dcc926b5991A149d04Cb7C0a4a25dC2D8f9a, //deUSD-DOLA
0x4f5ea72d932f554f08e97CB78DD25F8AAE43C08e, //yv-deUSD-DOLA
0xD68d3a44d46dd50BFeBa8Cca544717B76e7C4b29, //sUSDS-sDOLA
0xb427fC22561f3963B04202F9bb5BCEbd76c14A99, //sUSDE-sDOLA
0x4A33baFA8a31E4ec9649f65646022cAD1957808b, //yv-sUSDS-sDOLA
0x2fed508aAc87c0e6f0b647Fe83164A7AA6eb2FC9, //scrvUSD-DOLA
0x5bb8f6aAcFF2971B42F9fE6945D24726A2541CF2, //yv-scrvUSD-DOLA
0x4E264618dC015219CD83dbc53B31251D73c2db1a, //yv-sUSDE-sDOLA
0x63D27fC9d463Ed727676367D3F818999962737E8, //scrvUSD-sDOLA
0xb8bc1E9c0a2d445bc39d2A745F47619E954dD565, //yv-scrvUSD-sDOLA
0x0971B1690d101169BFca4715897aD3a9b3C39b26, //DAI
0x3FD3daBB9F9480621C8A111603D3Ba70F17550BC, //wstETH
0x79eF6d28C41e47A588E2F2ffB4140Eb6d952AEc4, //sUSDe
0x6A522f3BD3fDA15e74180953f203cf55aA6C631E, //crvUSD-DOLA
0x2A256306D8ba899E33B01e495982656884Ac77FF, //cbBTC
0xe85943e280776254ee6C9801553B93F10Ef4C99C, //yv-crvUSD-DOLA
0xFEA3A862eE4b3F9b6015581d6d2D25AF816C54f1, //sFRAX
0x0DFE3D04536a74Dd532dd0cEf5005bA14c5f4112, //pt-sUSDe-mar27
0x0c0bb843FAbda441edeFB93331cFff8EC92bD168, //st-yETH
0x4797A68c8feB383c3372c0e098533aCf8eD95B26, //FraxPyUsd-DOLA
0x29fe42F4F71Ba5b9a7aaE794468e7ca4128a93b8, //COMP
0xf013D998D4cf7f45547958094F1EEE75Ca43c4f5, //yv-FraxPyUsd-DOLA
0x87df9A00f0e4908e61756d2Fcb348ADF95Ce72Ea, //FraxBP-DOLA
0x8205bE13cC245740F9EA23Dc88a9B56206bEC0e3 //yv-FraxBP-DOLA
];
//0x743A502cf0e213F6FEE56cD9C6B03dE7Fa951dCf, //steth
//0x7Cd3ab8354289BEF52c84c2BF0A54E3608e66b37, //gohm

function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.broadcast(deployerPrivateKey);
newBorrowController = new BorrowController(deployerAddress, address(DBR));

vm.createSelectFork(vm.envString("RPC_MAINNET"));
vm.broadcast(deployerPrivateKey);
newBorrowController = new BorrowController(gov, address(DBR));
/**
for(uint i; i < markets.length; ++i){
address market = markets[i];
require(DBR.markets(market), "Not a market");
console.log("Market:", IErc20(IMarket(market).collateral()).name());
IBC oldBorrowController = IBC(IMarket(market).borrowController());
//console.log("Old BorrowController:", address(oldBorrowController));
uint newLimit = newBorrowController.dailyLimits(market);
uint newMinDebt = newBorrowController.minDebts(market);
uint oldLimit = oldBorrowController.dailyLimits(market);
vm.broadcast(deployerPrivateKey);
newBorrowController.setDailyLimit(market, oldLimit);
uint oldMinDebt = oldBorrowController.minDebts(market);
if(newLimit != oldLimit || newMinDebt != oldMinDebt){
console.log("Old Limit :", oldLimit);
console.log("Old MinDebt:", oldMinDebt);
console.log("New Limit :", newLimit);
console.log("New MinDebt:", newMinDebt);
//vm.startBroadcast(deployerPrivateKey);
//newBorrowController.setDailyLimit(market, oldLimit);
//newBorrowController.setMinDebt(market, oldMinDebt);
//vm.stopBroadcast();
}
console.log("----------------");
}

*/
//Add helper contract to allowList
vm.broadcast(deployerPrivateKey);
newBorrowController.allow(0xae8165f37FC453408Fb1cd1064973df3E6499a76);
/*
vm.startBroadcast(deployerPrivateKey);
newBorrowController.allow(0x0aBb47c564296D34B0F5B068361985f507fe123c);
newBorrowController.allow(0x0591926d5d3b9Cc48ae6eFB8Db68025ddc3adFA5);
newBorrowController.allow(0x496a3Fc15209350487F7136b7c3c163F9204eE70);
newBorrowController.allow(0x495886947EAce9788360F46be55c758f92Ecd074);
newBorrowController.allow(0x5233f4C2515ae21B540c438862Abb5603506dEBC);


//Transfer ownership to gov
vm.broadcast(deployerPrivateKey);
newBorrowController.setOperator(gov);
*/
}
}
5 changes: 3 additions & 2 deletions src/BorrowController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ contract BorrowController {
uint lastUpdated = DBR.lastUpdated(borrower);
uint debts = DBR.debts(borrower);
//Check to prevent effects of edge case bug
if(lastUpdated > 0 && debts == 0 && lastUpdated != block.timestamp){
uint timeElapsed = block.timestamp - lastUpdated;
if(lastUpdated > 0 && debts * timeElapsed < 365 days && lastUpdated != block.timestamp){
//Important check, otherwise a user could repeatedly mint themsevles DBR
require(DBR.markets(msg.sender), "Message sender is not a market");
uint deficit = (block.timestamp - lastUpdated) * amount / 365 days;
Expand All @@ -114,7 +115,7 @@ contract BorrowController {
//If the chainlink oracle price feed is stale, deny borrow
if(isPriceStale(msg.sender)) return false;
//If the message sender is not a contract, then there's no need check allowlist
if(msgSender == tx.origin) return true;
if(msgSender == tx.origin && msgSender.code.length == 0) return true;
return contractAllowlist[msgSender];
}

Expand Down
161 changes: 159 additions & 2 deletions test/BorrowController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ contract BorrowContractTxOrigin {
market.borrow((AMOUNT * COLLATERAL_FACTOR_BPS * PRICE) / BPS_BASIS);
}
}
contract BatchApprove {
IDBR immutable dbr;

constructor(address _dbr){
dbr = IDBR(_dbr);
}

function approveDepositAndBorrow(address _market, uint depositAmount, uint borrowAmount) external {
require(msg.sender == address(this), "Invalid authority");
require(tx.origin == address(this), "Invalid origin");
require(dbr.markets(_market), "Invalid market");
IMarket market = IMarket(_market);
IERC20(market.collateral()).approve(_market, depositAmount);
market.depositAndBorrow(depositAmount, borrowAmount);
}
}

contract BorrowControllerTest is FiRMBaseTest {
BorrowContract borrowContract;
Expand Down Expand Up @@ -170,6 +186,130 @@ contract BorrowControllerTest is FiRMBaseTest {
);
}

function test_BorrowAllowed_True_Where_EdgeCaseBugDebtNonZero()
public
{
vm.prank(gov);
borrowController.setDailyLimit(address(market), 2000 ether);
uint testAmount = 1e18;
gibWeth(user, testAmount);
uint halfBorrow = getMaxBorrowAmount(testAmount) / 2;
gibDOLA(address(market), halfBorrow * 2);
vm.startPrank(user, user);
deposit(testAmount);
market.borrow(halfBorrow);
market.repay(user, halfBorrow-1);
vm.stopPrank();

assertEq(market.debts(user), 1, "User debt not 1");
assertEq(dbr.balanceOf(user), 0, "DBR balance of user is not 0 before time skip");
vm.warp(block.timestamp + 30 days);
ethFeed.changeUpdatedAt(block.timestamp);
vm.prank(gov);
dbr.addMinter(address(borrowController));
vm.prank(address(market), user);
assertTrue(
borrowController.borrowAllowed(user, user, 1)
);
vm.startPrank(user, user);
assertGt(market.getCreditLimit(user), 0, "User has no credit limit");
market.borrow(market.getCreditLimit(user) / 100);
assertLe(dbr.deficitOf(user), 30 days * 1, "Deficit of user more than expected");
assertEq(dbr.balanceOf(user), 0, "DBR balance of user is not 0");
}

function test_BorrowAllowed_True_Where_EdgeCaseBugDebtNonZero365Days()
public
{
vm.prank(gov);
borrowController.setDailyLimit(address(market), 2000 ether);
uint testAmount = 1e18;
gibWeth(user, testAmount);
uint halfBorrow = getMaxBorrowAmount(testAmount) / 2;
gibDOLA(address(market), halfBorrow * 2);
vm.startPrank(user, user);
deposit(testAmount);
market.borrow(halfBorrow);
market.repay(user, halfBorrow-1);
vm.stopPrank();

assertEq(market.debts(user), 1, "User debt not 1");
assertEq(dbr.balanceOf(user), 0, "DBR balance of user is not 0 before time skip");
vm.warp(block.timestamp + 365 days);
ethFeed.changeUpdatedAt(block.timestamp);
vm.prank(gov);
dbr.addMinter(address(borrowController));
vm.prank(address(market), user);
assertTrue(
borrowController.borrowAllowed(user, user, 1)
);
vm.startPrank(user, user);
assertGt(market.getCreditLimit(user), 0, "User has no credit limit");
uint creditLimit = market.getCreditLimit(user);
vm.expectRevert("DBR Deficit");
market.borrow(creditLimit / 100);
dbr.accrueDueTokens(user);
assertEq(dbr.deficitOf(user), 1);
}


function test_BorrowAllowed_True_Where_EdgeCaseBugDebtNonZeroFuzz(uint timeElapsed)
public
{
vm.prank(gov);
borrowController.setDailyLimit(address(market), 2000 ether);
uint timeElapsed = timeElapsed % 365 days;
uint testAmount = 1e18;
gibWeth(user, testAmount);
uint halfBorrow = getMaxBorrowAmount(testAmount) / 2;
gibDOLA(address(market), halfBorrow * 2);
vm.startPrank(user, user);
deposit(testAmount);
market.borrow(halfBorrow);
market.repay(user, halfBorrow-1);
vm.stopPrank();

assertEq(market.debts(user), 1, "User debt not 1");
assertEq(dbr.balanceOf(user), 0, "DBR balance of user is not 0 before time skip");
vm.warp(block.timestamp + timeElapsed);
ethFeed.changeUpdatedAt(block.timestamp);
vm.prank(gov);
dbr.addMinter(address(borrowController));
vm.prank(address(market), user);
assertTrue(
borrowController.borrowAllowed(user, user, 1)
);
vm.startPrank(user, user);
assertGt(market.getCreditLimit(user), 0, "User has no credit limit");
market.borrow(market.getCreditLimit(user) / 100);
assertLe(dbr.deficitOf(user), timeElapsed * 1, "Deficit of user more than expected");
assertEq(dbr.balanceOf(user), 0, "DBR balance of user is not 0");
}

function test_BorrowAllowed_False_Where_EdgeCaseBugTriggeredWithMinimalDebt()
public
{
vm.prank(gov);
borrowController.setDailyLimit(address(market), 2000 ether);
uint testAmount = 1e18;
gibWeth(user, testAmount);
uint maxBorrow = getMaxBorrowAmount(testAmount);
gibDOLA(address(market), maxBorrow);
vm.startPrank(user, user);
deposit(testAmount);
market.borrow(maxBorrow);
market.repay(user, maxBorrow-1);
vm.stopPrank();

vm.warp(block.timestamp + 1);
vm.prank(address(market), user);
assertFalse(
borrowController.borrowAllowed(user, user, 1),
"User was allowed to borrow"
);
}


function test_BorrowAllowed_True_Where_EdgeCaseBugTriggeredAndAMinter()
public
{
Expand Down Expand Up @@ -231,6 +371,22 @@ contract BorrowControllerTest is FiRMBaseTest {
vm.stopPrank();
}

function test_borrowAllowed_False_whenCalledByEIP7702tx() public {
uint ALICE_PK = 1;
address ALICE = vm.addr(ALICE_PK);
BatchApprove implementation = new BatchApprove(address(dbr));
//Simulated borrow
uint testAmount = 1e18;
gibWeth(ALICE, testAmount);
uint maxBorrow = getMaxBorrowAmount(testAmount);
gibDOLA(address(market), maxBorrow);
vm.signAndAttachDelegation(address(implementation), ALICE_PK);
vm.startPrank(ALICE, ALICE);
vm.expectRevert("Denied by borrow controller");
BatchApprove(ALICE).approveDepositAndBorrow(address(market), testAmount, maxBorrow);
vm.stopPrank();
}

function test_BorrowAllowed_False_Where_DebtIsBelowMininimum() public {
vm.startPrank(gov);
borrowController.setMinDebt(address(market), 1 ether);
Expand Down Expand Up @@ -312,6 +468,9 @@ contract BorrowControllerTest is FiRMBaseTest {
);
}


//Access Control

function test_setDailyLimit() public {
vm.expectRevert(onlyOperatorLowercase);
borrowController.setDailyLimit(address(0), 1);
Expand All @@ -321,8 +480,6 @@ contract BorrowControllerTest is FiRMBaseTest {
assertEq(borrowController.dailyLimits(address(0)), 1);
}

//Access Control

function test_accessControl_setOperator() public {
vm.prank(gov);
borrowController.setOperator(address(0));
Expand Down
4 changes: 1 addition & 3 deletions test/marketForkTests/MarketBaseForkTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,14 @@ abstract contract MarketBaseForkTest is MarketForkTest {
deposit(testAmount);

uint borrowAmount = market.getCreditLimit(user) / 2;
uint timestamp = block.timestamp;
vm.warp(timestamp + 0.5 hours);
uint dbrBal = dbr.balanceOf(user);
market.borrow(borrowAmount);
assertEq(
dbrBal,
testAmount,
"DBR balance burned immediately after borrow"
);
vm.warp(timestamp + 0.5 hours + 1);
vm.warp(block.timestamp + 1);
dbr.accrueDueTokens(user);
assertEq(
dbr.balanceOf(user),
Expand Down