From 925973e436063b553c0af92bec1aacce8c1c35c8 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 2 Nov 2023 11:19:28 -0400 Subject: [PATCH 01/42] feat: add outline for sparklend pauser --- .../certora_debug_log.txt | 0 .../resource_errors.json | 3 + .../certora_debug_log.txt | 0 .../resource_errors.json | 3 + .certora_internal/latest | 1 + src/Counter.sol | 14 --- src/SparkLendPauser.sol | 105 ++++++++++++++++++ src/interfaces/ISparkLendPauser.sol | 51 +++++++++ 8 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 .certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt create mode 100644 .certora_internal/23_11_01_14_31_52_102/resource_errors.json create mode 100644 .certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt create mode 100644 .certora_internal/23_11_02_10_41_06_164/resource_errors.json create mode 120000 .certora_internal/latest delete mode 100644 src/Counter.sol create mode 100644 src/SparkLendPauser.sol create mode 100644 src/interfaces/ISparkLendPauser.sol diff --git a/.certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt b/.certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt new file mode 100644 index 0000000..e69de29 diff --git a/.certora_internal/23_11_01_14_31_52_102/resource_errors.json b/.certora_internal/23_11_01_14_31_52_102/resource_errors.json new file mode 100644 index 0000000..d9bd792 --- /dev/null +++ b/.certora_internal/23_11_01_14_31_52_102/resource_errors.json @@ -0,0 +1,3 @@ +{ + "topics": [] +} \ No newline at end of file diff --git a/.certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt b/.certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt new file mode 100644 index 0000000..e69de29 diff --git a/.certora_internal/23_11_02_10_41_06_164/resource_errors.json b/.certora_internal/23_11_02_10_41_06_164/resource_errors.json new file mode 100644 index 0000000..d9bd792 --- /dev/null +++ b/.certora_internal/23_11_02_10_41_06_164/resource_errors.json @@ -0,0 +1,3 @@ +{ + "topics": [] +} \ No newline at end of file diff --git a/.certora_internal/latest b/.certora_internal/latest new file mode 120000 index 0000000..f68a670 --- /dev/null +++ b/.certora_internal/latest @@ -0,0 +1 @@ +23_11_02_10_41_06_164 \ No newline at end of file diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index 146c9e2..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/SparkLendPauser.sol b/src/SparkLendPauser.sol new file mode 100644 index 0000000..3679ef6 --- /dev/null +++ b/src/SparkLendPauser.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { ISparkLendPauser } from "src/interfaces/ISparkLendPauser.sol"; + +interface ProxyLike { + function exec(address target, bytes calldata args) external payable returns (bytes memory out); +} + +interface AuthorityLike { + function canCall(address src, address dst, bytes4 sig) external view returns (bool); +} + +interface IPoolConfiguratorLike { + function setPoolPause(bool paused) external; +} + +contract SparkLendPauser is ISparkLendPauser { + + /**********************************************************************************************/ + /*** Declarations and Constructor ***/ + /**********************************************************************************************/ + + address public immutable sparklendPoolConfigurator; + + address public authority; + + bool public canPause; + + mapping (address => uint256) public override wards; + + constructor(address sparklendPoolConfigurator_, address authority_) { + sparklendPoolConfigurator = sparklendPoolConfigurator_; + authority = authority_; + wards[msg.sender] = 1; + canPause = true; + emit Rely(msg.sender); + } + + /**********************************************************************************************/ + /*** Modifiers ***/ + /**********************************************************************************************/ + + modifier onlyWards { + require(wards[msg.sender] == 1, "SparkLendPauser/not-authorized"); + _; + } + + modifier auth { + require(isAuthorized(msg.sender, msg.sig), "SparkLendPauser/not-authorized"); + _; + } + + /**********************************************************************************************/ + /*** Auth Functions ***/ + /**********************************************************************************************/ + + function deny(address usr) external override onlyWards { + wards[usr] = 0; + emit Deny(usr); + } + + function rely(address usr) external override onlyWards { + wards[usr] = 1; + emit Rely(usr); + } + + function setAuthority(address authority_) external onlyWards { + address oldAuthority = authority; + authority = authority_; + emit SetAuthority(oldAuthority, authority_); + } + + function resetPause() external onlyWards { + canPause = true; + } + + /**********************************************************************************************/ + /*** Chief Functions ***/ + /**********************************************************************************************/ + + function pause() external auth { + require( + AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), + "SparkLendPauser/not-authorized" + ); + IPoolConfiguratorLike(sparklendPoolConfigurator).setPoolPause(true); + } + + /**********************************************************************************************/ + /*** Helper Functions ***/ + /**********************************************************************************************/ + + function isAuthorized(address src, bytes4 sig) internal view returns (bool) { + if (src == address(this)) { + return true; + } else if (wards[src] == 1) { + return true; + } else if (authority == address(0)) { + return false; + } else { + return AuthorityLike(authority).canCall(src, address(this), sig); + } + } + +} diff --git a/src/interfaces/ISparkLendPauser.sol b/src/interfaces/ISparkLendPauser.sol new file mode 100644 index 0000000..0a62716 --- /dev/null +++ b/src/interfaces/ISparkLendPauser.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.0; + +interface ISparkLendPauser { + + /**********************************************************************************************/ + /*** Events ***/ + /**********************************************************************************************/ + + event SetOwner(address indexed oldOwner, address indexed newOwner); + event SetAuthority(address indexed oldAuthority, address indexed newAuthority); + + /** + * @dev Event emitted when a new admin is removed from the Conduit. + * @param usr The address of the user to remove. + */ + event Deny(address indexed usr); + + /** + * @dev Event emitted when a new admin is added to the Conduit. + * @param usr The address of the user to add. + */ + event Rely(address indexed usr); + + /**********************************************************************************************/ + /*** Storage Variables ***/ + /**********************************************************************************************/ + + /** + * @dev Returns a 0 or 1 depending on if the user has been added as an admin. + * @return relied The value of the user's admin status. + */ + function wards(address user) external view returns (uint256 relied); + + /**********************************************************************************************/ + /*** Administrative Functions ***/ + /**********************************************************************************************/ + + /** + * @dev Function to remove an addresses admin permissions. + * @param usr The address of the admin. + */ + function deny(address usr) external; + + /** + * @dev Function to give an address admin permissions. + * @param usr The address of the new admin. + */ + function rely(address usr) external; + +} From 5e833db127019f1a8dfad7cd8d341943cf6aa073 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 2 Nov 2023 11:20:08 -0400 Subject: [PATCH 02/42] fix: rm certora files --- .certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt | 0 .certora_internal/23_11_01_14_31_52_102/resource_errors.json | 3 --- .certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt | 0 .certora_internal/23_11_02_10_41_06_164/resource_errors.json | 3 --- .certora_internal/latest | 1 - 5 files changed, 7 deletions(-) delete mode 100644 .certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt delete mode 100644 .certora_internal/23_11_01_14_31_52_102/resource_errors.json delete mode 100644 .certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt delete mode 100644 .certora_internal/23_11_02_10_41_06_164/resource_errors.json delete mode 120000 .certora_internal/latest diff --git a/.certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt b/.certora_internal/23_11_01_14_31_52_102/certora_debug_log.txt deleted file mode 100644 index e69de29..0000000 diff --git a/.certora_internal/23_11_01_14_31_52_102/resource_errors.json b/.certora_internal/23_11_01_14_31_52_102/resource_errors.json deleted file mode 100644 index d9bd792..0000000 --- a/.certora_internal/23_11_01_14_31_52_102/resource_errors.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "topics": [] -} \ No newline at end of file diff --git a/.certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt b/.certora_internal/23_11_02_10_41_06_164/certora_debug_log.txt deleted file mode 100644 index e69de29..0000000 diff --git a/.certora_internal/23_11_02_10_41_06_164/resource_errors.json b/.certora_internal/23_11_02_10_41_06_164/resource_errors.json deleted file mode 100644 index d9bd792..0000000 --- a/.certora_internal/23_11_02_10_41_06_164/resource_errors.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "topics": [] -} \ No newline at end of file diff --git a/.certora_internal/latest b/.certora_internal/latest deleted file mode 120000 index f68a670..0000000 --- a/.certora_internal/latest +++ /dev/null @@ -1 +0,0 @@ -23_11_02_10_41_06_164 \ No newline at end of file From b989973926e2e6dcb425f6bf3fdbbc4997b3b8b3 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 2 Nov 2023 11:35:24 -0400 Subject: [PATCH 03/42] feat: add test coverage for wards ACL functions --- src/SparkLendPauser.sol | 11 ++-- test/Counter.t.sol | 25 --------- test/SparkLendPauser.base.t.sol | 93 +++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 30 deletions(-) delete mode 100644 test/Counter.t.sol create mode 100644 test/SparkLendPauser.base.t.sol diff --git a/src/SparkLendPauser.sol b/src/SparkLendPauser.sol index 3679ef6..2aa9a48 100644 --- a/src/SparkLendPauser.sol +++ b/src/SparkLendPauser.sol @@ -41,7 +41,7 @@ contract SparkLendPauser is ISparkLendPauser { /**********************************************************************************************/ modifier onlyWards { - require(wards[msg.sender] == 1, "SparkLendPauser/not-authorized"); + require(wards[msg.sender] == 1, "SparkLendPauser/not-ward"); _; } @@ -51,7 +51,7 @@ contract SparkLendPauser is ISparkLendPauser { } /**********************************************************************************************/ - /*** Auth Functions ***/ + /*** Wards Functions ***/ /**********************************************************************************************/ function deny(address usr) external override onlyWards { @@ -70,15 +70,16 @@ contract SparkLendPauser is ISparkLendPauser { emit SetAuthority(oldAuthority, authority_); } - function resetPause() external onlyWards { - canPause = true; + function setCanPause(bool canPause_) external onlyWards { + canPause = canPause_; } /**********************************************************************************************/ - /*** Chief Functions ***/ + /*** Auth Functions ***/ /**********************************************************************************************/ function pause() external auth { + require(canPause, "SparkLendPauser/pause-not-allowed"); require( AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), "SparkLendPauser/not-authorized" diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index c90feb7..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import { Counter } from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendPauser.base.t.sol new file mode 100644 index 0000000..8dd0df7 --- /dev/null +++ b/test/SparkLendPauser.base.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; + +import { SparkLendPauser } from "../src/SparkLendPauser.sol"; + +contract ConfiguratorMock { + function setPause(bool paused) external {} +} + +contract SparkLendPauserUnitTestBase is Test { + + address public authority; + address public configurator; + address public ward; + + SparkLendPauser public pauser; + + function setUp() public { + authority = makeAddr("authority"); + ward = makeAddr("ward"); + + configurator = address(new ConfiguratorMock()); + pauser = new SparkLendPauser(configurator, authority); + + pauser.rely(ward); + pauser.deny(address(this)); + } + + function test_deny_no_auth() public { + vm.expectRevert("SparkLendPauser/not-ward"); + pauser.deny(ward); + } + + function test_deny() public { + assertEq(pauser.wards(ward), 1); + + vm.prank(ward); + pauser.deny(ward); + + assertEq(pauser.wards(ward), 0); + } + + function test_rely_no_auth() public { + vm.expectRevert("SparkLendPauser/not-ward"); + pauser.rely(makeAddr("new ward")); + } + + function test_rely() public { + address newWard = makeAddr("new ward"); + assertEq(pauser.wards(newWard), 0); + + vm.prank(ward); + pauser.rely(newWard); + + assertEq(pauser.wards(ward), 1); + } + + function test_setAuthority_no_auth() public { + vm.expectRevert("SparkLendPauser/not-ward"); + pauser.setAuthority(makeAddr("new authority")); + } + + function test_setAuthority() public { + address newAuthority = makeAddr("new authority"); + assertEq(pauser.authority(), authority); + + vm.prank(ward); + pauser.setAuthority(newAuthority); + + assertEq(pauser.authority(), newAuthority); + } + + function test_resetPause_no_auth() public { + vm.expectRevert("SparkLendPauser/not-ward"); + pauser.setCanPause(false); + } + + function test_setCanPause() public { + assertEq(pauser.canPause(), true); + + vm.startPrank(ward); + pauser.setCanPause(false); + + assertEq(pauser.canPause(), false); + + pauser.setCanPause(true); + + assertEq(pauser.canPause(), true); + } + +} From 8afcb351be0f34f0b5dc317265b97428fb007bb8 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 11:00:34 -0400 Subject: [PATCH 04/42] feat: add pool mock, use freeze instead of pause --- ...arkLendPauser.sol => SparkLendFreezer.sol} | 51 ++++---- ...rkLendPauser.sol => ISparkLendFreezer.sol} | 2 +- test/Mocks.sol | 40 +++++++ test/SparkLendPauser.base.t.sol | 110 ++++++++++++------ 4 files changed, 148 insertions(+), 55 deletions(-) rename src/{SparkLendPauser.sol => SparkLendFreezer.sol} (69%) rename src/interfaces/{ISparkLendPauser.sol => ISparkLendFreezer.sol} (98%) create mode 100644 test/Mocks.sol diff --git a/src/SparkLendPauser.sol b/src/SparkLendFreezer.sol similarity index 69% rename from src/SparkLendPauser.sol rename to src/SparkLendFreezer.sol index 2aa9a48..b547ce9 100644 --- a/src/SparkLendPauser.sol +++ b/src/SparkLendFreezer.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import { ISparkLendPauser } from "src/interfaces/ISparkLendPauser.sol"; +import { ISparkLendFreezer } from "src/interfaces/ISparkLendFreezer.sol"; interface ProxyLike { function exec(address target, bytes calldata args) external payable returns (bytes memory out); @@ -10,29 +10,36 @@ interface AuthorityLike { function canCall(address src, address dst, bytes4 sig) external view returns (bool); } -interface IPoolConfiguratorLike { - function setPoolPause(bool paused) external; +interface PoolConfiguratorLike { + function setReserveFreeze(address asset, bool freeze) external; } -contract SparkLendPauser is ISparkLendPauser { +interface PoolLike { + function getReservesList() external view returns (address[] memory); +} + +contract SparkLendFreezer is ISparkLendFreezer { /**********************************************************************************************/ /*** Declarations and Constructor ***/ /**********************************************************************************************/ - address public immutable sparklendPoolConfigurator; + address public immutable poolConfigurator; + address public immutable pool; address public authority; - bool public canPause; + bool public canFreeze; mapping (address => uint256) public override wards; - constructor(address sparklendPoolConfigurator_, address authority_) { - sparklendPoolConfigurator = sparklendPoolConfigurator_; - authority = authority_; + constructor(address poolConfigurator_, address pool_, address authority_) { + poolConfigurator = poolConfigurator_; + pool = pool_; + authority = authority_; + wards[msg.sender] = 1; - canPause = true; + canFreeze = true; emit Rely(msg.sender); } @@ -41,12 +48,12 @@ contract SparkLendPauser is ISparkLendPauser { /**********************************************************************************************/ modifier onlyWards { - require(wards[msg.sender] == 1, "SparkLendPauser/not-ward"); + require(wards[msg.sender] == 1, "SparkLendFreezer/not-ward"); _; } modifier auth { - require(isAuthorized(msg.sender, msg.sig), "SparkLendPauser/not-authorized"); + require(isAuthorized(msg.sender, msg.sig), "SparkLendFreezer/not-authorized"); _; } @@ -70,21 +77,23 @@ contract SparkLendPauser is ISparkLendPauser { emit SetAuthority(oldAuthority, authority_); } - function setCanPause(bool canPause_) external onlyWards { - canPause = canPause_; + function setCanFreeze(bool canFreeze_) external onlyWards { + canFreeze = canFreeze_; } /**********************************************************************************************/ /*** Auth Functions ***/ /**********************************************************************************************/ - function pause() external auth { - require(canPause, "SparkLendPauser/pause-not-allowed"); - require( - AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), - "SparkLendPauser/not-authorized" - ); - IPoolConfiguratorLike(sparklendPoolConfigurator).setPoolPause(true); + function freeze() external auth { + require(canFreeze, "SparkLendFreezer/pause-not-allowed"); + + address[] memory reserves = PoolLike(pool).getReservesList(); + + for (uint256 i = 0; i < reserves.length; i++) { + if (reserves[i] == address(0)) continue; + PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); + } } /**********************************************************************************************/ diff --git a/src/interfaces/ISparkLendPauser.sol b/src/interfaces/ISparkLendFreezer.sol similarity index 98% rename from src/interfaces/ISparkLendPauser.sol rename to src/interfaces/ISparkLendFreezer.sol index 0a62716..ee692f8 100644 --- a/src/interfaces/ISparkLendPauser.sol +++ b/src/interfaces/ISparkLendFreezer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; -interface ISparkLendPauser { +interface ISparkLendFreezer { /**********************************************************************************************/ /*** Events ***/ diff --git a/test/Mocks.sol b/test/Mocks.sol new file mode 100644 index 0000000..89cafad --- /dev/null +++ b/test/Mocks.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +contract AuthorityMock { + + bool internal _allCanCall; + + mapping (address => mapping (address => mapping (bytes4 => bool))) internal callAllowed; + + function __setCanCall(address src, address dst, bytes4 sig, bool allowed) public { + callAllowed[src][dst][sig] = allowed; + } + + function _setAllCanCall(bool allCanCall_) public { + _allCanCall = allCanCall_; + } + + function canCall(address src, address dst, bytes4 sig) external view returns (bool) { + return callAllowed[src][dst][sig] || _allCanCall; + } + +} + +contract ConfiguratorMock { + + function setReserveFreeze(address asset, bool freeze) external {} + +} + +contract PoolMock { + + function getReservesList() external pure returns (address[] memory list) { + list = new address[](1); + + list[0] = address(0x1); + } + +} + + diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendPauser.base.t.sol index 8dd0df7..fa2ff62 100644 --- a/test/SparkLendPauser.base.t.sol +++ b/test/SparkLendPauser.base.t.sol @@ -3,91 +3,135 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import { SparkLendPauser } from "../src/SparkLendPauser.sol"; +import { SparkLendFreezer } from "../src/SparkLendFreezer.sol"; -contract ConfiguratorMock { - function setPause(bool paused) external {} -} +import { ConfiguratorMock, PoolMock } from "./Mocks.sol"; -contract SparkLendPauserUnitTestBase is Test { +contract SparkLendFreezerUnitTests is Test { address public authority; address public configurator; + address public pool; address public ward; - SparkLendPauser public pauser; + SparkLendFreezer public freezer; function setUp() public { authority = makeAddr("authority"); ward = makeAddr("ward"); configurator = address(new ConfiguratorMock()); - pauser = new SparkLendPauser(configurator, authority); + pool = address(new PoolMock()); + freezer = new SparkLendFreezer(configurator, pool, authority); - pauser.rely(ward); - pauser.deny(address(this)); + freezer.rely(ward); + freezer.deny(address(this)); } + /********************/ + /*** `deny` Tests ***/ + /********************/ + function test_deny_no_auth() public { - vm.expectRevert("SparkLendPauser/not-ward"); - pauser.deny(ward); + vm.expectRevert("SparkLendFreezer/not-ward"); + freezer.deny(ward); } function test_deny() public { - assertEq(pauser.wards(ward), 1); + assertEq(freezer.wards(ward), 1); vm.prank(ward); - pauser.deny(ward); + freezer.deny(ward); - assertEq(pauser.wards(ward), 0); + assertEq(freezer.wards(ward), 0); } + /********************/ + /*** `rely` Tests ***/ + /********************/ + function test_rely_no_auth() public { - vm.expectRevert("SparkLendPauser/not-ward"); - pauser.rely(makeAddr("new ward")); + vm.expectRevert("SparkLendFreezer/not-ward"); + freezer.rely(makeAddr("new ward")); } function test_rely() public { address newWard = makeAddr("new ward"); - assertEq(pauser.wards(newWard), 0); + assertEq(freezer.wards(newWard), 0); vm.prank(ward); - pauser.rely(newWard); + freezer.rely(newWard); - assertEq(pauser.wards(ward), 1); + assertEq(freezer.wards(ward), 1); } + /****************************/ + /*** `setAuthority` Tests ***/ + /****************************/ + function test_setAuthority_no_auth() public { - vm.expectRevert("SparkLendPauser/not-ward"); - pauser.setAuthority(makeAddr("new authority")); + vm.expectRevert("SparkLendFreezer/not-ward"); + freezer.setAuthority(makeAddr("new authority")); } function test_setAuthority() public { address newAuthority = makeAddr("new authority"); - assertEq(pauser.authority(), authority); + assertEq(freezer.authority(), authority); vm.prank(ward); - pauser.setAuthority(newAuthority); + freezer.setAuthority(newAuthority); - assertEq(pauser.authority(), newAuthority); + assertEq(freezer.authority(), newAuthority); } - function test_resetPause_no_auth() public { - vm.expectRevert("SparkLendPauser/not-ward"); - pauser.setCanPause(false); + /**************************/ + /*** `setCanFreeze` Tests ***/ + /**************************/ + + function test_setCanFreeze_no_auth() public { + vm.expectRevert("SparkLendFreezer/not-ward"); + freezer.setCanFreeze(false); } - function test_setCanPause() public { - assertEq(pauser.canPause(), true); + function test_setCanFreeze() public { + assertEq(freezer.canFreeze(), true); vm.startPrank(ward); - pauser.setCanPause(false); + freezer.setCanFreeze(false); - assertEq(pauser.canPause(), false); + assertEq(freezer.canFreeze(), false); - pauser.setCanPause(true); + freezer.setCanFreeze(true); - assertEq(pauser.canPause(), true); + assertEq(freezer.canFreeze(), true); } + // /*********************/ + // /*** `pause` Tests ***/ + // /*********************/ + + // function test_freeze_noAuth() public { + // assertEq(freezer.canPause(), true); + + // vm.startPrank(ward); + // freezer.setCanFreeze(false); + + // assertEq(freezer.canPause(), false); + + // freezer.setCanFreeze(true); + + // assertEq(freezer.canPause(), true); + // } + + // function test_pause_cannotPause() public { + // vm.startPrank(ward); + // freezer.setCanFreeze(false); + + // vm.expectRevert("SparkLendFreezer/pause-not-allowed"); + + // freezer.setCanFreeze(false); + // } + } + + From 5d54d716e6b2b2e49596b1d4c93be08f098fa37b Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 11:20:09 -0400 Subject: [PATCH 05/42] feat: add test coverage for freeze --- src/SparkLendFreezer.sol | 25 +++++----- test/Mocks.sol | 14 ++++-- test/SparkLendPauser.base.t.sol | 81 +++++++++++++++++++++------------ 3 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index b547ce9..1d50eec 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -47,13 +47,8 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Modifiers ***/ /**********************************************************************************************/ - modifier onlyWards { - require(wards[msg.sender] == 1, "SparkLendFreezer/not-ward"); - _; - } - modifier auth { - require(isAuthorized(msg.sender, msg.sig), "SparkLendFreezer/not-authorized"); + require(wards[msg.sender] == 1, "SparkLendFreezer/not-authorized"); _; } @@ -61,23 +56,23 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Wards Functions ***/ /**********************************************************************************************/ - function deny(address usr) external override onlyWards { + function deny(address usr) external override auth { wards[usr] = 0; emit Deny(usr); } - function rely(address usr) external override onlyWards { + function rely(address usr) external override auth { wards[usr] = 1; emit Rely(usr); } - function setAuthority(address authority_) external onlyWards { + function setAuthority(address authority_) external auth { address oldAuthority = authority; authority = authority_; emit SetAuthority(oldAuthority, authority_); } - function setCanFreeze(bool canFreeze_) external onlyWards { + function setCanFreeze(bool canFreeze_) external auth { canFreeze = canFreeze_; } @@ -85,8 +80,12 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Auth Functions ***/ /**********************************************************************************************/ - function freeze() external auth { - require(canFreeze, "SparkLendFreezer/pause-not-allowed"); + function freeze() external { + require(canFreeze, "SparkLendFreezer/freeze-not-allowed"); + require( + AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), + "SparkLendFreezer/cannot-call" + ); address[] memory reserves = PoolLike(pool).getReservesList(); @@ -94,6 +93,8 @@ contract SparkLendFreezer is ISparkLendFreezer { if (reserves[i] == address(0)) continue; PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); } + + canFreeze = false; } /**********************************************************************************************/ diff --git a/test/Mocks.sol b/test/Mocks.sol index 89cafad..6a40210 100644 --- a/test/Mocks.sol +++ b/test/Mocks.sol @@ -29,10 +29,18 @@ contract ConfiguratorMock { contract PoolMock { - function getReservesList() external pure returns (address[] memory list) { - list = new address[](1); + address[] internal assets; - list[0] = address(0x1); + function getReservesList() external view returns (address[] memory list) { + list = new address[](assets.length); + + for (uint256 i; i < assets.length; ++i) { + list[i] = assets[i]; + } + } + + function __addAsset(address asset) public { + assets.push(asset); } } diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendPauser.base.t.sol index fa2ff62..ea91d89 100644 --- a/test/SparkLendPauser.base.t.sol +++ b/test/SparkLendPauser.base.t.sol @@ -5,24 +5,25 @@ import "forge-std/Test.sol"; import { SparkLendFreezer } from "../src/SparkLendFreezer.sol"; -import { ConfiguratorMock, PoolMock } from "./Mocks.sol"; +import { AuthorityMock, ConfiguratorMock, PoolMock } from "./Mocks.sol"; contract SparkLendFreezerUnitTests is Test { - address public authority; address public configurator; address public pool; address public ward; + AuthorityMock public authority; + SparkLendFreezer public freezer; function setUp() public { - authority = makeAddr("authority"); - ward = makeAddr("ward"); + ward = makeAddr("ward"); + authority = new AuthorityMock(); configurator = address(new ConfiguratorMock()); pool = address(new PoolMock()); - freezer = new SparkLendFreezer(configurator, pool, authority); + freezer = new SparkLendFreezer(configurator, pool, address(authority)); freezer.rely(ward); freezer.deny(address(this)); @@ -33,7 +34,7 @@ contract SparkLendFreezerUnitTests is Test { /********************/ function test_deny_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-ward"); + vm.expectRevert("SparkLendFreezer/not-authorized"); freezer.deny(ward); } @@ -51,7 +52,7 @@ contract SparkLendFreezerUnitTests is Test { /********************/ function test_rely_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-ward"); + vm.expectRevert("SparkLendFreezer/not-authorized"); freezer.rely(makeAddr("new ward")); } @@ -70,13 +71,13 @@ contract SparkLendFreezerUnitTests is Test { /****************************/ function test_setAuthority_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-ward"); + vm.expectRevert("SparkLendFreezer/not-authorized"); freezer.setAuthority(makeAddr("new authority")); } function test_setAuthority() public { address newAuthority = makeAddr("new authority"); - assertEq(freezer.authority(), authority); + assertEq(freezer.authority(), address(authority)); vm.prank(ward); freezer.setAuthority(newAuthority); @@ -84,12 +85,12 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.authority(), newAuthority); } - /**************************/ + /****************************/ /*** `setCanFreeze` Tests ***/ - /**************************/ + /****************************/ function test_setCanFreeze_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-ward"); + vm.expectRevert("SparkLendFreezer/not-authorized"); freezer.setCanFreeze(false); } @@ -106,31 +107,53 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.canFreeze(), true); } - // /*********************/ - // /*** `pause` Tests ***/ - // /*********************/ + /**********************/ + /*** `freeze` Tests ***/ + /**********************/ + + function test_freeze_notAllowed() public { + vm.prank(ward); + freezer.setCanFreeze(false); + + vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); + freezer.freeze(); + } + + function test_freeze_noAuth() public { + vm.expectRevert("SparkLendFreezer/cannot-call"); + freezer.freeze(); + } + + function test_freeze_cannotCallTwice() public { + authority.__setCanCall(address(this), address(freezer), freezer.freeze.selector, true); + + freezer.freeze(); - // function test_freeze_noAuth() public { - // assertEq(freezer.canPause(), true); + vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); + freezer.freeze(); + } - // vm.startPrank(ward); - // freezer.setCanFreeze(false); + function test_freeze() public { + authority.__setCanCall(address(this), address(freezer), freezer.freeze.selector, true); - // assertEq(freezer.canPause(), false); + address asset1 = makeAddr("asset1"); + address asset2 = makeAddr("asset2"); - // freezer.setCanFreeze(true); + PoolMock(pool).__addAsset(asset1); + PoolMock(pool).__addAsset(asset2); - // assertEq(freezer.canPause(), true); - // } + bytes4 poolSig = PoolMock.getReservesList.selector; + bytes4 configSig = ConfiguratorMock.setReserveFreeze.selector; - // function test_pause_cannotPause() public { - // vm.startPrank(ward); - // freezer.setCanFreeze(false); + assertEq(freezer.canFreeze(), true); - // vm.expectRevert("SparkLendFreezer/pause-not-allowed"); + vm.expectCall(pool, abi.encodePacked(poolSig)); + vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset1, true))); + vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset2, true))); + freezer.freeze(); - // freezer.setCanFreeze(false); - // } + assertEq(freezer.canFreeze(), false); + } } From 9b0c0d316b03c59634d16b668fe1b325df11cc48 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 11:22:57 -0400 Subject: [PATCH 06/42] feat: get to 100% unit test coverage --- src/SparkLendFreezer.sol | 16 ---------------- test/SparkLendPauser.base.t.sol | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index 1d50eec..0a927ac 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -97,20 +97,4 @@ contract SparkLendFreezer is ISparkLendFreezer { canFreeze = false; } - /**********************************************************************************************/ - /*** Helper Functions ***/ - /**********************************************************************************************/ - - function isAuthorized(address src, bytes4 sig) internal view returns (bool) { - if (src == address(this)) { - return true; - } else if (wards[src] == 1) { - return true; - } else if (authority == address(0)) { - return false; - } else { - return AuthorityLike(authority).canCall(src, address(this), sig); - } - } - } diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendPauser.base.t.sol index ea91d89..dec4e24 100644 --- a/test/SparkLendPauser.base.t.sol +++ b/test/SparkLendPauser.base.t.sol @@ -29,6 +29,20 @@ contract SparkLendFreezerUnitTests is Test { freezer.deny(address(this)); } + /***************************/ + /*** `constructor` Tests ***/ + /***************************/ + + function test_constructor() public { + freezer = new SparkLendFreezer(configurator, pool, address(authority)); + + assertEq(freezer.poolConfigurator(), configurator); + assertEq(freezer.pool(), pool); + assertEq(freezer.authority(), address(authority)); + assertEq(freezer.canFreeze(), true); + assertEq(freezer.wards(address(this)), 1); + } + /********************/ /*** `deny` Tests ***/ /********************/ From 70516b12b784412cb2aad727f67648bb4b86648f Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 11:23:29 -0400 Subject: [PATCH 07/42] fix: rm counter.s.sol --- script/Counter.s.sol | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 script/Counter.s.sol diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 20d39cf..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.13; - -import { Script, console2 } from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} From 0c149774a0bd7cdc4433339adb2cb39e62ac5291 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 11:28:31 -0400 Subject: [PATCH 08/42] fix: add pragma and remove unused interface --- src/SparkLendFreezer.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index 0a927ac..bbcec18 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -1,11 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; import { ISparkLendFreezer } from "src/interfaces/ISparkLendFreezer.sol"; -interface ProxyLike { - function exec(address target, bytes calldata args) external payable returns (bytes memory out); -} - interface AuthorityLike { function canCall(address src, address dst, bytes4 sig) external view returns (bool); } From c8109d1b2894bf12e6819e6efe46283d90b27902 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 3 Nov 2023 12:27:14 -0400 Subject: [PATCH 09/42] fix: add double underscore --- test/Mocks.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Mocks.sol b/test/Mocks.sol index 6a40210..da6467b 100644 --- a/test/Mocks.sol +++ b/test/Mocks.sol @@ -11,7 +11,7 @@ contract AuthorityMock { callAllowed[src][dst][sig] = allowed; } - function _setAllCanCall(bool allCanCall_) public { + function __setAllCanCall(bool allCanCall_) public { _allCanCall = allCanCall_; } From 327fba74145481afeed19072cd1c1b9a5928a675 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Mon, 6 Nov 2023 14:05:08 -0500 Subject: [PATCH 10/42] feat: add reserve freezes, update test structure --- src/SparkLendFreezer.sol | 23 ++++++++---- test/SparkLendPauser.base.t.sol | 66 +++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index bbcec18..1e7d282 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -49,6 +49,15 @@ contract SparkLendFreezer is ISparkLendFreezer { _; } + modifier freezeCheck { + require(canFreeze, "SparkLendFreezer/freeze-not-allowed"); + require( + AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), + "SparkLendFreezer/cannot-call" + ); + _; + } + /**********************************************************************************************/ /*** Wards Functions ***/ /**********************************************************************************************/ @@ -77,13 +86,7 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Auth Functions ***/ /**********************************************************************************************/ - function freeze() external { - require(canFreeze, "SparkLendFreezer/freeze-not-allowed"); - require( - AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), - "SparkLendFreezer/cannot-call" - ); - + function freezeAllMarkets() external freezeCheck { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { @@ -94,4 +97,10 @@ contract SparkLendFreezer is ISparkLendFreezer { canFreeze = false; } + function freezeMarket(address reserve) external freezeCheck { + PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); + + canFreeze = false; + } + } diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendPauser.base.t.sol index dec4e24..5a323a3 100644 --- a/test/SparkLendPauser.base.t.sol +++ b/test/SparkLendPauser.base.t.sol @@ -7,7 +7,7 @@ import { SparkLendFreezer } from "../src/SparkLendFreezer.sol"; import { AuthorityMock, ConfiguratorMock, PoolMock } from "./Mocks.sol"; -contract SparkLendFreezerUnitTests is Test { +contract SparkLendFreezerUnitTestBase is Test { address public configurator; address public pool; @@ -29,9 +29,9 @@ contract SparkLendFreezerUnitTests is Test { freezer.deny(address(this)); } - /***************************/ - /*** `constructor` Tests ***/ - /***************************/ +} + +contract SparkLendFreezerConstructorTests is SparkLendFreezerUnitTestBase { function test_constructor() public { freezer = new SparkLendFreezer(configurator, pool, address(authority)); @@ -43,9 +43,9 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.wards(address(this)), 1); } - /********************/ - /*** `deny` Tests ***/ - /********************/ +} + +contract SparkLendFreezerDenyTests is SparkLendFreezerUnitTestBase { function test_deny_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -61,9 +61,9 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.wards(ward), 0); } - /********************/ - /*** `rely` Tests ***/ - /********************/ +} + +contract SparkLendFreezerRelyTests is SparkLendFreezerUnitTestBase { function test_rely_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -80,9 +80,9 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.wards(ward), 1); } - /****************************/ - /*** `setAuthority` Tests ***/ - /****************************/ +} + +contract SparkLendFreezerSetAuthorityTests is SparkLendFreezerUnitTestBase { function test_setAuthority_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -99,6 +99,10 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.authority(), newAuthority); } +} + +contract SparkLendFreezerSetCanFreezeTests is SparkLendFreezerUnitTestBase { + /****************************/ /*** `setCanFreeze` Tests ***/ /****************************/ @@ -121,34 +125,48 @@ contract SparkLendFreezerUnitTests is Test { assertEq(freezer.canFreeze(), true); } +} + +contract SparkLendFreezerFreezeAllMarketsTests is SparkLendFreezerUnitTestBase { + /**********************/ /*** `freeze` Tests ***/ /**********************/ - function test_freeze_notAllowed() public { + function test_freezeAllMarkets_notAllowed() public { vm.prank(ward); freezer.setCanFreeze(false); vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); - freezer.freeze(); + freezer.freezeAllMarkets(); } - function test_freeze_noAuth() public { + function test_freezeAllMarkets_noAuth() public { vm.expectRevert("SparkLendFreezer/cannot-call"); - freezer.freeze(); + freezer.freezeAllMarkets(); } - function test_freeze_cannotCallTwice() public { - authority.__setCanCall(address(this), address(freezer), freezer.freeze.selector, true); + function test_freezeAllMarkets_cannotCallTwice() public { + authority.__setCanCall( + address(this), + address(freezer), + freezer.freezeAllMarkets.selector, + true + ); - freezer.freeze(); + freezer.freezeAllMarkets(); vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); - freezer.freeze(); + freezer.freezeAllMarkets(); } - function test_freeze() public { - authority.__setCanCall(address(this), address(freezer), freezer.freeze.selector, true); + function test_freezeAllMarkets() public { + authority.__setCanCall( + address(this), + address(freezer), + freezer.freezeAllMarkets.selector, + true + ); address asset1 = makeAddr("asset1"); address asset2 = makeAddr("asset2"); @@ -164,7 +182,7 @@ contract SparkLendFreezerUnitTests is Test { vm.expectCall(pool, abi.encodePacked(poolSig)); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset1, true))); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset2, true))); - freezer.freeze(); + freezer.freezeAllMarkets(); assertEq(freezer.canFreeze(), false); } From 566cb4514f55dfd2b1d7ec48f95a2a9703fff2f3 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Mon, 6 Nov 2023 14:17:23 -0500 Subject: [PATCH 11/42] feat: add freeze and freeze all coverage --- src/SparkLendFreezer.sol | 19 +--- ...user.base.t.sol => SparkLendFreezer.t.sol} | 90 ++++++------------- 2 files changed, 33 insertions(+), 76 deletions(-) rename test/{SparkLendPauser.base.t.sol => SparkLendFreezer.t.sol} (67%) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index 1e7d282..14d4db2 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -26,8 +26,6 @@ contract SparkLendFreezer is ISparkLendFreezer { address public authority; - bool public canFreeze; - mapping (address => uint256) public override wards; constructor(address poolConfigurator_, address pool_, address authority_) { @@ -36,7 +34,6 @@ contract SparkLendFreezer is ISparkLendFreezer { authority = authority_; wards[msg.sender] = 1; - canFreeze = true; emit Rely(msg.sender); } @@ -49,8 +46,7 @@ contract SparkLendFreezer is ISparkLendFreezer { _; } - modifier freezeCheck { - require(canFreeze, "SparkLendFreezer/freeze-not-allowed"); + modifier canCall { require( AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), "SparkLendFreezer/cannot-call" @@ -78,29 +74,22 @@ contract SparkLendFreezer is ISparkLendFreezer { emit SetAuthority(oldAuthority, authority_); } - function setCanFreeze(bool canFreeze_) external auth { - canFreeze = canFreeze_; - } - /**********************************************************************************************/ /*** Auth Functions ***/ /**********************************************************************************************/ - function freezeAllMarkets() external freezeCheck { + function freezeAllMarkets() external canCall { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { if (reserves[i] == address(0)) continue; PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); } - - canFreeze = false; } - function freezeMarket(address reserve) external freezeCheck { + function freezeMarket(address reserve) external canCall { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); - - canFreeze = false; } } + diff --git a/test/SparkLendPauser.base.t.sol b/test/SparkLendFreezer.t.sol similarity index 67% rename from test/SparkLendPauser.base.t.sol rename to test/SparkLendFreezer.t.sol index 5a323a3..4a27f7c 100644 --- a/test/SparkLendPauser.base.t.sol +++ b/test/SparkLendFreezer.t.sol @@ -31,7 +31,7 @@ contract SparkLendFreezerUnitTestBase is Test { } -contract SparkLendFreezerConstructorTests is SparkLendFreezerUnitTestBase { +contract ConstructorTests is SparkLendFreezerUnitTestBase { function test_constructor() public { freezer = new SparkLendFreezer(configurator, pool, address(authority)); @@ -39,13 +39,12 @@ contract SparkLendFreezerConstructorTests is SparkLendFreezerUnitTestBase { assertEq(freezer.poolConfigurator(), configurator); assertEq(freezer.pool(), pool); assertEq(freezer.authority(), address(authority)); - assertEq(freezer.canFreeze(), true); assertEq(freezer.wards(address(this)), 1); } } -contract SparkLendFreezerDenyTests is SparkLendFreezerUnitTestBase { +contract DenyTests is SparkLendFreezerUnitTestBase { function test_deny_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -63,7 +62,7 @@ contract SparkLendFreezerDenyTests is SparkLendFreezerUnitTestBase { } -contract SparkLendFreezerRelyTests is SparkLendFreezerUnitTestBase { +contract RelyTests is SparkLendFreezerUnitTestBase { function test_rely_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -82,7 +81,7 @@ contract SparkLendFreezerRelyTests is SparkLendFreezerUnitTestBase { } -contract SparkLendFreezerSetAuthorityTests is SparkLendFreezerUnitTestBase { +contract SetAuthorityTests is SparkLendFreezerUnitTestBase { function test_setAuthority_no_auth() public { vm.expectRevert("SparkLendFreezer/not-authorized"); @@ -101,65 +100,13 @@ contract SparkLendFreezerSetAuthorityTests is SparkLendFreezerUnitTestBase { } -contract SparkLendFreezerSetCanFreezeTests is SparkLendFreezerUnitTestBase { - - /****************************/ - /*** `setCanFreeze` Tests ***/ - /****************************/ - - function test_setCanFreeze_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-authorized"); - freezer.setCanFreeze(false); - } - - function test_setCanFreeze() public { - assertEq(freezer.canFreeze(), true); - - vm.startPrank(ward); - freezer.setCanFreeze(false); - - assertEq(freezer.canFreeze(), false); - - freezer.setCanFreeze(true); - - assertEq(freezer.canFreeze(), true); - } - -} - -contract SparkLendFreezerFreezeAllMarketsTests is SparkLendFreezerUnitTestBase { - - /**********************/ - /*** `freeze` Tests ***/ - /**********************/ - - function test_freezeAllMarkets_notAllowed() public { - vm.prank(ward); - freezer.setCanFreeze(false); - - vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); - freezer.freezeAllMarkets(); - } +contract FreezeAllMarketsTests is SparkLendFreezerUnitTestBase { function test_freezeAllMarkets_noAuth() public { vm.expectRevert("SparkLendFreezer/cannot-call"); freezer.freezeAllMarkets(); } - function test_freezeAllMarkets_cannotCallTwice() public { - authority.__setCanCall( - address(this), - address(freezer), - freezer.freezeAllMarkets.selector, - true - ); - - freezer.freezeAllMarkets(); - - vm.expectRevert("SparkLendFreezer/freeze-not-allowed"); - freezer.freezeAllMarkets(); - } - function test_freezeAllMarkets() public { authority.__setCanCall( address(this), @@ -177,14 +124,35 @@ contract SparkLendFreezerFreezeAllMarketsTests is SparkLendFreezerUnitTestBase { bytes4 poolSig = PoolMock.getReservesList.selector; bytes4 configSig = ConfiguratorMock.setReserveFreeze.selector; - assertEq(freezer.canFreeze(), true); - vm.expectCall(pool, abi.encodePacked(poolSig)); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset1, true))); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset2, true))); freezer.freezeAllMarkets(); + } + +} + +contract FreezeMarketTests is SparkLendFreezerUnitTestBase { + + address reserve = makeAddr("reserve"); + + function test_freezeMarket_noAuth() public { + vm.expectRevert("SparkLendFreezer/cannot-call"); + freezer.freezeMarket(reserve); + } + + function test_freezeMarket() public { + authority.__setCanCall( + address(this), + address(freezer), + freezer.freezeMarket.selector, + true + ); + + bytes4 configSig = ConfiguratorMock.setReserveFreeze.selector; - assertEq(freezer.canFreeze(), false); + vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(reserve, true))); + freezer.freezeMarket(reserve); } } From 36cab19432da17809d7a6eedaca518a84f4d4bd9 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 11:11:18 -0500 Subject: [PATCH 12/42] feat: set up test --- lib/forge-std | 2 +- src/FreezeWETH.sol | 20 +++++++++++ src/SparkLendFreezer.sol | 12 +++---- src/interfaces/ISparkLendFreezer.sol | 42 +++++++++++++++++++++- test/IntegrationTests.t.sol | 53 ++++++++++++++++++++++++++++ test/Interfaces.sol | 8 +++++ 6 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 src/FreezeWETH.sol create mode 100644 test/IntegrationTests.t.sol create mode 100644 test/Interfaces.sol diff --git a/lib/forge-std b/lib/forge-std index f73c73d..37a37ab 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit 37a37ab73364d6644bfe11edf88a07880f99bd56 diff --git a/src/FreezeWETH.sol b/src/FreezeWETH.sol new file mode 100644 index 0000000..520f2b1 --- /dev/null +++ b/src/FreezeWETH.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import { ISparkLendFreezer } from "src/interfaces/ISparkLendFreezer.sol"; + +contract FreezeWETH { + + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + address public sparkLendFreezer; + + constructor(address sparklendFreezer_) { + sparkLendFreezer = sparklendFreezer_; + } + + function freeze() external { + ISparkLendFreezer(sparkLendFreezer).freezeMarket(WETH); + } + +} diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezer.sol index 14d4db2..4f3d303 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezer.sol @@ -21,10 +21,10 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Declarations and Constructor ***/ /**********************************************************************************************/ - address public immutable poolConfigurator; - address public immutable pool; + address public override immutable poolConfigurator; + address public override immutable pool; - address public authority; + address public override authority; mapping (address => uint256) public override wards; @@ -68,7 +68,7 @@ contract SparkLendFreezer is ISparkLendFreezer { emit Rely(usr); } - function setAuthority(address authority_) external auth { + function setAuthority(address authority_) external override auth { address oldAuthority = authority; authority = authority_; emit SetAuthority(oldAuthority, authority_); @@ -78,7 +78,7 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Auth Functions ***/ /**********************************************************************************************/ - function freezeAllMarkets() external canCall { + function freezeAllMarkets() external override canCall { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { @@ -87,7 +87,7 @@ contract SparkLendFreezer is ISparkLendFreezer { } } - function freezeMarket(address reserve) external canCall { + function freezeMarket(address reserve) external override canCall { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); } diff --git a/src/interfaces/ISparkLendFreezer.sol b/src/interfaces/ISparkLendFreezer.sol index ee692f8..a94a894 100644 --- a/src/interfaces/ISparkLendFreezer.sol +++ b/src/interfaces/ISparkLendFreezer.sol @@ -26,14 +26,33 @@ interface ISparkLendFreezer { /*** Storage Variables ***/ /**********************************************************************************************/ + /** + * @dev Returns the address of the pool configurator. + * @return The address of the pool configurator. + */ + function poolConfigurator() external view returns (address); + + /** + * @dev Returns the address of the pool. + * @return The address of the pool. + */ + function pool() external view returns (address); + + /** + * @dev Returns the address of the authority. + * @return The address of the authority. + */ + function authority() external view returns (address); + /** * @dev Returns a 0 or 1 depending on if the user has been added as an admin. + * 0 means the user is not an admin, 1 means the user is an admin. * @return relied The value of the user's admin status. */ function wards(address user) external view returns (uint256 relied); /**********************************************************************************************/ - /*** Administrative Functions ***/ + /*** Wards Functions ***/ /**********************************************************************************************/ /** @@ -48,4 +67,25 @@ interface ISparkLendFreezer { */ function rely(address usr) external; + /** + * @dev Function to set a new authority, permissioned to wards. + * @param authority The address of the new authority. + */ + function setAuthority(address authority) external; + + /**********************************************************************************************/ + /*** Auth Functions ***/ + /**********************************************************************************************/ + + /** + * @dev Function to freeze a specified market. Permissioned to the `hat` in the Chief. + * @param reserve The address of the market to freeze. + */ + function freezeMarket(address reserve) external; + + /** + * @dev Function to freeze all markets. Permissioned to the `hat` in the Chief. + */ + function freezeAllMarkets() external; + } diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol new file mode 100644 index 0000000..855f08d --- /dev/null +++ b/test/IntegrationTests.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; + +import { SparkLendFreezer } from "src/SparkLendFreezer.sol"; +import { FreezeWETH } from "src/FreezeWETH.sol"; + +import { IAuthorityLike } from "test/Interfaces.sol"; + +contract IntegrationTests is Test { + + address constant AUTHORITY = 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5; + address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; + address constant PAUSE_PROXY = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; + address constant POOL = 0xC13e21B648A5Ee794902342038FF3aDAB66BE987; + address constant POOL_CONFIG = 0x542DBa469bdE58FAeE189ffB60C6b49CE60E0738; + + address mkrWhale = makeAddr("mkrWhale"); + + IAuthorityLike authority = IAuthorityLike(AUTHORITY); + + SparkLendFreezer freezer; + FreezeWETH freezeWeth; + + function setUp() public { + vm.createSelectFork(getChain('mainnet').rpcUrl); + + freezer = new SparkLendFreezer(POOL_CONFIG, POOL, AUTHORITY); + freezeWeth = new FreezeWETH(address(freezer)); + + freezer.rely(PAUSE_PROXY); + freezer.deny(address(this)); + } + + function test_cannotCallWithoutHat() external { + assertTrue(authority.hat() != address(freezeWeth)); + assertTrue( + !authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) + ); + + + } + + function _vote(address spell) internal { + deal(MKR, mkrWhale, 1_000_000 ether); + + vm.startPrank(mkrWhale); + + + } + +} diff --git a/test/Interfaces.sol b/test/Interfaces.sol new file mode 100644 index 0000000..990308b --- /dev/null +++ b/test/Interfaces.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +interface IAuthorityLike { + function canCall(address src, address dst, bytes4 sig) external view returns (bool); + + function hat() external view returns (address); +} From 891405422a7fb2aa6c8f324de67217bf1daff73c Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 11:11:26 -0500 Subject: [PATCH 13/42] forge install: erc20-helpers --- .gitmodules | 3 +++ lib/erc20-helpers | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/erc20-helpers diff --git a/.gitmodules b/.gitmodules index 888d42d..7122960 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/erc20-helpers"] + path = lib/erc20-helpers + url = https://github.com/marsfoundation/erc20-helpers diff --git a/lib/erc20-helpers b/lib/erc20-helpers new file mode 160000 index 0000000..9cf80a0 --- /dev/null +++ b/lib/erc20-helpers @@ -0,0 +1 @@ +Subproject commit 9cf80a0d21692a4442c38bf2cb76c866ee3b7d85 From 6c0058d5c919c4ad51d010d01fcd6cab2e6d68ec Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 15:57:45 -0500 Subject: [PATCH 14/42] feat: get initial freeze tests working --- test/IntegrationTests.t.sol | 75 +++++++++++++++++++++++++++++++++---- test/Interfaces.sol | 28 ++++++++++++++ 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 855f08d..9b6dab7 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -3,22 +3,33 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; +import { SafeERC20 } from "lib/erc20-helpers/src/SafeERC20.sol"; +import { IERC20 } from "lib/erc20-helpers/src/interfaces/IERC20.sol"; + import { SparkLendFreezer } from "src/SparkLendFreezer.sol"; import { FreezeWETH } from "src/FreezeWETH.sol"; -import { IAuthorityLike } from "test/Interfaces.sol"; +import { IACLManagerLike, IAuthorityLike, IPoolDataProviderLike } from "test/Interfaces.sol"; contract IntegrationTests is Test { - address constant AUTHORITY = 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5; - address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; - address constant PAUSE_PROXY = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; - address constant POOL = 0xC13e21B648A5Ee794902342038FF3aDAB66BE987; - address constant POOL_CONFIG = 0x542DBa469bdE58FAeE189ffB60C6b49CE60E0738; + using SafeERC20 for IERC20; + + address constant ACL_MANAGER = 0xdA135Cd78A086025BcdC87B038a1C462032b510C; + address constant AUTHORITY = 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5; + address constant DATA_PROVIDER = 0xFc21d6d146E6086B8359705C8b28512a983db0cb; + address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; + address constant PAUSE_PROXY = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; + address constant POOL = 0xC13e21B648A5Ee794902342038FF3aDAB66BE987; + address constant POOL_CONFIG = 0x542DBa469bdE58FAeE189ffB60C6b49CE60E0738; + address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address mkrWhale = makeAddr("mkrWhale"); + address user = makeAddr("user"); - IAuthorityLike authority = IAuthorityLike(AUTHORITY); + IAuthorityLike authority = IAuthorityLike(AUTHORITY); + IPoolDataProviderLike dataProvider = IPoolDataProviderLike(DATA_PROVIDER); SparkLendFreezer freezer; FreezeWETH freezeWeth; @@ -39,15 +50,63 @@ contract IntegrationTests is Test { !authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) ); + vm.expectRevert("SparkLendFreezer/cannot-call"); + freezeWeth.freeze(); + } + function test_cannotCallWithoutRoleSetup() external { + _vote(address(freezeWeth)); + + assertTrue(authority.hat() == address(freezeWeth)); + assertTrue( + authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) + ); + + vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN + freezeWeth.freeze(); } + // function test_freezeWeth() external { + // _vote(address(freezeWeth)); + + // vm.prank(SPARK_PROXY); + // IACLManagerLike(ACL_MANAGER).addRiskAdmin(address(freezer)); + + // assertEq(_isFrozen(WETH), false); + + // vm.startPrank(user); + // IERC20(WETH).safeApprove(POOL, 1e18); + // pool.supply(config.underlying, amount, user, 0); + // vm.stopPrank(); + + // freezeWeth.freeze(); + + // assertEq(_isFrozen(WETH), true); + + // // NOTE: Not checking pool.swapBorrowRateMode since stable rate isn't enabled on any reserve. + // } + function _vote(address spell) internal { - deal(MKR, mkrWhale, 1_000_000 ether); + uint256 amount = 1_000_000 ether; + + deal(MKR, mkrWhale, amount); vm.startPrank(mkrWhale); + IERC20(MKR).approve(AUTHORITY, amount); + authority.lock(amount); + + address[] memory slate = new address[](1); + slate[0] = spell; + authority.vote(slate); + authority.lift(spell); + vm.stopPrank(); + + assertTrue(authority.hat() == spell); + } + function _isFrozen(address asset) internal view returns (bool isFrozen) { + ( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset); } } diff --git a/test/Interfaces.sol b/test/Interfaces.sol index 990308b..0c2f79d 100644 --- a/test/Interfaces.sol +++ b/test/Interfaces.sol @@ -5,4 +5,32 @@ interface IAuthorityLike { function canCall(address src, address dst, bytes4 sig) external view returns (bool); function hat() external view returns (address); + + function lock(uint256 amount) external; + + function vote(address[] calldata slate) external; + + function lift(address target) external; +} + +interface IACLManagerLike { + function addRiskAdmin(address target) external; +} + +interface IPoolDataProviderLike { + function getReserveConfigurationData(address asset) + external + view + returns ( + uint256 decimals, + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + uint256 reserveFactor, + bool usageAsCollateralEnabled, + bool borrowingEnabled, + bool stableBorrowRateEnabled, + bool isActive, + bool isFrozen + ); } From ed4df8a17b6d49245ba00e1b8d662914c2cc1742 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 15:58:04 -0500 Subject: [PATCH 15/42] forge install: aave-v3-core --- .gitmodules | 3 +++ lib/aave-v3-core | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/aave-v3-core diff --git a/.gitmodules b/.gitmodules index 7122960..27164b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/erc20-helpers"] path = lib/erc20-helpers url = https://github.com/marsfoundation/erc20-helpers +[submodule "lib/aave-v3-core"] + path = lib/aave-v3-core + url = https://github.com/marsfoundation/aave-v3-core diff --git a/lib/aave-v3-core b/lib/aave-v3-core new file mode 160000 index 0000000..6070e82 --- /dev/null +++ b/lib/aave-v3-core @@ -0,0 +1 @@ +Subproject commit 6070e82d962d9b12835c88e68210d0e63f08d035 From 3a0950ab9767f15c0fca6ec6fac63dcaf8a2f483 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 16:18:16 -0500 Subject: [PATCH 16/42] feat: all tests passing --- test/IntegrationTests.t.sol | 93 +++++++++++++++++++++++++++++-------- test/Interfaces.sol | 22 --------- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 9b6dab7..d5ff4a1 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -9,7 +9,12 @@ import { IERC20 } from "lib/erc20-helpers/src/interfaces/IERC20.sol"; import { SparkLendFreezer } from "src/SparkLendFreezer.sol"; import { FreezeWETH } from "src/FreezeWETH.sol"; -import { IACLManagerLike, IAuthorityLike, IPoolDataProviderLike } from "test/Interfaces.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"; +import { IPool } from "lib/aave-v3-core/contracts/interfaces/IPool.sol"; + +import { IAuthorityLike } from "test/Interfaces.sol"; contract IntegrationTests is Test { @@ -25,11 +30,15 @@ contract IntegrationTests is Test { address constant SPARK_PROXY = 0x3300f198988e4C9C63F75dF86De36421f06af8c4; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address mkrWhale = makeAddr("mkrWhale"); - address user = makeAddr("user"); + address mkrWhale = makeAddr("mkrWhale"); + address sparkUser = makeAddr("sparkUser"); + address randomUser = makeAddr("randomUser"); - IAuthorityLike authority = IAuthorityLike(AUTHORITY); - IPoolDataProviderLike dataProvider = IPoolDataProviderLike(DATA_PROVIDER); + IAuthorityLike authority = IAuthorityLike(AUTHORITY); + IACLManager aclManager = IACLManager(ACL_MANAGER); + IPool pool = IPool(POOL); + IPoolConfigurator poolConfig = IPoolConfigurator(POOL_CONFIG); + IPoolDataProvider dataProvider = IPoolDataProvider(DATA_PROVIDER); SparkLendFreezer freezer; FreezeWETH freezeWeth; @@ -62,29 +71,75 @@ contract IntegrationTests is Test { authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) ); - vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN + vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN freezeWeth.freeze(); } - // function test_freezeWeth() external { - // _vote(address(freezeWeth)); + function test_freezeWeth() external { + _vote(address(freezeWeth)); + + vm.prank(SPARK_PROXY); + aclManager.addRiskAdmin(address(freezer)); + + assertEq(_isFrozen(WETH), false); + + deal(WETH, sparkUser, 20e18); // Deal enough for 2 supplies + + // Check user actions + + vm.startPrank(sparkUser); + + // User can supply + IERC20(WETH).safeApprove(POOL, 10e18); + pool.supply(WETH, 10e18, sparkUser, 0); + + // User can borrow + pool.borrow(WETH, 1e18, 2, 0, sparkUser); + + vm.stopPrank(); + + freezeWeth.freeze(); + + assertEq(_isFrozen(WETH), true); + + // Check user actions + // NOTE: Not checking pool.swapBorrowRateMode() since stable rate + // isn't enabled on any reserve. + + vm.startPrank(sparkUser); + + deal(WETH, sparkUser, 10e18); + + // User can't supply + IERC20(WETH).safeApprove(POOL, 10e18); + vm.expectRevert(bytes("28")); // RESERVE_FROZEN + pool.supply(WETH, 10e18, sparkUser, 0); - // vm.prank(SPARK_PROXY); - // IACLManagerLike(ACL_MANAGER).addRiskAdmin(address(freezer)); + // User can't borrow + vm.expectRevert(bytes("28")); // RESERVE_FROZEN + pool.borrow(WETH, 1e18, 2, 0, sparkUser); - // assertEq(_isFrozen(WETH), false); + vm.stopPrank(); + + // Simulate spell after freeze, unfreezing market + vm.prank(SPARK_PROXY); + poolConfig.setReserveFreeze(WETH, false); + + assertEq(_isFrozen(WETH), false); - // vm.startPrank(user); - // IERC20(WETH).safeApprove(POOL, 1e18); - // pool.supply(config.underlying, amount, user, 0); - // vm.stopPrank(); + // Check user actions - // freezeWeth.freeze(); + vm.startPrank(sparkUser); - // assertEq(_isFrozen(WETH), true); + // User can supply + IERC20(WETH).safeApprove(POOL, 10e18); + pool.supply(WETH, 10e18, sparkUser, 0); - // // NOTE: Not checking pool.swapBorrowRateMode since stable rate isn't enabled on any reserve. - // } + // User can borrow + pool.borrow(WETH, 1e18, 2, 0, sparkUser); + + vm.stopPrank(); + } function _vote(address spell) internal { uint256 amount = 1_000_000 ether; diff --git a/test/Interfaces.sol b/test/Interfaces.sol index 0c2f79d..19097e5 100644 --- a/test/Interfaces.sol +++ b/test/Interfaces.sol @@ -12,25 +12,3 @@ interface IAuthorityLike { function lift(address target) external; } - -interface IACLManagerLike { - function addRiskAdmin(address target) external; -} - -interface IPoolDataProviderLike { - function getReserveConfigurationData(address asset) - external - view - returns ( - uint256 decimals, - uint256 ltv, - uint256 liquidationThreshold, - uint256 liquidationBonus, - uint256 reserveFactor, - bool usageAsCollateralEnabled, - bool borrowingEnabled, - bool stableBorrowRateEnabled, - bool isActive, - bool isFrozen - ); -} From 0894430cbea693006c72fe0f06e46665b81a64de Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Wed, 8 Nov 2023 16:24:31 -0500 Subject: [PATCH 17/42] chore: improve test comments --- test/IntegrationTests.t.sol | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index d5ff4a1..420b417 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -85,7 +85,9 @@ contract IntegrationTests is Test { deal(WETH, sparkUser, 20e18); // Deal enough for 2 supplies - // Check user actions + // 1. Check supply/borrow before freeze + // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate + // isn't enabled on any reserve. vm.startPrank(sparkUser); @@ -98,18 +100,17 @@ contract IntegrationTests is Test { vm.stopPrank(); + // 2. Freeze market + + vm.prank(randomUser); // Demonstrate no ACL in spell freezeWeth.freeze(); assertEq(_isFrozen(WETH), true); - // Check user actions - // NOTE: Not checking pool.swapBorrowRateMode() since stable rate - // isn't enabled on any reserve. + // 3. Check supply/borrow after freeze vm.startPrank(sparkUser); - deal(WETH, sparkUser, 10e18); - // User can't supply IERC20(WETH).safeApprove(POOL, 10e18); vm.expectRevert(bytes("28")); // RESERVE_FROZEN @@ -121,13 +122,13 @@ contract IntegrationTests is Test { vm.stopPrank(); - // Simulate spell after freeze, unfreezing market + // 4. Simulate spell after freeze, unfreezing market vm.prank(SPARK_PROXY); poolConfig.setReserveFreeze(WETH, false); assertEq(_isFrozen(WETH), false); - // Check user actions + // 5. Check supply/borrow after unfreeze vm.startPrank(sparkUser); @@ -137,8 +138,6 @@ contract IntegrationTests is Test { // User can borrow pool.borrow(WETH, 1e18, 2, 0, sparkUser); - - vm.stopPrank(); } function _vote(address spell) internal { From 9086ad495f846d6af5c78aa09eab7ceaa7f78f5e Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 9 Nov 2023 09:33:16 -0500 Subject: [PATCH 18/42] fix: update README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1b62350..32af2a3 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,6 @@ $ forge --help $ anvil --help $ cast --help ``` + +*** +*The IP in this repository was assigned to Mars SPC Limited in respect of the MarsOne SP* From faec6fe9141f86b3a247dbb2b2530a27812f72cb Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 9 Nov 2023 09:48:46 -0500 Subject: [PATCH 19/42] fix: update to standard structure --- ...endFreezer.sol => SparkLendFreezerMom.sol} | 59 +++++++------ src/interfaces/ISparkLendFreezer.sol | 51 ------------ src/interfaces/ISparkLendFreezerMom.sol | 13 +++ ...reezer.t.sol => SparkLendFreezerMom.t.sol} | 83 +++++++------------ 4 files changed, 78 insertions(+), 128 deletions(-) rename src/{SparkLendFreezer.sol => SparkLendFreezerMom.sol} (62%) delete mode 100644 src/interfaces/ISparkLendFreezer.sol create mode 100644 src/interfaces/ISparkLendFreezerMom.sol rename test/{SparkLendFreezer.t.sol => SparkLendFreezerMom.t.sol} (52%) diff --git a/src/SparkLendFreezer.sol b/src/SparkLendFreezerMom.sol similarity index 62% rename from src/SparkLendFreezer.sol rename to src/SparkLendFreezerMom.sol index 14d4db2..22eb846 100644 --- a/src/SparkLendFreezer.sol +++ b/src/SparkLendFreezerMom.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.13; -import { ISparkLendFreezer } from "src/interfaces/ISparkLendFreezer.sol"; +import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol"; interface AuthorityLike { function canCall(address src, address dst, bytes4 sig) external view returns (bool); @@ -15,7 +15,7 @@ interface PoolLike { function getReservesList() external view returns (address[] memory); } -contract SparkLendFreezer is ISparkLendFreezer { +contract SparkLendFreezerMom is ISparkLendFreezerMom { /**********************************************************************************************/ /*** Declarations and Constructor ***/ @@ -25,32 +25,28 @@ contract SparkLendFreezer is ISparkLendFreezer { address public immutable pool; address public authority; - - mapping (address => uint256) public override wards; + address public owner; constructor(address poolConfigurator_, address pool_, address authority_) { poolConfigurator = poolConfigurator_; pool = pool_; authority = authority_; + owner = msg.sender; - wards[msg.sender] = 1; - emit Rely(msg.sender); + emit SetOwner(address(0), msg.sender); } /**********************************************************************************************/ /*** Modifiers ***/ /**********************************************************************************************/ - modifier auth { - require(wards[msg.sender] == 1, "SparkLendFreezer/not-authorized"); + modifier onlyOwner { + require(msg.sender == owner, "SparkLendFreezerMom/only-owner"); _; } - modifier canCall { - require( - AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig), - "SparkLendFreezer/cannot-call" - ); + modifier auth { + require(isAuthorized(msg.sender, msg.sig), "SparkLendFreezerMom/not-authorized"); _; } @@ -58,27 +54,22 @@ contract SparkLendFreezer is ISparkLendFreezer { /*** Wards Functions ***/ /**********************************************************************************************/ - function deny(address usr) external override auth { - wards[usr] = 0; - emit Deny(usr); + function setAuthority(address authority_) external onlyOwner { + emit SetAuthority(authority, authority_); + authority = authority_; } - function rely(address usr) external override auth { - wards[usr] = 1; - emit Rely(usr); - } - function setAuthority(address authority_) external auth { - address oldAuthority = authority; - authority = authority_; - emit SetAuthority(oldAuthority, authority_); + function setOwner(address owner_) external onlyOwner { + emit SetOwner(owner, owner_); + owner = owner_; } /**********************************************************************************************/ /*** Auth Functions ***/ /**********************************************************************************************/ - function freezeAllMarkets() external canCall { + function freezeAllMarkets() external auth { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { @@ -87,9 +78,25 @@ contract SparkLendFreezer is ISparkLendFreezer { } } - function freezeMarket(address reserve) external canCall { + function freezeMarket(address reserve) external auth { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); } + /**********************************************************************************************/ + /*** Internal Functions ***/ + /**********************************************************************************************/ + + function isAuthorized(address src, bytes4 sig) internal view returns (bool) { + if (src == address(this)) { + return true; + } else if (src == owner) { + return true; + } else if (authority == address(0)) { + return false; + } else { + return AuthorityLike(authority).canCall(src, address(this), sig); + } + } + } diff --git a/src/interfaces/ISparkLendFreezer.sol b/src/interfaces/ISparkLendFreezer.sol deleted file mode 100644 index ee692f8..0000000 --- a/src/interfaces/ISparkLendFreezer.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.0; - -interface ISparkLendFreezer { - - /**********************************************************************************************/ - /*** Events ***/ - /**********************************************************************************************/ - - event SetOwner(address indexed oldOwner, address indexed newOwner); - event SetAuthority(address indexed oldAuthority, address indexed newAuthority); - - /** - * @dev Event emitted when a new admin is removed from the Conduit. - * @param usr The address of the user to remove. - */ - event Deny(address indexed usr); - - /** - * @dev Event emitted when a new admin is added to the Conduit. - * @param usr The address of the user to add. - */ - event Rely(address indexed usr); - - /**********************************************************************************************/ - /*** Storage Variables ***/ - /**********************************************************************************************/ - - /** - * @dev Returns a 0 or 1 depending on if the user has been added as an admin. - * @return relied The value of the user's admin status. - */ - function wards(address user) external view returns (uint256 relied); - - /**********************************************************************************************/ - /*** Administrative Functions ***/ - /**********************************************************************************************/ - - /** - * @dev Function to remove an addresses admin permissions. - * @param usr The address of the admin. - */ - function deny(address usr) external; - - /** - * @dev Function to give an address admin permissions. - * @param usr The address of the new admin. - */ - function rely(address usr) external; - -} diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol new file mode 100644 index 0000000..0d43a53 --- /dev/null +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.0; + +interface ISparkLendFreezerMom { + + /**********************************************************************************************/ + /*** Events ***/ + /**********************************************************************************************/ + + event SetOwner(address indexed oldOwner, address indexed newOwner); + event SetAuthority(address indexed oldAuthority, address indexed newAuthority); + +} diff --git a/test/SparkLendFreezer.t.sol b/test/SparkLendFreezerMom.t.sol similarity index 52% rename from test/SparkLendFreezer.t.sol rename to test/SparkLendFreezerMom.t.sol index 4a27f7c..107d204 100644 --- a/test/SparkLendFreezer.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -3,96 +3,77 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import { SparkLendFreezer } from "../src/SparkLendFreezer.sol"; +import { SparkLendFreezerMom } from "../src/SparkLendFreezerMom.sol"; import { AuthorityMock, ConfiguratorMock, PoolMock } from "./Mocks.sol"; -contract SparkLendFreezerUnitTestBase is Test { +contract SparkLendFreezerMomUnitTestBase is Test { address public configurator; address public pool; - address public ward; + address public owner; AuthorityMock public authority; - SparkLendFreezer public freezer; + SparkLendFreezerMom public freezer; function setUp() public { - ward = makeAddr("ward"); + owner = makeAddr("owner"); authority = new AuthorityMock(); configurator = address(new ConfiguratorMock()); pool = address(new PoolMock()); - freezer = new SparkLendFreezer(configurator, pool, address(authority)); + freezer = new SparkLendFreezerMom(configurator, pool, address(authority)); - freezer.rely(ward); - freezer.deny(address(this)); + freezer.setOwner(owner); } } -contract ConstructorTests is SparkLendFreezerUnitTestBase { +contract ConstructorTests is SparkLendFreezerMomUnitTestBase { function test_constructor() public { - freezer = new SparkLendFreezer(configurator, pool, address(authority)); + freezer = new SparkLendFreezerMom(configurator, pool, address(authority)); - assertEq(freezer.poolConfigurator(), configurator); - assertEq(freezer.pool(), pool); - assertEq(freezer.authority(), address(authority)); - assertEq(freezer.wards(address(this)), 1); + assertEq(freezer.poolConfigurator(), configurator); + assertEq(freezer.pool(), pool); + assertEq(freezer.authority(), address(authority)); + assertEq(freezer.owner(), address(this)); } } -contract DenyTests is SparkLendFreezerUnitTestBase { +contract SetOwnerTests is SparkLendFreezerMomUnitTestBase { - function test_deny_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-authorized"); - freezer.deny(ward); + function test_setOwner_no_auth() public { + vm.expectRevert("SparkLendFreezerMom/only-owner"); + freezer.setOwner(address(1)); } - function test_deny() public { - assertEq(freezer.wards(ward), 1); + function test_setOwner() public { + address newOwner = makeAddr("newOwner"); + assertEq(freezer.owner(), owner); - vm.prank(ward); - freezer.deny(ward); + vm.prank(owner); + freezer.setOwner(newOwner); - assertEq(freezer.wards(ward), 0); + assertEq(freezer.owner(), newOwner); } } -contract RelyTests is SparkLendFreezerUnitTestBase { - - function test_rely_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-authorized"); - freezer.rely(makeAddr("new ward")); - } - - function test_rely() public { - address newWard = makeAddr("new ward"); - assertEq(freezer.wards(newWard), 0); - - vm.prank(ward); - freezer.rely(newWard); - - assertEq(freezer.wards(ward), 1); - } - -} - -contract SetAuthorityTests is SparkLendFreezerUnitTestBase { +contract SetAuthorityTests is SparkLendFreezerMomUnitTestBase { function test_setAuthority_no_auth() public { - vm.expectRevert("SparkLendFreezer/not-authorized"); - freezer.setAuthority(makeAddr("new authority")); + vm.expectRevert("SparkLendFreezerMom/only-owner"); + freezer.setAuthority(makeAddr("newAuthority")); } function test_setAuthority() public { - address newAuthority = makeAddr("new authority"); + address newAuthority = makeAddr("newAuthority"); assertEq(freezer.authority(), address(authority)); - vm.prank(ward); + vm.prank(owner); freezer.setAuthority(newAuthority); assertEq(freezer.authority(), newAuthority); @@ -100,10 +81,10 @@ contract SetAuthorityTests is SparkLendFreezerUnitTestBase { } -contract FreezeAllMarketsTests is SparkLendFreezerUnitTestBase { +contract FreezeAllMarketsTests is SparkLendFreezerMomUnitTestBase { function test_freezeAllMarkets_noAuth() public { - vm.expectRevert("SparkLendFreezer/cannot-call"); + vm.expectRevert("SparkLendFreezerMom/not-authorized"); freezer.freezeAllMarkets(); } @@ -132,12 +113,12 @@ contract FreezeAllMarketsTests is SparkLendFreezerUnitTestBase { } -contract FreezeMarketTests is SparkLendFreezerUnitTestBase { +contract FreezeMarketTests is SparkLendFreezerMomUnitTestBase { address reserve = makeAddr("reserve"); function test_freezeMarket_noAuth() public { - vm.expectRevert("SparkLendFreezer/cannot-call"); + vm.expectRevert("SparkLendFreezerMom/not-authorized"); freezer.freezeMarket(reserve); } From 9d74877d1a9a7ebc140978fbe6857e0433cccb14 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 9 Nov 2023 09:55:38 -0500 Subject: [PATCH 20/42] feat: get to full natspec --- src/SparkLendFreezerMom.sol | 10 ++++----- src/interfaces/ISparkLendFreezerMom.sol | 27 +++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index c825443..42ad304 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -24,8 +24,8 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { address public override immutable poolConfigurator; address public override immutable pool; - address public authority; - address public owner; + address public override authority; + address public override owner; constructor(address poolConfigurator_, address pool_, address authority_) { poolConfigurator = poolConfigurator_; @@ -51,16 +51,16 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { } /**********************************************************************************************/ - /*** Wards Functions ***/ + /*** Owner Functions ***/ /**********************************************************************************************/ - function setAuthority(address authority_) external onlyOwner { + function setAuthority(address authority_) external override onlyOwner { emit SetAuthority(authority, authority_); authority = authority_; } - function setOwner(address owner_) external onlyOwner { + function setOwner(address owner_) external override onlyOwner { emit SetOwner(owner, owner_); owner = owner_; } diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index edf356b..ebed816 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -7,7 +7,18 @@ interface ISparkLendFreezerMom { /*** Events ***/ /**********************************************************************************************/ + /** + * @dev Event to log the setting of a new owner. + * @param oldOwner The address of the previous owner. + * @param newOwner The address of the new owner. + */ event SetOwner(address indexed oldOwner, address indexed newOwner); + + /** + * @dev Event to log the setting of a new authority. + * @param oldAuthority The address of the previous authority. + * @param newAuthority The address of the new authority. + */ event SetAuthority(address indexed oldAuthority, address indexed newAuthority); /**********************************************************************************************/ @@ -32,16 +43,28 @@ interface ISparkLendFreezerMom { */ function authority() external view returns (address); + /** + * @dev Returns the address of the owner. + * @return The address of the owner. + */ + function owner() external view returns (address); + /**********************************************************************************************/ - /*** Wards Functions ***/ + /*** Owner Functions ***/ /**********************************************************************************************/ /** - * @dev Function to set a new authority, permissioned to wards. + * @dev Function to set a new authority, permissioned to owner. * @param authority The address of the new authority. */ function setAuthority(address authority) external; + /** + * @dev Function to set a new owner, permissioned to owner. + * @param owner The address of the new owner. + */ + function setOwner(address owner) external; + /**********************************************************************************************/ /*** Auth Functions ***/ /**********************************************************************************************/ From 58dc680884f8f8c55afcbe1dac5247f87f664b33 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 9 Nov 2023 09:57:27 -0500 Subject: [PATCH 21/42] feat: add natspec and overrides --- src/SparkLendFreezerMom.sol | 8 +-- src/interfaces/ISparkLendFreezerMom.sol | 70 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index 22eb846..edf49d1 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -54,13 +54,13 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { /*** Wards Functions ***/ /**********************************************************************************************/ - function setAuthority(address authority_) external onlyOwner { + function setAuthority(address authority_) external override onlyOwner { emit SetAuthority(authority, authority_); authority = authority_; } - function setOwner(address owner_) external onlyOwner { + function setOwner(address owner_) external override onlyOwner { emit SetOwner(owner, owner_); owner = owner_; } @@ -69,7 +69,7 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { /*** Auth Functions ***/ /**********************************************************************************************/ - function freezeAllMarkets() external auth { + function freezeAllMarkets() external override auth { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { @@ -78,7 +78,7 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { } } - function freezeMarket(address reserve) external auth { + function freezeMarket(address reserve) external override auth { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); } diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index 0d43a53..ebed816 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -7,7 +7,77 @@ interface ISparkLendFreezerMom { /*** Events ***/ /**********************************************************************************************/ + /** + * @dev Event to log the setting of a new owner. + * @param oldOwner The address of the previous owner. + * @param newOwner The address of the new owner. + */ event SetOwner(address indexed oldOwner, address indexed newOwner); + + /** + * @dev Event to log the setting of a new authority. + * @param oldAuthority The address of the previous authority. + * @param newAuthority The address of the new authority. + */ event SetAuthority(address indexed oldAuthority, address indexed newAuthority); + /**********************************************************************************************/ + /*** Storage Variables ***/ + /**********************************************************************************************/ + + /** + * @dev Returns the address of the pool configurator. + * @return The address of the pool configurator. + */ + function poolConfigurator() external view returns (address); + + /** + * @dev Returns the address of the pool. + * @return The address of the pool. + */ + function pool() external view returns (address); + + /** + * @dev Returns the address of the authority. + * @return The address of the authority. + */ + function authority() external view returns (address); + + /** + * @dev Returns the address of the owner. + * @return The address of the owner. + */ + function owner() external view returns (address); + + /**********************************************************************************************/ + /*** Owner Functions ***/ + /**********************************************************************************************/ + + /** + * @dev Function to set a new authority, permissioned to owner. + * @param authority The address of the new authority. + */ + function setAuthority(address authority) external; + + /** + * @dev Function to set a new owner, permissioned to owner. + * @param owner The address of the new owner. + */ + function setOwner(address owner) external; + + /**********************************************************************************************/ + /*** Auth Functions ***/ + /**********************************************************************************************/ + + /** + * @dev Function to freeze a specified market. Permissioned to the `hat` in the Chief. + * @param reserve The address of the market to freeze. + */ + function freezeMarket(address reserve) external; + + /** + * @dev Function to freeze all markets. Permissioned to the `hat` in the Chief. + */ + function freezeAllMarkets() external; + } From c4f64bf2f82904c78a636eaeb3153ef21c6f6bf1 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 9 Nov 2023 10:11:37 -0500 Subject: [PATCH 22/42] feat: get to full coverage --- test/SparkLendFreezerMom.t.sol | 55 +++++++++++++++++++ test/harnesses/SparkLendFreezerMomHarness.sol | 15 +++++ 2 files changed, 70 insertions(+) create mode 100644 test/harnesses/SparkLendFreezerMomHarness.sol diff --git a/test/SparkLendFreezerMom.t.sol b/test/SparkLendFreezerMom.t.sol index 107d204..9a2dabb 100644 --- a/test/SparkLendFreezerMom.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -5,6 +5,8 @@ import "forge-std/Test.sol"; import { SparkLendFreezerMom } from "../src/SparkLendFreezerMom.sol"; +import { SparkLendFreezerMomHarness } from "./harnesses/SparkLendFreezerMomHarness.sol"; + import { AuthorityMock, ConfiguratorMock, PoolMock } from "./Mocks.sol"; contract SparkLendFreezerMomUnitTestBase is Test { @@ -138,4 +140,57 @@ contract FreezeMarketTests is SparkLendFreezerMomUnitTestBase { } +contract SparkLendFreezerMomIsAuthorizedTest is Test { + + address public configurator; + address public pool; + address public owner; + + AuthorityMock public authority; + + SparkLendFreezerMomHarness public freezer; + + address caller = makeAddr("caller"); + + function setUp() public { + owner = makeAddr("owner"); + + authority = new AuthorityMock(); + configurator = address(new ConfiguratorMock()); + pool = address(new PoolMock()); + freezer = new SparkLendFreezerMomHarness(configurator, pool, address(authority)); + + freezer.setOwner(owner); + } + + function test_isAuthorized_internalCall() external { + assertEq(freezer.isAuthorizedExternal(address(freezer), bytes4("0")), true); + } + + function test_isAuthorized_srcIsOwner() external { + assertEq(freezer.isAuthorizedExternal(owner, bytes4("0")), true); + } + + function test_isAuthorized_authorityIsZero() external { + vm.prank(owner); + freezer.setAuthority(address(0)); + assertEq(freezer.isAuthorizedExternal(caller, bytes4("0")), false); + } + + function test_isAuthorized_canCall() external { + vm.prank(owner); + authority.__setCanCall(caller, address(freezer), bytes4("0"), true); + + vm.expectCall( + address(authority), + abi.encodePacked( + AuthorityMock.canCall.selector, + abi.encode(caller, address(freezer), bytes4("0")) + ) + ); + assertEq(freezer.isAuthorizedExternal(caller, bytes4("0")), true); + } + +} + diff --git a/test/harnesses/SparkLendFreezerMomHarness.sol b/test/harnesses/SparkLendFreezerMomHarness.sol new file mode 100644 index 0000000..dd7e2c8 --- /dev/null +++ b/test/harnesses/SparkLendFreezerMomHarness.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.0; + +import { SparkLendFreezerMom } from "src/SparkLendFreezerMom.sol"; + +contract SparkLendFreezerMomHarness is SparkLendFreezerMom { + + constructor(address poolConfigurator_, address pool_, address authority_) + SparkLendFreezerMom(poolConfigurator_, pool_, authority_) {} + + function isAuthorizedExternal(address src, bytes4 sig) public view returns (bool) { + return super.isAuthorized(src, sig); + } + +} From 21af6aa2f350c7b176cd1aa15214aaeb4056e9d3 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 10 Nov 2023 11:20:03 -0500 Subject: [PATCH 23/42] fix: rm authority from constructor --- src/SparkLendFreezerMom.sol | 3 +-- test/SparkLendFreezerMom.t.sol | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index edf49d1..89cb865 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -27,10 +27,9 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { address public authority; address public owner; - constructor(address poolConfigurator_, address pool_, address authority_) { + constructor(address poolConfigurator_, address pool_) { poolConfigurator = poolConfigurator_; pool = pool_; - authority = authority_; owner = msg.sender; emit SetOwner(address(0), msg.sender); diff --git a/test/SparkLendFreezerMom.t.sol b/test/SparkLendFreezerMom.t.sol index 107d204..49875dc 100644 --- a/test/SparkLendFreezerMom.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -23,8 +23,9 @@ contract SparkLendFreezerMomUnitTestBase is Test { authority = new AuthorityMock(); configurator = address(new ConfiguratorMock()); pool = address(new PoolMock()); - freezer = new SparkLendFreezerMom(configurator, pool, address(authority)); + freezer = new SparkLendFreezerMom(configurator, pool); + freezer.setAuthority(address(authority)); freezer.setOwner(owner); } @@ -33,11 +34,10 @@ contract SparkLendFreezerMomUnitTestBase is Test { contract ConstructorTests is SparkLendFreezerMomUnitTestBase { function test_constructor() public { - freezer = new SparkLendFreezerMom(configurator, pool, address(authority)); + freezer = new SparkLendFreezerMom(configurator, pool); assertEq(freezer.poolConfigurator(), configurator); assertEq(freezer.pool(), pool); - assertEq(freezer.authority(), address(authority)); assertEq(freezer.owner(), address(this)); } From b8e6669f6cb847a2faca6f335f9dc42134c01b9d Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 10 Nov 2023 11:23:48 -0500 Subject: [PATCH 24/42] fix: update comments --- src/SparkLendFreezerMom.sol | 3 +-- src/interfaces/ISparkLendFreezerMom.sol | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index 89cb865..2b648a5 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -50,7 +50,7 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { } /**********************************************************************************************/ - /*** Wards Functions ***/ + /*** Owner Functions ***/ /**********************************************************************************************/ function setAuthority(address authority_) external override onlyOwner { @@ -72,7 +72,6 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { - if (reserves[i] == address(0)) continue; PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); } } diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index ebed816..2c2e06e 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -70,13 +70,17 @@ interface ISparkLendFreezerMom { /**********************************************************************************************/ /** - * @dev Function to freeze a specified market. Permissioned to the `hat` in the Chief. + * @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. * @param reserve The address of the market to freeze. */ function freezeMarket(address reserve) external; /** - * @dev Function to freeze all markets. Permissioned to the `hat` in the Chief. + * @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. */ function freezeAllMarkets() external; From c64a86befb3aff2b89ce818391ebddbae4d22427 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 10 Nov 2023 11:25:47 -0500 Subject: [PATCH 25/42] fix: update to use specified caller, uniform tests --- test/SparkLendFreezerMom.t.sol | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/SparkLendFreezerMom.t.sol b/test/SparkLendFreezerMom.t.sol index 49875dc..2f917ba 100644 --- a/test/SparkLendFreezerMom.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -45,7 +45,7 @@ contract ConstructorTests is SparkLendFreezerMomUnitTestBase { contract SetOwnerTests is SparkLendFreezerMomUnitTestBase { - function test_setOwner_no_auth() public { + function test_setOwner_noAuth() public { vm.expectRevert("SparkLendFreezerMom/only-owner"); freezer.setOwner(address(1)); } @@ -64,7 +64,7 @@ contract SetOwnerTests is SparkLendFreezerMomUnitTestBase { contract SetAuthorityTests is SparkLendFreezerMomUnitTestBase { - function test_setAuthority_no_auth() public { + function test_setAuthority_noAuth() public { vm.expectRevert("SparkLendFreezerMom/only-owner"); freezer.setAuthority(makeAddr("newAuthority")); } @@ -89,8 +89,10 @@ contract FreezeAllMarketsTests is SparkLendFreezerMomUnitTestBase { } function test_freezeAllMarkets() public { + address caller = makeAddr("caller"); + authority.__setCanCall( - address(this), + caller, address(freezer), freezer.freezeAllMarkets.selector, true @@ -105,6 +107,7 @@ contract FreezeAllMarketsTests is SparkLendFreezerMomUnitTestBase { bytes4 poolSig = PoolMock.getReservesList.selector; bytes4 configSig = ConfiguratorMock.setReserveFreeze.selector; + vm.prank(caller); vm.expectCall(pool, abi.encodePacked(poolSig)); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset1, true))); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(asset2, true))); @@ -123,8 +126,10 @@ contract FreezeMarketTests is SparkLendFreezerMomUnitTestBase { } function test_freezeMarket() public { + address caller = makeAddr("caller"); + authority.__setCanCall( - address(this), + caller, address(freezer), freezer.freezeMarket.selector, true @@ -132,6 +137,7 @@ contract FreezeMarketTests is SparkLendFreezerMomUnitTestBase { bytes4 configSig = ConfiguratorMock.setReserveFreeze.selector; + vm.prank(caller); vm.expectCall(configurator, abi.encodePacked(configSig, abi.encode(reserve, true))); freezer.freezeMarket(reserve); } From 3bdc955d9adc6f793d418f3ef33df707b64fa221 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 10 Nov 2023 11:34:52 -0500 Subject: [PATCH 26/42] feat: update to add freeze market events, events testing --- src/SparkLendFreezerMom.sol | 2 + src/interfaces/ISparkLendFreezerMom.sol | 6 +++ test/SparkLendFreezerMom.t.sol | 65 +++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index 2b648a5..1fab45d 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -73,11 +73,13 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { for (uint256 i = 0; i < reserves.length; i++) { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); + emit FreezeMarket(reserves[i]); } } function freezeMarket(address reserve) external override auth { PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); + emit FreezeMarket(reserve); } /**********************************************************************************************/ diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index 2c2e06e..6aaeb99 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -7,6 +7,12 @@ interface ISparkLendFreezerMom { /*** Events ***/ /**********************************************************************************************/ + /** + * @dev Event to log the freezing of a given market in SparkLend. + * @param reserve The address of the market reserve. + */ + event FreezeMarket(address indexed reserve); + /** * @dev Event to log the setting of a new owner. * @param oldOwner The address of the previous owner. diff --git a/test/SparkLendFreezerMom.t.sol b/test/SparkLendFreezerMom.t.sol index 2f917ba..df2ae1e 100644 --- a/test/SparkLendFreezerMom.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -144,4 +144,69 @@ contract FreezeMarketTests is SparkLendFreezerMomUnitTestBase { } +contract EventTests is SparkLendFreezerMomUnitTestBase { + + event FreezeMarket(address indexed reserve); + event SetOwner(address indexed oldOwner, address indexed newOwner); + event SetAuthority(address indexed oldAuthority, address indexed newAuthority); + + function test_setAuthority_eventData() external { + address newAuthority = makeAddr("newAuthority"); + + vm.prank(owner); + vm.expectEmit(address(freezer)); + emit SetAuthority(address(authority), newAuthority); + freezer.setAuthority(newAuthority); + } + + function test_setOwner_eventData() external { + address newOwner = makeAddr("newOwner"); + + vm.prank(owner); + vm.expectEmit(address(freezer)); + emit SetOwner(owner, newOwner); + freezer.setOwner(newOwner); + } + + function test_freezeMarket_eventData() public { + address caller = makeAddr("caller"); + address asset = makeAddr("asset"); + + authority.__setCanCall( + caller, + address(freezer), + freezer.freezeMarket.selector, + true + ); + + vm.prank(caller); + emit FreezeMarket(asset); + freezer.freezeMarket(asset); + } + + function test_freezeAllMarkets_eventData() public { + address caller = makeAddr("caller"); + + authority.__setCanCall( + caller, + address(freezer), + freezer.freezeAllMarkets.selector, + true + ); + + address asset1 = makeAddr("asset1"); + address asset2 = makeAddr("asset2"); + + PoolMock(pool).__addAsset(asset1); + PoolMock(pool).__addAsset(asset2); + + vm.prank(caller); + vm.expectEmit(address(freezer)); + emit FreezeMarket(asset1); + vm.expectEmit(address(freezer)); + emit FreezeMarket(asset2); + freezer.freezeAllMarkets(); + } +} + From 21e0704f04bbeef380dc09bb137f37f5ac831ae9 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Fri, 10 Nov 2023 11:36:23 -0500 Subject: [PATCH 27/42] fix: alignment --- test/SparkLendFreezerMom.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/SparkLendFreezerMom.t.sol b/test/SparkLendFreezerMom.t.sol index df2ae1e..2b6524b 100644 --- a/test/SparkLendFreezerMom.t.sol +++ b/test/SparkLendFreezerMom.t.sol @@ -169,8 +169,8 @@ contract EventTests is SparkLendFreezerMomUnitTestBase { } function test_freezeMarket_eventData() public { - address caller = makeAddr("caller"); - address asset = makeAddr("asset"); + address caller = makeAddr("caller"); + address asset = makeAddr("asset"); authority.__setCanCall( caller, From c7411133a397115cecda7d901e5b50437cb86a55 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Sun, 12 Nov 2023 18:53:06 -0500 Subject: [PATCH 28/42] fix: update as per review --- src/SparkLendFreezerMom.sol | 5 +++-- src/interfaces/ISparkLendFreezerMom.sol | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index 1fab45d..042f3ba 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -72,8 +72,9 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { address[] memory reserves = PoolLike(pool).getReservesList(); for (uint256 i = 0; i < reserves.length; i++) { - PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserves[i], true); - emit FreezeMarket(reserves[i]); + address reserve = reserves[i]; + PoolConfiguratorLike(poolConfigurator).setReserveFreeze(reserve, true); + emit FreezeMarket(reserve); } } diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index 6aaeb99..5270fb1 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -9,6 +9,7 @@ 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. */ event FreezeMarket(address indexed reserve); @@ -78,7 +79,8 @@ interface ISparkLendFreezerMom { /** * @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. + * 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. */ function freezeMarket(address reserve) external; From 4e3d02580fed2332a2e054a2cb836f6c442fbabb Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Sun, 12 Nov 2023 19:12:19 -0500 Subject: [PATCH 29/42] fix: update to use constuctor param, update tests --- ...encySpell_SparkLend_FreezeSingleAsset.sol} | 10 +-- test/IntegrationTests.t.sol | 73 +++++++++++-------- 2 files changed, 47 insertions(+), 36 deletions(-) rename src/{FreezeWETH.sol => spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol} (53%) diff --git a/src/FreezeWETH.sol b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol similarity index 53% rename from src/FreezeWETH.sol rename to src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol index 3910fbd..2fbc908 100644 --- a/src/FreezeWETH.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol @@ -3,18 +3,18 @@ pragma solidity ^0.8.13; import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol"; -contract FreezeWETH { - - address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; +contract EmergencySpell_SparkLend_FreezeSingleAsset { address public sparkLendFreezer; + address public reserve; - constructor(address sparklendFreezer_) { + constructor(address sparklendFreezer_, address reserve_) { sparkLendFreezer = sparklendFreezer_; + reserve = reserve_; } function freeze() external { - ISparkLendFreezerMom(sparkLendFreezer).freezeMarket(WETH); + ISparkLendFreezerMom(sparkLendFreezer).freezeMarket(reserve); } } diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index e5a1481..994283f 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -7,7 +7,8 @@ import { SafeERC20 } from "lib/erc20-helpers/src/SafeERC20.sol"; import { IERC20 } from "lib/erc20-helpers/src/interfaces/IERC20.sol"; import { SparkLendFreezerMom } from "src/SparkLendFreezerMom.sol"; -import { FreezeWETH } from "src/FreezeWETH.sol"; +import { EmergencySpell_SparkLend_FreezeSingleAsset as FreezeSingleAssetSpell } + from "src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol"; import { IACLManager } from "lib/aave-v3-core/contracts/interfaces/IACLManager.sol"; import { IPoolConfigurator } from "lib/aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; @@ -21,7 +22,7 @@ contract IntegrationTests is Test { using SafeERC20 for IERC20; address constant ACL_MANAGER = 0xdA135Cd78A086025BcdC87B038a1C462032b510C; - address constant AUTHORITY = 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5; + address constant AUTHORITY = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0; address constant DATA_PROVIDER = 0xFc21d6d146E6086B8359705C8b28512a983db0cb; address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; address constant PAUSE_PROXY = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; @@ -40,50 +41,54 @@ contract IntegrationTests is Test { IPoolConfigurator poolConfig = IPoolConfigurator(POOL_CONFIG); IPoolDataProvider dataProvider = IPoolDataProvider(DATA_PROVIDER); - SparkLendFreezerMom freezer; - FreezeWETH freezeWeth; + SparkLendFreezerMom freezer; + FreezeSingleAssetSpell freezeWethSpell; function setUp() public { vm.createSelectFork(getChain('mainnet').rpcUrl); - freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); - freezeWeth = new FreezeWETH(address(freezer)); + freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); + freezeWethSpell = new FreezeSingleAssetSpell(address(freezer), WETH); freezer.setAuthority(AUTHORITY); freezer.setOwner(PAUSE_PROXY); } function test_cannotCallWithoutHat() external { - assertTrue(authority.hat() != address(freezeWeth)); + assertTrue(authority.hat() != address(freezeWethSpell)); assertTrue( - !authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) + !authority.canCall( + address(freezeWethSpell), + address(freezer), + freezer.freezeMarket.selector + ) ); vm.expectRevert("SparkLendFreezerMom/not-authorized"); - freezeWeth.freeze(); + freezeWethSpell.freeze(); } function test_cannotCallWithoutRoleSetup() external { - _vote(address(freezeWeth)); + _vote(address(freezeWethSpell)); - assertTrue(authority.hat() == address(freezeWeth)); + assertTrue(authority.hat() == address(freezeWethSpell)); assertTrue( - authority.canCall(address(freezeWeth), address(freezer), freezer.freezeMarket.selector) + authority.canCall(address(freezeWethSpell), address(freezer), freezer.freezeMarket.selector) ); vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN - freezeWeth.freeze(); + freezeWethSpell.freeze(); } - function test_freezeWeth() external { - _vote(address(freezeWeth)); + function test_freezeWethSpell() external { + _vote(address(freezeWethSpell)); vm.prank(SPARK_PROXY); aclManager.addRiskAdmin(address(freezer)); assertEq(_isFrozen(WETH), false); - deal(WETH, sparkUser, 20e18); // Deal enough for 2 supplies + deal(WETH, sparkUser, 20 ether); // Deal enough for 2 supplies // 1. Check supply/borrow before freeze // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate @@ -91,19 +96,20 @@ contract IntegrationTests is Test { vm.startPrank(sparkUser); - // User can supply - IERC20(WETH).safeApprove(POOL, 10e18); - pool.supply(WETH, 10e18, sparkUser, 0); + IERC20(WETH).safeApprove(POOL, type(uint256).max); - // User can borrow - pool.borrow(WETH, 1e18, 2, 0, sparkUser); + // User can supply, borrow, repay, and withdraw + pool.supply(WETH, 10 ether, sparkUser, 0); + pool.borrow(WETH, 1 ether, 2, 0, sparkUser); + pool.repay(WETH, 0.5 ether, 2, sparkUser); + pool.withdraw(WETH, 1 ether, sparkUser); vm.stopPrank(); // 2. Freeze market vm.prank(randomUser); // Demonstrate no ACL in spell - freezeWeth.freeze(); + freezeWethSpell.freeze(); assertEq(_isFrozen(WETH), true); @@ -112,13 +118,16 @@ contract IntegrationTests is Test { vm.startPrank(sparkUser); // User can't supply - IERC20(WETH).safeApprove(POOL, 10e18); vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.supply(WETH, 10e18, sparkUser, 0); + pool.supply(WETH, 10 ether, sparkUser, 0); // User can't borrow vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.borrow(WETH, 1e18, 2, 0, sparkUser); + pool.borrow(WETH, 1 ether, 2, 0, sparkUser); + + // User can still repay and withdraw + pool.repay(WETH, 0.5 ether, 2, sparkUser); + pool.withdraw(WETH, 1 ether, sparkUser); vm.stopPrank(); @@ -132,12 +141,11 @@ contract IntegrationTests is Test { vm.startPrank(sparkUser); - // User can supply - IERC20(WETH).safeApprove(POOL, 10e18); - pool.supply(WETH, 10e18, sparkUser, 0); - - // User can borrow - pool.borrow(WETH, 1e18, 2, 0, sparkUser); + // User can supply, borrow, repay, and withdraw + pool.supply(WETH, 10 ether, sparkUser, 0); + pool.borrow(WETH, 1 ether, 2, 0, sparkUser); + pool.repay(WETH, 1 ether, 2, sparkUser); + pool.withdraw(WETH, 1 ether, sparkUser); } function _vote(address spell) internal { @@ -152,6 +160,9 @@ contract IntegrationTests is Test { address[] memory slate = new address[](1); slate[0] = spell; authority.vote(slate); + + vm.roll(block.number + 1); + authority.lift(spell); vm.stopPrank(); From 24d642b8439aed3c19004033a4c5262243150492 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Sun, 12 Nov 2023 19:13:33 -0500 Subject: [PATCH 30/42] fix: update comments --- test/IntegrationTests.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 994283f..42c3fdf 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -90,7 +90,7 @@ contract IntegrationTests is Test { deal(WETH, sparkUser, 20 ether); // Deal enough for 2 supplies - // 1. Check supply/borrow before freeze + // 1. Check user actions before freeze // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate // isn't enabled on any reserve. @@ -113,7 +113,7 @@ contract IntegrationTests is Test { assertEq(_isFrozen(WETH), true); - // 3. Check supply/borrow after freeze + // 3. Check user actions after freeze vm.startPrank(sparkUser); @@ -137,7 +137,7 @@ contract IntegrationTests is Test { assertEq(_isFrozen(WETH), false); - // 5. Check supply/borrow after unfreeze + // 5. Check user actions after unfreeze vm.startPrank(sparkUser); From 43ebac158bd2d52ac9255c6514bb5b3b0ced54be Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Mon, 13 Nov 2023 09:25:07 -0500 Subject: [PATCH 31/42] fix: use immutable --- src/SparkLendFreezerMom.sol | 4 ++-- src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SparkLendFreezerMom.sol b/src/SparkLendFreezerMom.sol index 21b676b..6b6afd5 100644 --- a/src/SparkLendFreezerMom.sol +++ b/src/SparkLendFreezerMom.sol @@ -21,8 +21,8 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom { /*** Declarations and Constructor ***/ /**********************************************************************************************/ - address public override immutable poolConfigurator; - address public override immutable pool; + address public immutable override poolConfigurator; + address public immutable override pool; address public override authority; address public override owner; diff --git a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol index 2fbc908..d5593ac 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol @@ -5,8 +5,8 @@ import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol"; contract EmergencySpell_SparkLend_FreezeSingleAsset { - address public sparkLendFreezer; - address public reserve; + address public immutable sparkLendFreezer; + address public immutable reserve; constructor(address sparklendFreezer_, address reserve_) { sparkLendFreezer = sparklendFreezer_; From c11c530f0afb61b41a2c69765cd4543d4bbb07ca Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Mon, 13 Nov 2023 09:26:52 -0500 Subject: [PATCH 32/42] fix: update natspec --- src/interfaces/ISparkLendFreezerMom.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interfaces/ISparkLendFreezerMom.sol b/src/interfaces/ISparkLendFreezerMom.sol index 5270fb1..1ed3c54 100644 --- a/src/interfaces/ISparkLendFreezerMom.sol +++ b/src/interfaces/ISparkLendFreezerMom.sol @@ -88,7 +88,8 @@ interface ISparkLendFreezerMom { /** * @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. + * to call the function. Note that the `authority` in this contract is assumed to be + * the Chief in the MakerDAO protocol. */ function freezeAllMarkets() external; From 55594cc2c365aba21bb60191cb4ebf2a66b6676d Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Mon, 13 Nov 2023 09:43:58 -0500 Subject: [PATCH 33/42] fix: update naming --- .../EmergencySpell_SparkLend_FreezeSingleAsset.sol | 10 +++++----- test/IntegrationTests.t.sol | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol index d5593ac..59d2375 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol @@ -5,16 +5,16 @@ import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol"; contract EmergencySpell_SparkLend_FreezeSingleAsset { - address public immutable sparkLendFreezer; + address public immutable sparkLendFreezerMom; address public immutable reserve; - constructor(address sparklendFreezer_, address reserve_) { - sparkLendFreezer = sparklendFreezer_; - reserve = reserve_; + constructor(address sparklendFreezerMom_, address reserve_) { + sparkLendFreezerMom = sparklendFreezerMom_; + reserve = reserve_; } function freeze() external { - ISparkLendFreezerMom(sparkLendFreezer).freezeMarket(reserve); + ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve); } } diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 42c3fdf..ec037a6 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -73,7 +73,11 @@ contract IntegrationTests is Test { assertTrue(authority.hat() == address(freezeWethSpell)); assertTrue( - authority.canCall(address(freezeWethSpell), address(freezer), freezer.freezeMarket.selector) + authority.canCall( + address(freezeWethSpell), + address(freezer), + freezer.freezeMarket.selector + ) ); vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN From 88c5ea36b46cc7d982de3a74f34958779063ffca Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 10:15:18 -0500 Subject: [PATCH 34/42] feat: add freeze all spell --- ...mergencySpell_SparkLend_FreezeAllAssets.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol diff --git a/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol new file mode 100644 index 0000000..8c27490 --- /dev/null +++ b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import { ISparkLendFreezerMom } from "src/interfaces/ISparkLendFreezerMom.sol"; + +contract EmergencySpell_SparkLend_FreezeAllAssets { + + address public immutable sparkLendFreezerMom; + + constructor(address sparklendFreezerMom_) { + sparkLendFreezerMom = sparklendFreezerMom_; + } + + function freeze() external { + ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets(); + } + +} From 9c1d8f0750febc36b2a1583d931859784ae9c208 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 11:12:32 -0500 Subject: [PATCH 35/42] feat: test all assets --- test/IntegrationTests.t.sol | 199 +++++++++++++++++++++++++----------- 1 file changed, 138 insertions(+), 61 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index ec037a6..8cd14f1 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -17,9 +17,7 @@ import { IPool } from "lib/aave-v3-core/contracts/interfaces/IPool.s import { IAuthorityLike } from "test/Interfaces.sol"; -contract IntegrationTests is Test { - - using SafeERC20 for IERC20; +contract IntegrationTestsBase is Test { address constant ACL_MANAGER = 0xdA135Cd78A086025BcdC87B038a1C462032b510C; address constant AUTHORITY = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0; @@ -42,140 +40,219 @@ contract IntegrationTests is Test { IPoolDataProvider dataProvider = IPoolDataProvider(DATA_PROVIDER); SparkLendFreezerMom freezer; - FreezeSingleAssetSpell freezeWethSpell; - function setUp() public { + function setUp() public virtual { vm.createSelectFork(getChain('mainnet').rpcUrl); - freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); - freezeWethSpell = new FreezeSingleAssetSpell(address(freezer), WETH); + freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); freezer.setAuthority(AUTHORITY); freezer.setOwner(PAUSE_PROXY); } + function _vote(address spell) internal { + uint256 amount = 1_000_000 ether; + + deal(MKR, mkrWhale, amount); + + vm.startPrank(mkrWhale); + IERC20(MKR).approve(AUTHORITY, amount); + authority.lock(amount); + + address[] memory slate = new address[](1); + slate[0] = spell; + authority.vote(slate); + + vm.roll(block.number + 1); + + authority.lift(spell); + + vm.stopPrank(); + + assertTrue(authority.hat() == spell); + } + +} + +contract FreezeSingleAssetSpellFailures is IntegrationTestsBase { + + FreezeSingleAssetSpell freezeAssetSpell; + + function setUp() public override { + super.setUp(); + freezeAssetSpell = new FreezeSingleAssetSpell(address(freezer), WETH); + } + function test_cannotCallWithoutHat() external { - assertTrue(authority.hat() != address(freezeWethSpell)); + assertTrue(authority.hat() != address(freezeAssetSpell)); assertTrue( !authority.canCall( - address(freezeWethSpell), + address(freezeAssetSpell), address(freezer), freezer.freezeMarket.selector ) ); vm.expectRevert("SparkLendFreezerMom/not-authorized"); - freezeWethSpell.freeze(); + freezeAssetSpell.freeze(); } function test_cannotCallWithoutRoleSetup() external { - _vote(address(freezeWethSpell)); + _vote(address(freezeAssetSpell)); - assertTrue(authority.hat() == address(freezeWethSpell)); + assertTrue(authority.hat() == address(freezeAssetSpell)); assertTrue( authority.canCall( - address(freezeWethSpell), + address(freezeAssetSpell), address(freezer), freezer.freezeMarket.selector ) ); vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN - freezeWethSpell.freeze(); + freezeAssetSpell.freeze(); } - function test_freezeWethSpell() external { - _vote(address(freezeWethSpell)); +} + +contract FreezeSingleAssetSpellTest is IntegrationTestsBase { + + using SafeERC20 for IERC20; + + address[] public untestedReserves; + function setUp() public override { + super.setUp(); vm.prank(SPARK_PROXY); aclManager.addRiskAdmin(address(freezer)); + } + + function test_freezeAssetSpell_allAssets() external { + address[] memory reserves = pool.getReservesList(); - assertEq(_isFrozen(WETH), false); + assertEq(reserves.length, 9); - deal(WETH, sparkUser, 20 ether); // Deal enough for 2 supplies + deal(WETH, sparkUser, 1_000 ether); - // 1. Check user actions before freeze + // Since not all reserves are collateral, post enough WETH to ensure all reserves + // can be borrowed. + vm.startPrank(sparkUser); + IERC20(WETH).safeApprove(POOL, type(uint256).max); + pool.supply(WETH, 1_000 ether, sparkUser, 0); + vm.stopPrank(); + + for (uint256 i = 0; i < reserves.length; i++) { + // If the asset is frozen on mainnet, skip the test + if (_isFrozen(reserves[i])) { + untestedReserves.push(reserves[i]); + continue; + } + + uint256 snapshot = vm.snapshot(); + address freezeAssetSpell + = address(new FreezeSingleAssetSpell(address(freezer), reserves[i])); + + _testFreezeAsset(freezeAssetSpell, reserves[i]); + vm.revertTo(snapshot); + } + + assertEq(untestedReserves.length, 1); + assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC + } + + function _testFreezeAsset(address spell, address asset) internal { + // 1. Setup spell, max out supply caps so that they aren't hit during test + + _vote(spell); + + vm.startPrank(SPARK_PROXY); + poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP + poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP + vm.stopPrank(); + + assertEq(_isFrozen(asset), false); + + uint256 decimals = IERC20(asset).decimals(); + + uint256 supplyAmount = 1_000 * 10 ** decimals; + uint256 withdrawAmount = 1 * 10 ** decimals; + uint256 borrowAmount = 1 * 10 ** decimals; + uint256 repayAmount = 1 * 10 ** decimals / 2; // 0.5 + + deal(asset, sparkUser, supplyAmount * 2); // Deal enough for 2 supplies + + // 2. Check user actions before freeze // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate // isn't enabled on any reserve. vm.startPrank(sparkUser); - IERC20(WETH).safeApprove(POOL, type(uint256).max); + IERC20(asset).safeApprove(POOL, type(uint256).max); // User can supply, borrow, repay, and withdraw - pool.supply(WETH, 10 ether, sparkUser, 0); - pool.borrow(WETH, 1 ether, 2, 0, sparkUser); - pool.repay(WETH, 0.5 ether, 2, sparkUser); - pool.withdraw(WETH, 1 ether, sparkUser); + pool.supply(asset, supplyAmount, sparkUser, 0); + pool.withdraw(asset, withdrawAmount, sparkUser); + + if (_borrowingEnabled(asset)) { + pool.borrow(asset, borrowAmount, 2, 0, sparkUser); + pool.repay(asset, repayAmount, 2, sparkUser); + } vm.stopPrank(); - // 2. Freeze market + // 3. Freeze market vm.prank(randomUser); // Demonstrate no ACL in spell - freezeWethSpell.freeze(); + FreezeSingleAssetSpell(spell).freeze(); - assertEq(_isFrozen(WETH), true); + assertEq(_isFrozen(asset), true); - // 3. Check user actions after freeze + // 4. Check user actions after freeze vm.startPrank(sparkUser); // User can't supply vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.supply(WETH, 10 ether, sparkUser, 0); + pool.supply(asset, supplyAmount, sparkUser, 0); // User can't borrow vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.borrow(WETH, 1 ether, 2, 0, sparkUser); + pool.borrow(asset, borrowAmount, 2, 0, sparkUser); // User can still repay and withdraw - pool.repay(WETH, 0.5 ether, 2, sparkUser); - pool.withdraw(WETH, 1 ether, sparkUser); + if (_borrowingEnabled(asset)) { + pool.repay(asset, repayAmount, 2, sparkUser); + } + pool.withdraw(asset, withdrawAmount, sparkUser); vm.stopPrank(); - // 4. Simulate spell after freeze, unfreezing market + // 5. Simulate spell after freeze, unfreezing market vm.prank(SPARK_PROXY); - poolConfig.setReserveFreeze(WETH, false); + poolConfig.setReserveFreeze(asset, false); - assertEq(_isFrozen(WETH), false); + assertEq(_isFrozen(asset), false); - // 5. Check user actions after unfreeze + // 6. Check user actions after unfreeze vm.startPrank(sparkUser); // User can supply, borrow, repay, and withdraw - pool.supply(WETH, 10 ether, sparkUser, 0); - pool.borrow(WETH, 1 ether, 2, 0, sparkUser); - pool.repay(WETH, 1 ether, 2, sparkUser); - pool.withdraw(WETH, 1 ether, sparkUser); - } - - function _vote(address spell) internal { - uint256 amount = 1_000_000 ether; + pool.supply(asset, supplyAmount, sparkUser, 0); + pool.withdraw(asset, withdrawAmount, sparkUser); - deal(MKR, mkrWhale, amount); - - vm.startPrank(mkrWhale); - IERC20(MKR).approve(AUTHORITY, amount); - authority.lock(amount); - - address[] memory slate = new address[](1); - slate[0] = spell; - authority.vote(slate); - - vm.roll(block.number + 1); - - authority.lift(spell); - - vm.stopPrank(); - - assertTrue(authority.hat() == spell); + if (_borrowingEnabled(asset)) { + pool.borrow(asset, borrowAmount, 2, 0, sparkUser); + pool.repay(asset, repayAmount, 2, sparkUser); + } } function _isFrozen(address asset) internal view returns (bool isFrozen) { ( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset); } + function _borrowingEnabled(address asset) internal view returns (bool borrowingEnabled) { + ( ,,,,,, borrowingEnabled,,, ) = dataProvider.getReserveConfigurationData(asset); + } + } From 6ece12a54e3a201508a7996e089529f9141a8297 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 15:01:21 -0500 Subject: [PATCH 36/42] feat: refactor tests to use internal functions --- test/IntegrationTests.t.sol | 227 ++++++++++++++++++++---------------- 1 file changed, 127 insertions(+), 100 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 8cd14f1..3aa2840 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -19,6 +19,8 @@ import { IAuthorityLike } from "test/Interfaces.sol"; contract IntegrationTestsBase is Test { + using SafeERC20 for IERC20; + address constant ACL_MANAGER = 0xdA135Cd78A086025BcdC87B038a1C462032b510C; address constant AUTHORITY = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0; address constant DATA_PROVIDER = 0xFc21d6d146E6086B8359705C8b28512a983db0cb; @@ -42,7 +44,7 @@ contract IntegrationTestsBase is Test { SparkLendFreezerMom freezer; function setUp() public virtual { - vm.createSelectFork(getChain('mainnet').rpcUrl); + vm.createSelectFork(getChain('mainnet').rpcUrl, 18_572_000); freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); @@ -72,6 +74,79 @@ contract IntegrationTestsBase is Test { assertTrue(authority.hat() == spell); } + function _checkUserActionsUnfrozen( + address asset, + uint256 supplyAmount, + uint256 withdrawAmount, + uint256 borrowAmount, + uint256 repayAmount + ) + internal + { + assertEq(_isFrozen(asset), false); + + deal(asset, sparkUser, supplyAmount); + + // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate + // isn't enabled on any reserve. + + vm.startPrank(sparkUser); + + IERC20(asset).safeApprove(POOL, type(uint256).max); + + // User can supply and withdraw collateral always + pool.supply(asset, supplyAmount, sparkUser, 0); + pool.withdraw(asset, withdrawAmount, sparkUser); + + // User can borrow and repay if borrowing is enabled + if (_borrowingEnabled(asset)) { + pool.borrow(asset, borrowAmount, 2, 0, sparkUser); + pool.repay(asset, repayAmount, 2, sparkUser); + } + + vm.stopPrank(); + } + + function _checkUserActionsFrozen( + address asset, + uint256 supplyAmount, + uint256 withdrawAmount, + uint256 borrowAmount, + uint256 repayAmount + ) + internal + { + assertEq(_isFrozen(asset), true); + + vm.startPrank(sparkUser); + + // User can't supply + vm.expectRevert(bytes("28")); // RESERVE_FROZEN + pool.supply(asset, supplyAmount, sparkUser, 0); + + // User can't borrow + vm.expectRevert(bytes("28")); // RESERVE_FROZEN + pool.borrow(asset, borrowAmount, 2, 0, sparkUser); + + // User can still withdraw collateral always + pool.withdraw(asset, withdrawAmount, sparkUser); + + // User can repay if borrowing was enabled + if (_borrowingEnabled(asset)) { + pool.repay(asset, repayAmount, 2, sparkUser); + } + + vm.stopPrank(); + } + + function _isFrozen(address asset) internal view returns (bool isFrozen) { + ( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset); + } + + function _borrowingEnabled(address asset) internal view returns (bool borrowingEnabled) { + ( ,,,,,, borrowingEnabled,,, ) = dataProvider.getReserveConfigurationData(asset); + } + } contract FreezeSingleAssetSpellFailures is IntegrationTestsBase { @@ -142,17 +217,64 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { vm.stopPrank(); for (uint256 i = 0; i < reserves.length; i++) { + address asset = reserves[i]; + // If the asset is frozen on mainnet, skip the test - if (_isFrozen(reserves[i])) { - untestedReserves.push(reserves[i]); + if (_isFrozen(asset)) { + untestedReserves.push(asset); continue; } + uint256 decimals = IERC20(asset).decimals(); + + uint256 supplyAmount = 1_000 * 10 ** decimals; + uint256 withdrawAmount = 1 * 10 ** decimals; + uint256 borrowAmount = 1 * 10 ** decimals; + uint256 repayAmount = 1 * 10 ** decimals / 2; // 0.5 + uint256 snapshot = vm.snapshot(); + address freezeAssetSpell - = address(new FreezeSingleAssetSpell(address(freezer), reserves[i])); + = address(new FreezeSingleAssetSpell(address(freezer), asset)); + + // Setup spell, max out supply caps so that they aren't hit during test + _vote(freezeAssetSpell); + + vm.startPrank(SPARK_PROXY); + poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP + poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP + vm.stopPrank(); + + _checkUserActionsUnfrozen( + asset, + supplyAmount, + withdrawAmount, + borrowAmount, + repayAmount + ); + + vm.prank(randomUser); // Demonstrate no ACL in spell + FreezeSingleAssetSpell(freezeAssetSpell).freeze(); + + _checkUserActionsFrozen( + asset, + supplyAmount, + withdrawAmount, + borrowAmount, + repayAmount + ); + + vm.prank(SPARK_PROXY); + poolConfig.setReserveFreeze(asset, false); + + _checkUserActionsUnfrozen( + asset, + supplyAmount, + withdrawAmount, + borrowAmount, + repayAmount + ); - _testFreezeAsset(freezeAssetSpell, reserves[i]); vm.revertTo(snapshot); } @@ -160,99 +282,4 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC } - function _testFreezeAsset(address spell, address asset) internal { - // 1. Setup spell, max out supply caps so that they aren't hit during test - - _vote(spell); - - vm.startPrank(SPARK_PROXY); - poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP - poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP - vm.stopPrank(); - - assertEq(_isFrozen(asset), false); - - uint256 decimals = IERC20(asset).decimals(); - - uint256 supplyAmount = 1_000 * 10 ** decimals; - uint256 withdrawAmount = 1 * 10 ** decimals; - uint256 borrowAmount = 1 * 10 ** decimals; - uint256 repayAmount = 1 * 10 ** decimals / 2; // 0.5 - - deal(asset, sparkUser, supplyAmount * 2); // Deal enough for 2 supplies - - // 2. Check user actions before freeze - // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate - // isn't enabled on any reserve. - - vm.startPrank(sparkUser); - - IERC20(asset).safeApprove(POOL, type(uint256).max); - - // User can supply, borrow, repay, and withdraw - pool.supply(asset, supplyAmount, sparkUser, 0); - pool.withdraw(asset, withdrawAmount, sparkUser); - - if (_borrowingEnabled(asset)) { - pool.borrow(asset, borrowAmount, 2, 0, sparkUser); - pool.repay(asset, repayAmount, 2, sparkUser); - } - - vm.stopPrank(); - - // 3. Freeze market - - vm.prank(randomUser); // Demonstrate no ACL in spell - FreezeSingleAssetSpell(spell).freeze(); - - assertEq(_isFrozen(asset), true); - - // 4. Check user actions after freeze - - vm.startPrank(sparkUser); - - // User can't supply - vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.supply(asset, supplyAmount, sparkUser, 0); - - // User can't borrow - vm.expectRevert(bytes("28")); // RESERVE_FROZEN - pool.borrow(asset, borrowAmount, 2, 0, sparkUser); - - // User can still repay and withdraw - if (_borrowingEnabled(asset)) { - pool.repay(asset, repayAmount, 2, sparkUser); - } - pool.withdraw(asset, withdrawAmount, sparkUser); - - vm.stopPrank(); - - // 5. Simulate spell after freeze, unfreezing market - vm.prank(SPARK_PROXY); - poolConfig.setReserveFreeze(asset, false); - - assertEq(_isFrozen(asset), false); - - // 6. Check user actions after unfreeze - - vm.startPrank(sparkUser); - - // User can supply, borrow, repay, and withdraw - pool.supply(asset, supplyAmount, sparkUser, 0); - pool.withdraw(asset, withdrawAmount, sparkUser); - - if (_borrowingEnabled(asset)) { - pool.borrow(asset, borrowAmount, 2, 0, sparkUser); - pool.repay(asset, repayAmount, 2, sparkUser); - } - } - - function _isFrozen(address asset) internal view returns (bool isFrozen) { - ( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset); - } - - function _borrowingEnabled(address asset) internal view returns (bool borrowingEnabled) { - ( ,,,,,, borrowingEnabled,,, ) = dataProvider.getReserveConfigurationData(asset); - } - } From d4f4c54caff3920d32a38fdfd9cbbb2ab9431c56 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 15:04:08 -0500 Subject: [PATCH 37/42] feat: add failure tests for all asset spell --- test/IntegrationTests.t.sol | 47 ++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 3aa2840..0289d8f 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -7,9 +7,13 @@ import { SafeERC20 } from "lib/erc20-helpers/src/SafeERC20.sol"; import { IERC20 } from "lib/erc20-helpers/src/interfaces/IERC20.sol"; import { SparkLendFreezerMom } from "src/SparkLendFreezerMom.sol"; + import { EmergencySpell_SparkLend_FreezeSingleAsset as FreezeSingleAssetSpell } from "src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol"; +import { EmergencySpell_SparkLend_FreezeAllAssets as FreezeAllAssetsSpell } + from "src/spells/EmergencySpell_SparkLend_FreezeAllAssets.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"; @@ -41,7 +45,7 @@ contract IntegrationTestsBase is Test { IPoolConfigurator poolConfig = IPoolConfigurator(POOL_CONFIG); IPoolDataProvider dataProvider = IPoolDataProvider(DATA_PROVIDER); - SparkLendFreezerMom freezer; + SparkLendFreezerMom freezer; function setUp() public virtual { vm.createSelectFork(getChain('mainnet').rpcUrl, 18_572_000); @@ -190,6 +194,47 @@ contract FreezeSingleAssetSpellFailures is IntegrationTestsBase { } +contract FreezeAllAssetsSpellFailures is IntegrationTestsBase { + + FreezeAllAssetsSpell freezeAllAssetsSpell; + + function setUp() public override { + super.setUp(); + freezeAllAssetsSpell = new FreezeAllAssetsSpell(address(freezer)); + } + + function test_cannotCallWithoutHat() external { + assertTrue(authority.hat() != address(freezeAllAssetsSpell)); + assertTrue( + !authority.canCall( + address(freezeAllAssetsSpell), + address(freezer), + freezer.freezeAllMarkets.selector + ) + ); + + vm.expectRevert("SparkLendFreezerMom/not-authorized"); + freezeAllAssetsSpell.freeze(); + } + + function test_cannotCallWithoutRoleSetup() external { + _vote(address(freezeAllAssetsSpell)); + + assertTrue(authority.hat() == address(freezeAllAssetsSpell)); + assertTrue( + authority.canCall( + address(freezeAllAssetsSpell), + address(freezer), + freezer.freezeAllMarkets.selector + ) + ); + + vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN + freezeAllAssetsSpell.freeze(); + } + +} + contract FreezeSingleAssetSpellTest is IntegrationTestsBase { using SafeERC20 for IERC20; From 363a32a4305e4104095acc4ef20d08f0a0b24cb1 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 16:14:14 -0500 Subject: [PATCH 38/42] feat: all tests passing --- test/IntegrationTests.t.sol | 184 +++++++++++++++++++++++++++++++++--- 1 file changed, 169 insertions(+), 15 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 0289d8f..4abbfc7 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -36,7 +36,6 @@ contract IntegrationTestsBase is Test { address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address mkrWhale = makeAddr("mkrWhale"); - address sparkUser = makeAddr("sparkUser"); address randomUser = makeAddr("randomUser"); IAuthorityLike authority = IAuthorityLike(AUTHORITY); @@ -78,6 +77,8 @@ contract IntegrationTestsBase is Test { assertTrue(authority.hat() == spell); } + // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate + // isn't enabled on any reserve. function _checkUserActionsUnfrozen( address asset, uint256 supplyAmount, @@ -89,13 +90,20 @@ contract IntegrationTestsBase is Test { { assertEq(_isFrozen(asset), false); - deal(asset, sparkUser, supplyAmount); + // Make a new address for each asset to avoid side effects, eg. siloed borrowing + address sparkUser = makeAddr(string.concat(IERC20(asset).name(), " user")); - // NOTE: For all checks, not checking pool.swapBorrowRateMode() since stable rate - // isn't enabled on any reserve. + // If asset is not enabled as collateral, post enough WETH to ensure that the + // reserve asset can be borrowed. + // TODO: Remove LTV check once DAI spell goes through + if (!_usageAsCollateralEnabled(asset) || _ltv(asset) == 1) { + console.log("Collateral NOT enabled", IERC20(asset).name()); + _supplyWethCollateral(sparkUser, 1_000 ether); + } vm.startPrank(sparkUser); + deal(asset, sparkUser, supplyAmount); IERC20(asset).safeApprove(POOL, type(uint256).max); // User can supply and withdraw collateral always @@ -122,6 +130,9 @@ contract IntegrationTestsBase is Test { { assertEq(_isFrozen(asset), true); + // Use same address that borrowed when unfrozen to repay debt + address sparkUser = makeAddr(string.concat(IERC20(asset).name(), " user")); + vm.startPrank(sparkUser); // User can't supply @@ -143,6 +154,29 @@ contract IntegrationTestsBase is Test { vm.stopPrank(); } + function _supplyWethCollateral(address user, uint256 amount) internal { + bool frozenWeth = _isFrozen(WETH); + + // Unfreeze WETH market if necessary + if (frozenWeth) { + vm.prank(SPARK_PROXY); + poolConfig.setReserveFreeze(WETH, false); + } + + // Supply WETH + vm.startPrank(user); + deal(WETH, user, amount); + IERC20(WETH).safeApprove(POOL, type(uint256).max); + pool.supply(WETH, amount, user, 0); + vm.stopPrank(); + + // If the WETH market was originally frozen, return it back to frozen state + if (frozenWeth) { + vm.prank(SPARK_PROXY); + poolConfig.setReserveFreeze(WETH, false); + } + } + function _isFrozen(address asset) internal view returns (bool isFrozen) { ( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset); } @@ -151,6 +185,16 @@ contract IntegrationTestsBase is Test { ( ,,,,,, borrowingEnabled,,, ) = dataProvider.getReserveConfigurationData(asset); } + function _usageAsCollateralEnabled(address asset) + internal view returns (bool usageAsCollateralEnabled) + { + ( ,,,,, usageAsCollateralEnabled,,,, ) = dataProvider.getReserveConfigurationData(asset); + } + + function _ltv(address asset) internal view returns (uint256 ltv) { + ( , ltv,,,,,,,, ) = dataProvider.getReserveConfigurationData(asset); + } + } contract FreezeSingleAssetSpellFailures is IntegrationTestsBase { @@ -252,15 +296,6 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { assertEq(reserves.length, 9); - deal(WETH, sparkUser, 1_000 ether); - - // Since not all reserves are collateral, post enough WETH to ensure all reserves - // can be borrowed. - vm.startPrank(sparkUser); - IERC20(WETH).safeApprove(POOL, type(uint256).max); - pool.supply(WETH, 1_000 ether, sparkUser, 0); - vm.stopPrank(); - for (uint256 i = 0; i < reserves.length; i++) { address asset = reserves[i]; @@ -274,8 +309,8 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { uint256 supplyAmount = 1_000 * 10 ** decimals; uint256 withdrawAmount = 1 * 10 ** decimals; - uint256 borrowAmount = 1 * 10 ** decimals; - uint256 repayAmount = 1 * 10 ** decimals / 2; // 0.5 + uint256 borrowAmount = 2 * 10 ** decimals; + uint256 repayAmount = 1 * 10 ** decimals; uint256 snapshot = vm.snapshot(); @@ -328,3 +363,122 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { } } + +contract FreezeAllAssetsSpellTest is IntegrationTestsBase { + + using SafeERC20 for IERC20; + + address[] public untestedReserves; + + function setUp() public override { + super.setUp(); + vm.prank(SPARK_PROXY); + aclManager.addRiskAdmin(address(freezer)); + } + + function test_freezeAllAssetsSpell() external { + address[] memory reserves = pool.getReservesList(); + + assertEq(reserves.length, 9); + + uint256 supplyAmount = 1_000; + uint256 withdrawAmount = 1; + uint256 borrowAmount = 2; + uint256 repayAmount = 1; + + address freezeAllAssetsSpell = address(new FreezeAllAssetsSpell(address(freezer))); + + // Setup spell + _vote(freezeAllAssetsSpell); + + // Check that protocol is working as expected before spell for each asset + for (uint256 i = 0; i < reserves.length; i++) { + address asset = reserves[i]; + uint256 decimals = IERC20(asset).decimals(); + + // If the asset is frozen on mainnet, skip the test + if (_isFrozen(asset)) { + untestedReserves.push(asset); + continue; + } + + // Max out supply caps for this asset so that they aren't hit during test + vm.startPrank(SPARK_PROXY); + poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP + poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP + vm.stopPrank(); + + // uint256 snapshot = vm.snapshot(); + + console.log("i", i); + + _checkUserActionsUnfrozen( + asset, + supplyAmount * 10 ** decimals, + withdrawAmount * 10 ** decimals, + borrowAmount * 10 ** decimals, + repayAmount * 10 ** decimals + ); + + // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing + } + + + + assertEq(untestedReserves.length, 1); + assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC + + // Freeze all assets in the protocol + vm.prank(randomUser); // Demonstrate no ACL in spell + FreezeSingleAssetSpell(freezeAllAssetsSpell).freeze(); + + // Check that protocol is working as expected after the freeze spell for each asset + for (uint256 i = 0; i < reserves.length; i++) { + if (reserves[i] == untestedReserves[0]) { + continue; + } + + console.log("i", i); + + address asset = reserves[i]; + uint256 decimals = IERC20(asset).decimals(); + + // uint256 snapshot = vm.snapshot(); + + // Check all assets, including unfrozen ones from start + _checkUserActionsFrozen( + asset, + supplyAmount * 10 ** decimals, + withdrawAmount * 10 ** decimals, + borrowAmount * 10 ** decimals, + repayAmount * 10 ** decimals + ); + + // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing + } + + // Undo all freezes, including WBTC and make sure that protocol is back to working + // as expected + for (uint256 i = 0; i < reserves.length; i++) { + address asset = reserves[i]; + uint256 decimals = IERC20(asset).decimals(); + + vm.prank(SPARK_PROXY); + poolConfig.setReserveFreeze(asset, false); + + // uint256 snapshot = vm.snapshot(); + + _checkUserActionsUnfrozen( + asset, + supplyAmount * 10 ** decimals, + withdrawAmount * 10 ** decimals, + borrowAmount * 10 ** decimals, + repayAmount * 10 ** decimals + ); + + // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing + + } + } + +} From 4fc73af0ddb1e701a72568bce2ace909575086db Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 14 Nov 2023 16:22:26 -0500 Subject: [PATCH 39/42] fix: cleanup --- test/IntegrationTests.t.sol | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 4abbfc7..612ae6e 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -97,7 +97,6 @@ contract IntegrationTestsBase is Test { // reserve asset can be borrowed. // TODO: Remove LTV check once DAI spell goes through if (!_usageAsCollateralEnabled(asset) || _ltv(asset) == 1) { - console.log("Collateral NOT enabled", IERC20(asset).name()); _supplyWethCollateral(sparkUser, 1_000 ether); } @@ -396,7 +395,7 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { address asset = reserves[i]; uint256 decimals = IERC20(asset).decimals(); - // If the asset is frozen on mainnet, skip the test + // If the asset is already frozen on mainnet, skip the test if (_isFrozen(asset)) { untestedReserves.push(asset); continue; @@ -408,10 +407,6 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP vm.stopPrank(); - // uint256 snapshot = vm.snapshot(); - - console.log("i", i); - _checkUserActionsUnfrozen( asset, supplyAmount * 10 ** decimals, @@ -419,12 +414,8 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { borrowAmount * 10 ** decimals, repayAmount * 10 ** decimals ); - - // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing } - - assertEq(untestedReserves.length, 1); assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC @@ -432,19 +423,15 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { vm.prank(randomUser); // Demonstrate no ACL in spell FreezeSingleAssetSpell(freezeAllAssetsSpell).freeze(); - // Check that protocol is working as expected after the freeze spell for each asset + // Check that protocol is working as expected after the freeze spell for all assets for (uint256 i = 0; i < reserves.length; i++) { if (reserves[i] == untestedReserves[0]) { continue; } - console.log("i", i); - address asset = reserves[i]; uint256 decimals = IERC20(asset).decimals(); - // uint256 snapshot = vm.snapshot(); - // Check all assets, including unfrozen ones from start _checkUserActionsFrozen( asset, @@ -453,8 +440,6 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { borrowAmount * 10 ** decimals, repayAmount * 10 ** decimals ); - - // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing } // Undo all freezes, including WBTC and make sure that protocol is back to working @@ -466,8 +451,6 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { vm.prank(SPARK_PROXY); poolConfig.setReserveFreeze(asset, false); - // uint256 snapshot = vm.snapshot(); - _checkUserActionsUnfrozen( asset, supplyAmount * 10 ** decimals, @@ -475,9 +458,6 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { borrowAmount * 10 ** decimals, repayAmount * 10 ** decimals ); - - // vm.revertTo(snapshot); // Use snapshotting to avoid issues with siloed borrowing - } } From 74624652074bf9e8fe0d74308613d4896451f746 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 21 Nov 2023 11:50:53 -0500 Subject: [PATCH 40/42] feat: update to use zero supply caps and updated protocol state --- test/IntegrationTests.t.sol | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index 612ae6e..b95c9d8 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -47,7 +47,7 @@ contract IntegrationTestsBase is Test { SparkLendFreezerMom freezer; function setUp() public virtual { - vm.createSelectFork(getChain('mainnet').rpcUrl, 18_572_000); + vm.createSelectFork(getChain('mainnet').rpcUrl, 18_621_350); freezer = new SparkLendFreezerMom(POOL_CONFIG, POOL); @@ -95,8 +95,8 @@ contract IntegrationTestsBase is Test { // If asset is not enabled as collateral, post enough WETH to ensure that the // reserve asset can be borrowed. - // TODO: Remove LTV check once DAI spell goes through - if (!_usageAsCollateralEnabled(asset) || _ltv(asset) == 1) { + // NOTE: LTV check is necessary because LT of DAI is still 1. + if (!_usageAsCollateralEnabled(asset) || _ltv(asset) == 0) { _supplyWethCollateral(sparkUser, 1_000 ether); } @@ -304,6 +304,8 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { continue; } + assertEq(untestedReserves.length, 0); + uint256 decimals = IERC20(asset).decimals(); uint256 supplyAmount = 1_000 * 10 ** decimals; @@ -320,8 +322,8 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { _vote(freezeAssetSpell); vm.startPrank(SPARK_PROXY); - poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP - poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP + poolConfig.setSupplyCap(asset, 0); + poolConfig.setBorrowCap(asset, 0); vm.stopPrank(); _checkUserActionsUnfrozen( @@ -356,9 +358,6 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { vm.revertTo(snapshot); } - - assertEq(untestedReserves.length, 1); - assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC } } @@ -403,8 +402,8 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { // Max out supply caps for this asset so that they aren't hit during test vm.startPrank(SPARK_PROXY); - poolConfig.setSupplyCap(asset, 68_719_476_735); // MAX_SUPPLY_CAP - poolConfig.setBorrowCap(asset, 68_719_476_735); // MAX_BORROW_CAP + poolConfig.setSupplyCap(asset, 0); + poolConfig.setBorrowCap(asset, 0); vm.stopPrank(); _checkUserActionsUnfrozen( @@ -416,8 +415,7 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { ); } - assertEq(untestedReserves.length, 1); - assertEq(untestedReserves[0], 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // WBTC + assertEq(untestedReserves.length, 0); // Freeze all assets in the protocol vm.prank(randomUser); // Demonstrate no ACL in spell @@ -425,10 +423,6 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { // Check that protocol is working as expected after the freeze spell for all assets for (uint256 i = 0; i < reserves.length; i++) { - if (reserves[i] == untestedReserves[0]) { - continue; - } - address asset = reserves[i]; uint256 decimals = IERC20(asset).decimals(); From 856308c5727f46b4fe15bef1ccbfbbe8e2dfd45a Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Tue, 21 Nov 2023 11:58:29 -0500 Subject: [PATCH 41/42] feat: update to add executed boolean --- ...ergencySpell_SparkLend_FreezeAllAssets.sol | 4 ++ ...gencySpell_SparkLend_FreezeSingleAsset.sol | 4 ++ test/IntegrationTests.t.sol | 54 ++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol index 8c27490..c2a1ade 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol @@ -7,12 +7,16 @@ contract EmergencySpell_SparkLend_FreezeAllAssets { address public immutable sparkLendFreezerMom; + bool public executed; + constructor(address sparklendFreezerMom_) { sparkLendFreezerMom = sparklendFreezerMom_; } function freeze() external { + require(!executed, "FreezeAllAssetsSpell/already-executed"); ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets(); + executed = true; } } diff --git a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol index 59d2375..3615928 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol @@ -8,13 +8,17 @@ contract EmergencySpell_SparkLend_FreezeSingleAsset { address public immutable sparkLendFreezerMom; address public immutable reserve; + bool public executed; + constructor(address sparklendFreezerMom_, address reserve_) { sparkLendFreezerMom = sparklendFreezerMom_; reserve = reserve_; } function freeze() external { + require(!executed, "FreezeSingleAssetSpell/already-executed"); ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve); + executed = true; } } diff --git a/test/IntegrationTests.t.sol b/test/IntegrationTests.t.sol index b95c9d8..249aea8 100644 --- a/test/IntegrationTests.t.sol +++ b/test/IntegrationTests.t.sol @@ -235,6 +235,28 @@ contract FreezeSingleAssetSpellFailures is IntegrationTestsBase { freezeAssetSpell.freeze(); } + function test_cannotCallTwice() external { + vm.prank(SPARK_PROXY); + aclManager.addRiskAdmin(address(freezer)); + + _vote(address(freezeAssetSpell)); + + assertTrue(authority.hat() == address(freezeAssetSpell)); + assertTrue( + authority.canCall( + address(freezeAssetSpell), + address(freezer), + freezer.freezeMarket.selector + ) + ); + + vm.startPrank(randomUser); // Demonstrate no ACL in spell + freezeAssetSpell.freeze(); + + vm.expectRevert("FreezeSingleAssetSpell/already-executed"); + freezeAssetSpell.freeze(); + } + } contract FreezeAllAssetsSpellFailures is IntegrationTestsBase { @@ -276,6 +298,28 @@ contract FreezeAllAssetsSpellFailures is IntegrationTestsBase { freezeAllAssetsSpell.freeze(); } + function test_cannotCallTwice() external { + vm.prank(SPARK_PROXY); + aclManager.addRiskAdmin(address(freezer)); + + _vote(address(freezeAllAssetsSpell)); + + assertTrue(authority.hat() == address(freezeAllAssetsSpell)); + assertTrue( + authority.canCall( + address(freezeAllAssetsSpell), + address(freezer), + freezer.freezeMarket.selector + ) + ); + + vm.startPrank(randomUser); // Demonstrate no ACL in spell + freezeAllAssetsSpell.freeze(); + + vm.expectRevert("FreezeAllAssetsSpell/already-executed"); + freezeAllAssetsSpell.freeze(); + } + } contract FreezeSingleAssetSpellTest is IntegrationTestsBase { @@ -334,9 +378,13 @@ contract FreezeSingleAssetSpellTest is IntegrationTestsBase { repayAmount ); + assertEq(FreezeSingleAssetSpell(freezeAssetSpell).executed(), false); + vm.prank(randomUser); // Demonstrate no ACL in spell FreezeSingleAssetSpell(freezeAssetSpell).freeze(); + assertEq(FreezeSingleAssetSpell(freezeAssetSpell).executed(), true); + _checkUserActionsFrozen( asset, supplyAmount, @@ -417,9 +465,13 @@ contract FreezeAllAssetsSpellTest is IntegrationTestsBase { assertEq(untestedReserves.length, 0); + assertEq(FreezeAllAssetsSpell(freezeAllAssetsSpell).executed(), false); + // Freeze all assets in the protocol vm.prank(randomUser); // Demonstrate no ACL in spell - FreezeSingleAssetSpell(freezeAllAssetsSpell).freeze(); + FreezeAllAssetsSpell(freezeAllAssetsSpell).freeze(); + + assertEq(FreezeAllAssetsSpell(freezeAllAssetsSpell).executed(), true); // Check that protocol is working as expected after the freeze spell for all assets for (uint256 i = 0; i < reserves.length; i++) { From 7c1c1df421a26954777918f830fdbc394e9775c8 Mon Sep 17 00:00:00 2001 From: lucas-manuel Date: Thu, 23 Nov 2023 13:05:47 -0500 Subject: [PATCH 42/42] fix: update to use cei --- src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol | 2 +- src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol index c2a1ade..c75534d 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeAllAssets.sol @@ -15,8 +15,8 @@ contract EmergencySpell_SparkLend_FreezeAllAssets { function freeze() external { require(!executed, "FreezeAllAssetsSpell/already-executed"); - ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets(); executed = true; + ISparkLendFreezerMom(sparkLendFreezerMom).freezeAllMarkets(); } } diff --git a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol index 3615928..6381290 100644 --- a/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol +++ b/src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol @@ -17,8 +17,8 @@ contract EmergencySpell_SparkLend_FreezeSingleAsset { function freeze() external { require(!executed, "FreezeSingleAssetSpell/already-executed"); - ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve); executed = true; + ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve); } }