Skip to content

Commit

Permalink
[SC-288] Add pause support and external authorized contract (#5)
Browse files Browse the repository at this point in the history
* add in pause support

* add wards

* add pause spells and rename to execute() to be more generic

* readme and doc fixing

* fix readme

* add integration tests for pause; add interface for execute-once spells and fix pragma

* add multisig integration test

* formating and naming fixes

* more accurate prod env for integration test of spell

* dont need extra role auth
  • Loading branch information
hexonaut authored Dec 6, 2023
1 parent 0e551fa commit 1c480e1
Showing 11 changed files with 846 additions and 182 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -9,18 +9,26 @@

## Overview

This repo contains three contracts:
This repo contains five contracts:

### `src/SparkLendFreezerMom.sol`
A contract that will have `RISK_ADMIN_ROLE` in SparkLend, and has two functions:
- `freezeAllMarkets`: Freezes all markets in SparkLend, callable by the `hat` in the Chief or by the PauseProxy in MakerDAO.
- `freezeMarket`: Freezes a single market in SparkLend, callable by the `hat` in the Chief or by the PauseProxy in MakerDAO.
A contract that will have `RISK_ADMIN_ROLE` & `EMERGENCY_ADMIN_ROLE` in SparkLend, and has four functions:
- `freezeAllMarkets`: Freezes all markets in SparkLend, callable by the `hat` in the Chief, an authorized contract (ward) or by the PauseProxy in MakerDAO.
- `freezeMarket`: Freezes a single market in SparkLend, callable by the `hat` in the Chief, an authorized contract (ward) or by the PauseProxy in MakerDAO.
- `pauseAllMarkets`: Pauses all markets in SparkLend, callable by the `hat` in the Chief, an authorized contract (ward) or by the PauseProxy in MakerDAO.
- `pauseMarket`: Pauses a single market in SparkLend, callable by the `hat` in the Chief, an authorized contract (ward) or by the PauseProxy in MakerDAO.

### `src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol`
A spell that can be set as the `hat` in the Chief to freeze all markets in SparkLend by calling `freezeAllMarkets` in `SparkLendFreezerMom`.
A spell that can be set as the `hat` in the Chief to freeze all markets in SparkLend by calling `freezeAllMarkets(true)` in `SparkLendFreezerMom`.

### `src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol`
A spell that can be set as the `hat` in the Chief to freeze all markets in SparkLend by calling `freezeMarket` in `SparkLendFreezerMom`. A separate spell is needed for each market, with the reserve being declared in the constructor.
A spell that can be set as the `hat` in the Chief to freeze a specific market in SparkLend by calling `freezeMarket(reserve, true)` in `SparkLendFreezerMom`. A separate spell is needed for each market, with the reserve being declared in the constructor.

### `src/spells/EmergencySpell_SparkLend_PauseAllAssets.sol`
A spell that can be set as the `hat` in the Chief to pauses all markets in SparkLend by calling `pauseAllMarkets(true)` in `SparkLendFreezerMom`.

### `src/spells/EmergencySpell_SparkLend_PauseSingleAsset.sol`
A spell that can be set as the `hat` in the Chief to pause a specific market in SparkLend by calling `pauseMarket(reserve, true)` in `SparkLendFreezerMom`. A separate spell is needed for each market, with the reserve being declared in the constructor.

## Testing
To run the tests, run `forge test`.
54 changes: 38 additions & 16 deletions src/SparkLendFreezerMom.sol
Original file line number Diff line number Diff line change
@@ -3,18 +3,13 @@ pragma solidity ^0.8.13;

import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol";

import { IPool } from "aave-v3-core/contracts/interfaces/IPool.sol";
import { IPoolConfigurator } from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol";

interface AuthorityLike {
function canCall(address src, address dst, bytes4 sig) external view returns (bool);
}

interface PoolConfiguratorLike {
function setReserveFreeze(address asset, bool freeze) external;
}

interface PoolLike {
function getReservesList() external view returns (address[] memory);
}

contract SparkLendFreezerMom is ISparkLendFreezerMom {

/**********************************************************************************************/
@@ -26,6 +21,8 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom {

address public override authority;
address public override owner;

mapping(address => uint256) public override wards;

constructor(address poolConfigurator_, address pool_) {
poolConfigurator = poolConfigurator_;
@@ -64,23 +61,48 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom {
owner = owner_;
}

function rely(address usr) external override onlyOwner {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external override onlyOwner {
wards[usr] = 0;
emit Deny(usr);
}

/**********************************************************************************************/
/*** Auth Functions ***/
/**********************************************************************************************/

function freezeAllMarkets() external override auth {
address[] memory reserves = PoolLike(pool).getReservesList();
function freezeAllMarkets(bool freeze) external override auth {
address[] memory reserves = IPool(pool).getReservesList();

for (uint256 i = 0; i < reserves.length; i++) {
address reserve = reserves[i];
IPoolConfigurator(poolConfigurator).setReserveFreeze(reserve, freeze);
emit FreezeMarket(reserve, freeze);
}
}

function freezeMarket(address reserve, bool freeze) external override auth {
IPoolConfigurator(poolConfigurator).setReserveFreeze(reserve, freeze);
emit FreezeMarket(reserve, freeze);
}

function pauseAllMarkets(bool pause) external override auth {
address[] memory reserves = IPool(pool).getReservesList();

for (uint256 i = 0; i < reserves.length; i++) {
address reserve = reserves[i];
PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true);
emit FreezeMarket(reserve);
IPoolConfigurator(poolConfigurator).setReservePause(reserve, pause);
emit PauseMarket(reserve, pause);
}
}

function freezeMarket(address reserve) external override auth {
PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true);
emit FreezeMarket(reserve);
function pauseMarket(address reserve, bool pause) external override auth {
IPoolConfigurator(poolConfigurator).setReservePause(reserve, pause);
emit PauseMarket(reserve, pause);
}

/**********************************************************************************************/
@@ -90,7 +112,7 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom {
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
} else if (src == owner || wards[src] == 1) {
return true;
} else if (authority == address(0)) {
return false;
16 changes: 16 additions & 0 deletions src/interfaces/IExecuteOnceSpell.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;

interface IExecuteOnceSpell {

/**
* @dev Returns true if the spell has been executed.
*/
function executed() external view returns (bool);

/**
* @dev Executes the spell. Can only be called once.
*/
function execute() external;

}
82 changes: 71 additions & 11 deletions src/interfaces/ISparkLendFreezerMom.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;
pragma solidity >=0.8.0;

interface ISparkLendFreezerMom {

@@ -11,8 +11,17 @@ interface ISparkLendFreezerMom {
* @dev Event to log the freezing of a given market in SparkLend.
* @dev NOTE: This event will fire even if the market is already frozen.
* @param reserve The address of the market reserve.
* @param freeze A boolean indicating whether the market is frozen or unfrozen.
*/
event FreezeMarket(address indexed reserve);
event FreezeMarket(address indexed reserve, bool freeze);

/**
* @dev Event to log the pausing of a given market in SparkLend.
* @dev NOTE: This event will fire even if the market is already paused.
* @param reserve The address of the market reserve.
* @param pause A boolean indicating whether the market is paused or unpaused.
*/
event PauseMarket(address indexed reserve, bool pause);

/**
* @dev Event to log the setting of a new owner.
@@ -28,6 +37,18 @@ interface ISparkLendFreezerMom {
*/
event SetAuthority(address indexed oldAuthority, address indexed newAuthority);

/**
* @dev Authorize a contract to trigger this mom.
* @param usr The address to authorize.
*/
event Rely(address indexed usr);

/**
* @dev Deauthorize a contract to trigger this mom.
* @param usr The address to deauthorize.
*/
event Deny(address indexed usr);

/**********************************************************************************************/
/*** Storage Variables ***/
/**********************************************************************************************/
@@ -56,6 +77,12 @@ interface ISparkLendFreezerMom {
*/
function owner() external view returns (address);

/**
* @dev Returns if an address is authorized to trigger this mom (or not).
* @return 1 if authorized, 0 if not.
*/
function wards(address usr) external view returns (uint256);

/**********************************************************************************************/
/*** Owner Functions ***/
/**********************************************************************************************/
@@ -72,25 +99,58 @@ interface ISparkLendFreezerMom {
*/
function setOwner(address owner) external;

/**
* @dev Authorize a contract to trigger this mom.
* @param usr The address to authorize.
*/
function rely(address usr) external;

/**
* @dev Deauthorize a contract to trigger this mom.
* @param usr The address to deauthorize.
*/
function deny(address usr) external;

/**********************************************************************************************/
/*** Auth Functions ***/
/**********************************************************************************************/

/**
* @dev Function to freeze a specified market. Permissioned using the isAuthorized function
* which allows the owner, the freezer contract itself, or the `hat` in the Chief
* to call the function. Note that the `authority` in this contract is assumed to be
* the Chief in the MakerDAO protocol.
* which allows the owner, a ward, the freezer contract itself, or the `hat` in the
* Chief to call the function. Note that the `authority` in this contract is assumed to
* be the Chief in the MakerDAO protocol.
* @param reserve The address of the market to freeze.
* @param freeze A boolean indicating whether to freeze or unfreeze the market.
*/
function freezeMarket(address reserve, bool freeze) external;

/**
* @dev Function to freeze all markets. Permissioned using the isAuthorized function
* which allows the owner, a ward, the freezer contract itself, or the `hat` in the
* Chief to call the function. Note that the `authority` in this contract is assumed to
* be the Chief in the MakerDAO protocol.
* @param freeze A boolean indicating whether to freeze or unfreeze the market.
*/
function freezeAllMarkets(bool freeze) external;

/**
* @dev Function to pause a specified market. Permissioned using the isAuthorized function
* which allows the owner, a ward, the freezer contract itself, or the `hat` in the
* Chief to call the function. Note that the `authority` in this contract is assumed to
* be the Chief in the MakerDAO protocol.
* @param reserve The address of the market to pause.
* @param pause A boolean indicating whether to pause or unpause the market.
*/
function freezeMarket(address reserve) external;
function pauseMarket(address reserve, bool pause) external;

/**
* @dev Function to freeze all markets. Permissioned using the isAuthorized function
* which allows the owner, the freezer contract itself, or the `hat` in the Chief
* to call the function. Note that the `authority` in this contract is assumed to be
* the Chief in the MakerDAO protocol.
* @dev Function to pause all markets. Permissioned using the isAuthorized function
* which allows the owner, a ward, the freezer contract itself, or the `hat` in the
* Chief to call the function. Note that the `authority` in this contract is assumed to
* be the Chief in the MakerDAO protocol.
* @param pause A boolean indicating whether to pause or unpause the market.
*/
function freezeAllMarkets() external;
function pauseAllMarkets(bool pause) external;

}
13 changes: 7 additions & 6 deletions src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import { IExecuteOnceSpell } from "src/interfaces/IExecuteOnceSpell.sol";
import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol";

contract EmergencySpell_SparkLend_FreezeAllAssets {
contract EmergencySpell_SparkLend_FreezeAllAssets is IExecuteOnceSpell {

address public immutable sparkLendFreezerMom;

bool public executed;
bool public override executed;

constructor(address sparklendFreezerMom_) {
sparkLendFreezerMom = sparklendFreezerMom_;
constructor(address sparkLendFreezerMom_) {
sparkLendFreezerMom = sparkLendFreezerMom_;
}

function freeze() external {
function execute() external override {
require(!executed, "FreezeAllAssetsSpell/already-executed");
executed = true;
ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets();
ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets(true);
}

}
13 changes: 7 additions & 6 deletions src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import { IExecuteOnceSpell } from "src/interfaces/IExecuteOnceSpell.sol";
import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol";

contract EmergencySpell_SparkLend_FreezeSingleAsset {
contract EmergencySpell_SparkLend_FreezeSingleAsset is IExecuteOnceSpell {

address public immutable sparkLendFreezerMom;
address public immutable reserve;

bool public executed;
bool public override executed;

constructor(address sparklendFreezerMom_, address reserve_) {
sparkLendFreezerMom = sparklendFreezerMom_;
constructor(address sparkLendFreezerMom_, address reserve_) {
sparkLendFreezerMom = sparkLendFreezerMom_;
reserve = reserve_;
}

function freeze() external {
function execute() external override {
require(!executed, "FreezeSingleAssetSpell/already-executed");
executed = true;
ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve);
ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve, true);
}

}
23 changes: 23 additions & 0 deletions src/spells/EmergencySpell_SparkLend_PauseAllAssets.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import { IExecuteOnceSpell } from "src/interfaces/IExecuteOnceSpell.sol";
import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol";

contract EmergencySpell_SparkLend_PauseAllAssets is IExecuteOnceSpell {

address public immutable sparkLendFreezerMom;

bool public override executed;

constructor(address sparkLendFreezerMom_) {
sparkLendFreezerMom = sparkLendFreezerMom_;
}

function execute() external override {
require(!executed, "PauseAllAssetsSpell/already-executed");
executed = true;
ISparkLendFreezerMom(sparkLendFreezerMom).pauseAllMarkets(true);
}

}
25 changes: 25 additions & 0 deletions src/spells/EmergencySpell_SparkLend_PauseSingleAsset.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import { IExecuteOnceSpell } from "src/interfaces/IExecuteOnceSpell.sol";
import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol";

contract EmergencySpell_SparkLend_PauseSingleAsset is IExecuteOnceSpell {

address public immutable sparkLendFreezerMom;
address public immutable reserve;

bool public override executed;

constructor(address sparkLendFreezerMom_, address reserve_) {
sparkLendFreezerMom = sparkLendFreezerMom_;
reserve = reserve_;
}

function execute() external override {
require(!executed, "PauseSingleAssetSpell/already-executed");
executed = true;
ISparkLendFreezerMom(sparkLendFreezerMom).pauseMarket(reserve, true);
}

}
Loading

0 comments on commit 1c480e1

Please sign in to comment.