Skip to content

Commit

Permalink
Dev (#97)
Browse files Browse the repository at this point in the history
* Add GOhmTokenEscrow
* Add ALE
* Add Cvx Crv feed and escrow
* Add Cvx Fxs feed and escrow
* Add DAI feed and escrow
* Add wstETH feed
* Add st-yETH feed
* Add wBTC feed
* Add GovToken escrow
* Add DbrDistributor
------------------------------------------------------------------------------------------------------------------
Co-authored-by: webmass <[email protected]>
Co-authored-by: Nour Haridy <[email protected]>
Co-authored-by: lacoop6tu <[email protected]>
Co-authored-by: lacoop6tu <[email protected]>
Co-authored-by: 0xtj24 <[email protected]>
Co-authored-by: 0xtj24 <[email protected]>
  • Loading branch information
08xmt authored Nov 13, 2024
1 parent cf6b0a4 commit 6cd9f06
Show file tree
Hide file tree
Showing 159 changed files with 34,828 additions and 1,503 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main and dev branch
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
name: test

jobs:
check:
name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run tests
env:
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
run: forge test -vvv
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
cache/
out/

lcov.info
broadcast/
coverage/
foundry.toml
lcov.info
.env
*.sh
*.info
temp/
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate.git
Binary file added CoverageReport.pdf
Binary file not shown.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,12 @@ Each wallet has a unique escrow contract, one for every deployed market. Their p
### Fed
Feds are a class of contracts in the Inverse Finance ecosystem responsible for minting DOLA in a way that preserves the peg and can't be easily abused. In the FiRM protocol, the role of the Fed is to supply and remove DOLA to and from markets.

# Set-up
FiRM is built using [Foundry](https://book.getfoundry.sh/getting-started/installation).

## Test
To run tests, you must first add an `RPC_MAINNET` rpc endpoint to your local `.env` file.

To generate a coverage summary run `forge coverage`, and `forge coverage --reprot lcov` for a full coverage report. To generate a human readable document from the lcov.info file, use the following command:
`genhtml -o report --branch-coverage`

14 changes: 11 additions & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
[profile.default]
src = 'src'
out = 'out'
evm_version = "cancun"
libs = ['lib']
optimizer = true
optimizer_runs = 10000
out = 'out'
solc = "0.8.20"
src = 'src'
[rpc_endpoints]
mainnet = "${RPC_MAINNET}"
[etherscan]
mainnet = {key = "${ETHERSCAN_API_KEY}"}

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 63 files
+1 −0 .gitattributes
+128 −0 .github/workflows/ci.yml
+31 −0 .github/workflows/sync.yml
+0 −26 .github/workflows/tests.yml
+1 −1 .gitignore
+0 −3 .gitmodules
+1 −1 LICENSE-APACHE
+1 −1 LICENSE-MIT
+9 −5 README.md
+21 −0 foundry.toml
+0 −1 lib/ds-test
+16 −0 package.json
+635 −0 scripts/vm.py
+35 −0 src/Base.sol
+21 −33 src/Script.sol
+669 −0 src/StdAssertions.sol
+252 −0 src/StdChains.sol
+817 −0 src/StdCheats.sol
+15 −0 src/StdError.sol
+113 −0 src/StdInvariant.sol
+179 −0 src/StdJson.sol
+43 −0 src/StdMath.sol
+473 −0 src/StdStorage.sol
+333 −0 src/StdStyle.sol
+179 −0 src/StdToml.sol
+226 −0 src/StdUtils.sol
+31 −765 src/Test.sol
+1,738 −162 src/Vm.sol
+406 −386 src/console2.sol
+105 −0 src/interfaces/IERC1155.sol
+12 −0 src/interfaces/IERC165.sol
+43 −0 src/interfaces/IERC20.sol
+190 −0 src/interfaces/IERC4626.sol
+164 −0 src/interfaces/IERC721.sol
+73 −0 src/interfaces/IMulticall3.sol
+234 −0 src/mocks/MockERC20.sol
+231 −0 src/mocks/MockERC721.sol
+13,248 −0 src/safeconsole.sol
+0 −12 src/test/Script.t.sol
+0 −599 src/test/StdAssertions.t.sol
+0 −213 src/test/StdCheats.t.sol
+0 −200 src/test/StdMath.t.sol
+0 −321 src/test/StdStorage.t.sol
+145 −0 test/StdAssertions.t.sol
+223 −0 test/StdChains.t.sol
+618 −0 test/StdCheats.t.sol
+14 −18 test/StdError.t.sol
+49 −0 test/StdJson.t.sol
+212 −0 test/StdMath.t.sol
+463 −0 test/StdStorage.t.sol
+110 −0 test/StdStyle.t.sol
+49 −0 test/StdToml.t.sol
+342 −0 test/StdUtils.t.sol
+15 −0 test/Vm.t.sol
+10 −0 test/compilation/CompilationScript.sol
+10 −0 test/compilation/CompilationScriptBase.sol
+10 −0 test/compilation/CompilationTest.sol
+10 −0 test/compilation/CompilationTestBase.sol
+187 −0 test/fixtures/broadcast.log.json
+8 −0 test/fixtures/test.json
+6 −0 test/fixtures/test.toml
+441 −0 test/mocks/MockERC20.t.sol
+721 −0 test/mocks/MockERC721.t.sol
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at a241f0
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at c89230
6 changes: 6 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
solmate/=lib/solmate/src/
15 changes: 15 additions & 0 deletions scripts/ERC4626Helper.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";
import {ERC4626Helper} from "src/util/ERC4626Helper.sol";
import {ConfigAddr} from "test/ConfigAddr.sol";

contract Deploy is Script, ConfigAddr {
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.createSelectFork(vm.envString("RPC_MAINNET"));
vm.broadcast(deployerPrivateKey);
ERC4626Helper helper = new ERC4626Helper(gov, pauseGuardian);
}
}
54 changes: 54 additions & 0 deletions scripts/borrowControllerSetup.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import {Market} from "src/Market.sol";
import {BorrowController} from "src/BorrowController.sol";
import "src/DBR.sol";

interface IBorrowController {
function setDailyLimit(address market, uint newLimit) external;
function dailyLimits(address market) external returns(uint);
function allow(address market) external;
function setOperator(address gov) external;
}


contract borrowControllerSetup is Script {
address gov = 0x926dF14a23BE491164dCF93f4c468A50ef659D5B;
address deployerAddress = 0x11EC78492D53c9276dD7a184B1dbfB34E50B710D;
IBorrowController oldBorrowController = IBorrowController(0x20C7349f6D6A746a25e66f7c235E96DAC880bc0D);
BorrowController newBorrowController; // = BorrowController(0x81ff13c46f363D13fC25FB801a4335c6097B7862);
DolaBorrowingRights DBR = DolaBorrowingRights(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);
address[] markets =
[
0x93685185666c8D34ad4c574B3DBF41231bbfB31b, //cvxFxs
0x3474ad0e3a9775c9F68B415A7a9880B0CAB9397a, //cvxCrv
0x63fAd99705a255fE2D500e498dbb3A9aE5AA1Ee8, //crv
0x7Cd3ab8354289BEF52c84c2BF0A54E3608e66b37, //gohm
0xb516247596Ca36bf32876199FBdCaD6B3322330B, //inv
0x743A502cf0e213F6FEE56cD9C6B03dE7Fa951dCf, //steth
0x63Df5e23Db45a2066508318f172bA45B9CD37035, //weth
0x27b6c301Fd441f3345d61B7a4245E1F823c3F9c4 //stycrv
];

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

for(uint i; i < markets.length; ++i){
address market = markets[i];
require(DBR.markets(market), "Not a market");
uint oldLimit = oldBorrowController.dailyLimits(market);
vm.broadcast(deployerPrivateKey);
newBorrowController.setDailyLimit(market, oldLimit);
}

//Add helper contract to allowList
vm.broadcast(deployerPrivateKey);
newBorrowController.allow(0xae8165f37FC453408Fb1cd1064973df3E6499a76);

//Transfer ownership to gov
vm.broadcast(deployerPrivateKey);
newBorrowController.setOperator(gov);
}
}
93 changes: 88 additions & 5 deletions src/BorrowController.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "src/DBR.sol";

