Skip to content

Commit

Permalink
Audit Changes (#2)
Browse files Browse the repository at this point in the history
* switch to borrow disabled

* readme update

* update natspec

* add a check to make sure user can repay loans even with borrow disabled

* make sure we cant borrow on any asset

* add GNO and sDAI

* use actual errors
  • Loading branch information
hexonaut authored Mar 14, 2024
1 parent 0788632 commit 87e451b
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 300 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[foundry]: https://getfoundry.sh/
[foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg

This module registers pegged asset oracles and will trigger a lockdown mode for SparkLend if certain price thresholds are met. For example, if WBTC/BTC is observed to reach a market price of 0.95 and the threshold for this oracle is set to 0.95 then anyone can permissionlessly trigger to set SparkLend into lockdown mode in which all collateral assets have their LTVs set to 0 and all borrowable assets are frozen.
This module registers pegged asset oracles and will trigger a lockdown mode for SparkLend if certain price thresholds are met. For example, if WBTC/BTC is observed to reach a market price of 0.95 and the threshold for this oracle is set to 0.95 then anyone can permissionlessly trigger to set SparkLend into lockdown mode which prevents new borrows on all assets.

The reasoning behind this is to limit the damage in the event of extreme market conditions. Depegging assets may be temporary, but there is no harm in an excess of caution in these situations. Users can still top up collateral and repay/withdraw in lockdown mode. This just prevents further borrowing to limit downside exposure to lenders.

Expand Down
21 changes: 4 additions & 17 deletions src/KillSwitchOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,10 @@ contract KillSwitchOracle is IKillSwitchOracle, Ownable {
config.getPaused()
) continue;

if (config.getLtv() > 0) {
// This asset is being used as collateral
// We only want to disable new borrows against this to allow users
// to top up their positions to prevent getting liquidated
poolConfigurator.configureReserveAsCollateral(
asset,
0,
config.getLiquidationThreshold(),
config.getLiquidationBonus()
);

emit AssetLTV0(asset);
} else if (config.getBorrowingEnabled()) {
// This is a borrow-only asset
poolConfigurator.setReserveFreeze(asset, true);

emit AssetFrozen(asset);
if (config.getBorrowingEnabled()) {
poolConfigurator.setReserveBorrowing(asset, false);

emit BorrowDisabled(asset);
}
}
}
Expand Down
17 changes: 5 additions & 12 deletions src/interfaces/IKillSwitchOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,10 @@ interface IKillSwitchOracle {
event Trigger(address indexed oracle, uint256 threshold, uint256 price);

/**
* @dev Emitted when the LTV (Loan to Value) of an asset is set to 0.
* @param asset The address of the asset whose LTV is set to 0.
* @dev Emitted when the borrow is disabled for an asset.
* @param asset The address of the asset whose borrow has been disabled.
*/
event AssetLTV0(address indexed asset);

/**
* @dev Emitted when an asset is frozen.
* @param asset The address of the asset that is frozen.
*/
event AssetFrozen(address indexed asset);
event BorrowDisabled(address indexed asset);

/**
* @dev Emitted when the contract is reset.
Expand Down Expand Up @@ -138,9 +132,8 @@ interface IKillSwitchOracle {
* @notice Permissionless function to trigger the kill switch.
* @dev If the kill switch has not been triggered, the oracle threshold has been defined,
* and the oracle is below the threshold, the kill switch is triggered. This will
* set LTV to 0 on any collateral asset preventing new loans and freeze any freeze
* any other asset which can be borrowed. This function can only be called once
* and will require a call to `reset()` by the owner to be called again.
* disable borrowing on all assets. This function can only be called once and will
* require a call to `reset()` by the owner to be called again.
* @param oracle The address of the oracle which is below the threshold.
*/
function trigger(address oracle) external;
Expand Down
81 changes: 19 additions & 62 deletions test/KillSwitchOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ contract KillSwitchOracleTestBase is Test {
event SetOracle(address indexed oracle, uint256 threshold);
event DisableOracle(address indexed oracle);
event Trigger(address indexed oracle, uint256 threshold, uint256 price);
event AssetLTV0(address indexed asset);
event AssetFrozen(address indexed asset);
event BorrowDisabled(address indexed asset);
event Reset();

MockPoolAddressesProvider poolAddressesProvider;
Expand All @@ -40,7 +39,6 @@ contract KillSwitchOracleTestBase is Test {
address asset3 = makeAddr("asset3");
address asset4 = makeAddr("asset4");
address asset5 = makeAddr("asset5");
address asset6 = makeAddr("asset6");

function setUp() public {
pool = new MockPool();
Expand Down Expand Up @@ -262,9 +260,6 @@ contract KillSwitchOracleTriggerTests is KillSwitchOracleTestBase {
bool frozen;
bool paused;
bool borrowingEnabled;
uint256 ltv;
uint256 liquidationThreshold;
uint256 liquidationBonus;
}

using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
Expand Down Expand Up @@ -312,80 +307,54 @@ contract KillSwitchOracleTriggerTests is KillSwitchOracleTestBase {
}

function test_trigger() public {
ReserveConfigParams[6] memory reserves = [
// Collateral asset /w borrow enabled (Ex. ETH, wstETH)
ReserveConfigParams[5] memory reserves = [
// Asset with borrow enabled (Ex. ETH, wstETH, DAI)
ReserveConfigParams({
asset: asset1,
active: true,
frozen: false,
paused: false,
borrowingEnabled: true,
ltv: 80_00,
liquidationThreshold: 83_00,
liquidationBonus: 105_00
borrowingEnabled: true
}),
// Collateral asset /w no borrow (Ex. sDAI)
// Collateral-only asset (Ex. sDAI)
ReserveConfigParams({
asset: asset2,
active: true,
frozen: false,
paused: false,
borrowingEnabled: false,
ltv: 80_00,
liquidationThreshold: 83_00,
liquidationBonus: 105_00
borrowingEnabled: false
}),
// Borrow-only asset (Ex. DAI, USDC)
// Frozen asset (Ex. GNO)
ReserveConfigParams({
asset: asset3,
active: true,
frozen: false,
paused: false,
borrowingEnabled: true,
ltv: 0,
liquidationThreshold: 0,
liquidationBonus: 0
}),
// Frozen/LTV0 asset (Ex. GNO)
ReserveConfigParams({
asset: asset4,
active: true,
frozen: true,
paused: false,
borrowingEnabled: true,
ltv: 0,
liquidationThreshold: 25_00,
liquidationBonus: 110_00
borrowingEnabled: true
}),
// Paused asset
ReserveConfigParams({
asset: asset5,
asset: asset4,
active: true,
frozen: false,
paused: true,
borrowingEnabled: true,
ltv: 80_00,
liquidationThreshold: 83_00,
liquidationBonus: 105_00
borrowingEnabled: true
}),
// Inactive asset
ReserveConfigParams({
asset: asset6,
asset: asset5,
active: false,
frozen: false,
paused: false,
borrowingEnabled: false,
ltv: 0,
liquidationThreshold: 0,
liquidationBonus: 0
borrowingEnabled: false
})
];

for (uint256 i = 0; i < reserves.length; i++) {
_initReserve(reserves[i]);
}

assertEq(pool.getReservesList().length, 6);
assertEq(pool.getReservesList().length, 5);

vm.prank(owner);
killSwitchOracle.setOracle(address(oracle1), 0.99e8);
Expand All @@ -394,18 +363,12 @@ contract KillSwitchOracleTriggerTests is KillSwitchOracleTestBase {
vm.expectEmit(address(killSwitchOracle));
emit Trigger(address(oracle1), 0.99e8, 0.98e8);
vm.expectEmit(address(killSwitchOracle));
emit AssetLTV0(asset1);
vm.expectEmit(address(killSwitchOracle));
emit AssetLTV0(asset2);
vm.expectEmit(address(killSwitchOracle));
emit AssetFrozen(asset3);
emit BorrowDisabled(asset1);
vm.prank(randomAddress); // Permissionless call
killSwitchOracle.trigger(address(oracle1));

// Only update what has changed
reserves[0].ltv = 0;
reserves[1].ltv = 0;
reserves[2].frozen = true;
reserves[0].borrowingEnabled = false;

for (uint256 i = 0; i < reserves.length; i++) {
_assertReserve(reserves[i]);
Expand All @@ -419,23 +382,17 @@ contract KillSwitchOracleTriggerTests is KillSwitchOracleTestBase {
configuration.setFrozen(params.frozen);
configuration.setPaused(params.paused);
configuration.setBorrowingEnabled(params.borrowingEnabled);
configuration.setLtv(params.ltv);
configuration.setLiquidationThreshold(params.liquidationThreshold);
configuration.setLiquidationBonus(params.liquidationBonus);

pool.__addReserve(params.asset, configuration);
}

function _assertReserve(ReserveConfigParams memory params) internal {
DataTypes.ReserveConfigurationMap memory configuration = pool.getConfiguration(params.asset);

assertEq(configuration.getActive(), params.active);
assertEq(configuration.getFrozen(), params.frozen);
assertEq(configuration.getPaused(), params.paused);
assertEq(configuration.getBorrowingEnabled(), params.borrowingEnabled);
assertEq(configuration.getLtv(), params.ltv);
assertEq(configuration.getLiquidationThreshold(), params.liquidationThreshold);
assertEq(configuration.getLiquidationBonus(), params.liquidationBonus);
assertEq(configuration.getActive(), params.active);
assertEq(configuration.getFrozen(), params.frozen);
assertEq(configuration.getPaused(), params.paused);
assertEq(configuration.getBorrowingEnabled(), params.borrowingEnabled);
}

}
Loading

0 comments on commit 87e451b

Please sign in to comment.