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

Audit Changes #2

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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 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
Loading