diff --git a/src/spells/EmergencySpell_SparkLend_RemoveMultisig.sol b/src/spells/EmergencySpell_SparkLend_RemoveMultisig.sol new file mode 100644 index 0000000..87845f7 --- /dev/null +++ b/src/spells/EmergencySpell_SparkLend_RemoveMultisig.sol @@ -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_RemoveMultisig is IExecuteOnceSpell { + + address public immutable sparkLendFreezerMom; + address public immutable multisig; + + bool public override executed; + + constructor(address sparkLendFreezerMom_, address multisig_) { + sparkLendFreezerMom = sparkLendFreezerMom_; + multisig = multisig_; + } + + function execute() external override { + require(!executed, "RemoveMultisigSpell/already-executed"); + executed = true; + ISparkLendFreezerMom(sparkLendFreezerMom).deny(multisig); + } + +} diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index b89eb97..2a00118 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -21,6 +21,9 @@ import { EmergencySpell_SparkLend_PauseSingleAsset as PauseSingleAssetSpell } import { EmergencySpell_SparkLend_PauseAllAssets as PauseAllAssetsSpell } from "src/spells/EmergencySpell_SparkLend_PauseAllAssets.sol"; +import { EmergencySpell_SparkLend_RemoveMultisig as RemoveMultisigSpell } + from "src/spells/EmergencySpell_SparkLend_RemoveMultisig.sol"; + import { IACLManager } from "lib/aave-v3-core/contracts/interfaces/IACLManager.sol"; import { IPoolConfigurator } from "lib/aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; import { IPoolDataProvider } from "lib/aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; @@ -229,18 +232,8 @@ contract IntegrationTestsBase is Test { abstract contract ExecuteOnceSpellTests is IntegrationTestsBase { IExecuteOnceSpell spell; - bool isPauseSpell; string contractName; - function setUp() public virtual override { - super.setUp(); - - vm.startPrank(SPARK_PROXY); - aclManager.addEmergencyAdmin(address(freezerMom)); - aclManager.addRiskAdmin(address(freezerMom)); - vm.stopPrank(); - } - function _vote() internal { _vote(address(spell)); } @@ -281,12 +274,7 @@ abstract contract ExecuteOnceSpellTests is IntegrationTestsBase { spell.execute(); } - function test_cannotCallWithoutRoleSetup() external { - vm.startPrank(SPARK_PROXY); - aclManager.removeEmergencyAdmin(address(freezerMom)); - aclManager.removeRiskAdmin(address(freezerMom)); - vm.stopPrank(); - + function test_cannotCallTwice() external { _vote(); assertTrue(authority.hat() == address(spell)); @@ -298,12 +286,34 @@ abstract contract ExecuteOnceSpellTests is IntegrationTestsBase { ) ); - if (isPauseSpell) vm.expectRevert(bytes("3")); // CALLER_NOT_POOL_OR_EMERGENCY_ADMIN - else vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN + vm.startPrank(randomUser); // Demonstrate no ACL in spell + spell.execute(); + + vm.expectRevert(bytes(string.concat(contractName, "/already-executed"))); spell.execute(); } - function test_cannotCallTwice() external { +} + +abstract contract FreezePauseSpellTests is ExecuteOnceSpellTests { + + bool isPauseSpell; + + function setUp() public virtual override { + super.setUp(); + + vm.startPrank(SPARK_PROXY); + aclManager.addEmergencyAdmin(address(freezerMom)); + aclManager.addRiskAdmin(address(freezerMom)); + vm.stopPrank(); + } + + function test_cannotCallWithoutRoleSetup() external { + vm.startPrank(SPARK_PROXY); + aclManager.removeEmergencyAdmin(address(freezerMom)); + aclManager.removeRiskAdmin(address(freezerMom)); + vm.stopPrank(); + _vote(); assertTrue(authority.hat() == address(spell)); @@ -315,16 +325,14 @@ abstract contract ExecuteOnceSpellTests is IntegrationTestsBase { ) ); - vm.startPrank(randomUser); // Demonstrate no ACL in spell - spell.execute(); - - vm.expectRevert(bytes(string.concat(contractName, "/already-executed"))); + if (isPauseSpell) vm.expectRevert(bytes("3")); // CALLER_NOT_POOL_OR_EMERGENCY_ADMIN + else vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN spell.execute(); } } -contract FreezeSingleAssetSpellTest is ExecuteOnceSpellTests { +contract FreezeSingleAssetSpellTest is FreezePauseSpellTests { using SafeERC20 for IERC20; @@ -415,7 +423,7 @@ contract FreezeSingleAssetSpellTest is ExecuteOnceSpellTests { } -contract FreezeAllAssetsSpellTest is ExecuteOnceSpellTests { +contract FreezeAllAssetsSpellTest is FreezePauseSpellTests { using SafeERC20 for IERC20; @@ -513,7 +521,7 @@ contract FreezeAllAssetsSpellTest is ExecuteOnceSpellTests { } -contract PauseSingleAssetSpellTest is ExecuteOnceSpellTests { +contract PauseSingleAssetSpellTest is FreezePauseSpellTests { using SafeERC20 for IERC20; @@ -604,7 +612,7 @@ contract PauseSingleAssetSpellTest is ExecuteOnceSpellTests { } -contract PauseAllAssetsSpellTest is ExecuteOnceSpellTests { +contract PauseAllAssetsSpellTest is FreezePauseSpellTests { using SafeERC20 for IERC20; @@ -791,3 +799,48 @@ contract MultisigTest is IntegrationTestsBase { } } + +contract RemoveMultisigSpellTest is ExecuteOnceSpellTests { + + function setUp() public override { + super.setUp(); + + // For the revert testing + spell = new RemoveMultisigSpell(address(freezerMom), multisig); + contractName = "RemoveMultisigSpell"; + + vm.prank(SPARK_PROXY); + aclManager.addRiskAdmin(address(freezerMom)); + } + + function test_removeMultisigSpell_multisig() external { + assertEq(freezerMom.wards(multisig), 1); + + // Verify multisig can freeze and unfreeze markets + vm.startPrank(multisig); + assertEq(_isFrozen(WETH), false); + freezerMom.freezeMarket(WETH, true); + assertEq(_isFrozen(WETH), true); + freezerMom.freezeMarket(WETH, false); + assertEq(_isFrozen(WETH), false); + vm.stopPrank(); + + _vote(); + vm.prank(randomUser); // Demonstrate no ACL in spell + spell.execute(); + + assertEq(freezerMom.wards(multisig), 0); + + // Verify multisig can no longer freeze and unfreeze markets + vm.startPrank(multisig); + assertEq(_isFrozen(WETH), false); + vm.expectRevert("SparkLendFreezerMom/not-authorized"); + freezerMom.freezeMarket(WETH, true); + assertEq(_isFrozen(WETH), false); + vm.expectRevert("SparkLendFreezerMom/not-authorized"); + freezerMom.freezeMarket(WETH, false); + assertEq(_isFrozen(WETH), false); + vm.stopPrank(); + } + +}