interface IChainlinkFeed {
function decimals() external view returns (uint8);
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80);
}

interface IOracle {
function feeds(address token) external view returns(IChainlinkFeed, uint8);
}

interface IMarket {
function collateral() external view returns(address);
function oracle() external view returns(IOracle);
function debts(address) external view returns(uint);
}

/**
@title Borrow Controller
Expand All @@ -8,12 +24,16 @@ pragma solidity ^0.8.13;
contract BorrowController {

address public operator;
DolaBorrowingRights public immutable DBR;
mapping(address => uint) public minDebts;
mapping(address => bool) public contractAllowlist;
mapping(address => uint) public dailyLimits;
mapping(address => uint) public stalenessThreshold;
mapping(address => mapping(uint => uint)) public dailyBorrows;

constructor(address _operator) {
constructor(address _operator, address _DBR) {
operator = _operator;
DBR = DolaBorrowingRights(_DBR);
}

modifier onlyOperator {
Expand All @@ -35,28 +55,45 @@ contract BorrowController {

/**
@notice Denies a contract to use the associated market
@param deniedContract The addres of the denied contract
@param deniedContract The address of the denied contract
*/
function deny(address deniedContract) public onlyOperator { contractAllowlist[deniedContract] = false; }

/**
@notice Sets the daily borrow limit for a specific market
@param market The addres of the market contract
@param market The address of the market contract
@param limit The daily borrow limit amount
*/
function setDailyLimit(address market, uint limit) public onlyOperator { dailyLimits[market] = limit; }

/**
@notice Sets the staleness threshold for Chainlink feeds
@param newStalenessThreshold The new staleness threshold denominated in seconds
@dev Only callable by operator
*/
function setStalenessThreshold(address market, uint newStalenessThreshold) public onlyOperator { stalenessThreshold[market] = newStalenessThreshold; }

/**
@notice sets the market specific minimum amount a debt a borrower needs to take on.
@param market The market to set the minimum debt for.
@param newMinDebt The new minimum amount of debt.
@dev This is to mitigate the creation of positions which are uneconomical to liquidate. Only callable by operator.
*/
function setMinDebt(address market, uint newMinDebt) public onlyOperator {minDebts[market] = newMinDebt; }

/**
@notice Checks if a borrow is allowed
@dev Currently the borrowController checks if contracts are part of an allow list and enforces a daily limit
@param msgSender The message sender trying to borrow
@param borrower The address being borrowed on behalf of
@param amount The amount to be borrowed
@return A boolean that is true if borrowing is allowed and false if not.
*/
function borrowAllowed(address msgSender, address, uint amount) public returns (bool) {
uint day = block.timestamp / 1 days;
function borrowAllowed(address msgSender, address borrower, uint amount) public returns (bool) {
uint dailyLimit = dailyLimits[msg.sender];
//Check if market exceeds daily limit
if(dailyLimit > 0) {
uint day = block.timestamp / 1 days;
if(dailyBorrows[msg.sender][day] + amount > dailyLimit) {
return false;
} else {
Expand All @@ -66,6 +103,23 @@ 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){
//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;
//If the contract is not a DBR minter, it should disallow borrowing for edgecase users
if(!DBR.minters(address(this))) return false;
//Mint user deficit caused by edge case bug
DBR.mint(borrower, deficit);
}
//If the debt is below the minimum debt threshold, deny borrow
if(isBelowMinDebt(msg.sender, borrower, amount)) return false;
//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;
return contractAllowlist[msgSender];
}
Expand All @@ -86,4 +140,33 @@ contract BorrowController {
}
}
}

/**
* @notice Checks if the price for the given market is stale.
* @param market The address of the market for which the price staleness is to be checked.
* @return bool Returns true if the price is stale, false otherwise.
*/
function isPriceStale(address market) public view returns(bool){
uint marketStalenessThreshold = stalenessThreshold[market];
if(marketStalenessThreshold == 0) return false;
IOracle oracle = IMarket(market).oracle();
(IChainlinkFeed feed,) = oracle.feeds(IMarket(market).collateral());
(,,,uint updatedAt,) = feed.latestRoundData();
return block.timestamp - updatedAt > marketStalenessThreshold;
}

/**
* @notice Checks if the borrower's debt in the given market is below the minimum debt after adding the specified amount.
* @param market The address of the market for which the borrower's debt is to be checked.
* @param borrower The address of the borrower whose debt is to be checked.
* @param amount The amount to be added to the borrower's current debt before checking against the minimum debt.
* @return bool Returns true if the borrower's debt after adding the amount is below the minimum debt, false otherwise.
*/
function isBelowMinDebt(address market, address borrower, uint amount) public view returns(bool){
//Optimization to check if borrow amount itself is higher than the minimum
//This avoids an expensive lookup in the market
uint minDebt = minDebts[market];
if(amount >= minDebt) return false;
return IMarket(market).debts(borrower) + amount < minDebt;
}
}
Loading

0 comments on commit 6cd9f06

Please sign in to comment.