Skip to content

Commit

Permalink
feat: Add integration testing and freezeWETH spell (SC-239) (#2)
Browse files Browse the repository at this point in the history
* feat: add outline for sparklend pauser

* fix: rm certora files

* feat: add test coverage for wards ACL functions

* feat: add pool mock, use freeze instead of pause

* feat: add test coverage for freeze

* feat: get to 100% unit test coverage

* fix: rm counter.s.sol

* fix: add pragma and remove unused interface

* fix: add double underscore

* feat: add reserve freezes, update test structure

* feat: add freeze and freeze all coverage

* feat: set up test

* forge install: erc20-helpers

* feat: get initial freeze tests working

* forge install: aave-v3-core

* feat: all tests passing

* chore: improve test comments

* fix: update README

* fix: update to standard structure

* feat: get to full natspec

* feat: add natspec and overrides

* feat: get to full coverage

* fix: rm authority from constructor

* fix: update comments

* fix: update to use specified caller, uniform tests

* feat: update to add freeze market events, events testing

* fix: alignment

* fix: update as per review

* fix: update to use constuctor param, update tests

* fix: update comments

* fix: use immutable

* fix: update natspec

* fix: update naming
  • Loading branch information
lucas-manuel authored Nov 17, 2023
1 parent 226ca07 commit 6544a9a
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[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
[submodule "lib/aave-v3-core"]
path = lib/aave-v3-core
url = https://github.com/marsfoundation/aave-v3-core
1 change: 1 addition & 0 deletions lib/aave-v3-core
Submodule aave-v3-core added at 6070e8
1 change: 1 addition & 0 deletions lib/erc20-helpers
Submodule erc20-helpers added at 9cf80a
2 changes: 1 addition & 1 deletion lib/forge-std
8 changes: 4 additions & 4 deletions src/SparkLendFreezerMom.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ contract SparkLendFreezerMom is ISparkLendFreezerMom {
/*** Declarations and Constructor ***/
/**********************************************************************************************/

address public immutable poolConfigurator;
address public immutable pool;
address public immutable override poolConfigurator;
address public immutable override pool;

address public authority;
address public owner;
address public override authority;
address public override owner;

constructor(address poolConfigurator_, address pool_) {
poolConfigurator = poolConfigurator_;
Expand Down
20 changes: 20 additions & 0 deletions src/spells/EmergencySpell_SparkLend_FreezeSingleAsset.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

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

contract EmergencySpell_SparkLend_FreezeSingleAsset {

address public immutable sparkLendFreezerMom;
address public immutable reserve;

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

function freeze() external {
ISparkLendFreezerMom(sparkLendFreezerMom).freezeMarket(reserve);
}

}
181 changes: 181 additions & 0 deletions test/IntegrationTests.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
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 { SparkLendFreezerMom } from "src/SparkLendFreezerMom.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";
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 {

using SafeERC20 for IERC20;

address constant ACL_MANAGER = 0xdA135Cd78A086025BcdC87B038a1C462032b510C;
address constant AUTHORITY = 0x0a3f6849f78076aefaDf113F5BED87720274dDC0;
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 sparkUser = makeAddr("sparkUser");
address randomUser = makeAddr("randomUser");

IAuthorityLike authority = IAuthorityLike(AUTHORITY);
IACLManager aclManager = IACLManager(ACL_MANAGER);
IPool pool = IPool(POOL);
IPoolConfigurator poolConfig = IPoolConfigurator(POOL_CONFIG);
IPoolDataProvider dataProvider = IPoolDataProvider(DATA_PROVIDER);

SparkLendFreezerMom freezer;
FreezeSingleAssetSpell freezeWethSpell;

function setUp() public {
vm.createSelectFork(getChain('mainnet').rpcUrl);

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(freezeWethSpell));
assertTrue(
!authority.canCall(
address(freezeWethSpell),
address(freezer),
freezer.freezeMarket.selector
)
);

vm.expectRevert("SparkLendFreezerMom/not-authorized");
freezeWethSpell.freeze();
}

function test_cannotCallWithoutRoleSetup() external {
_vote(address(freezeWethSpell));

assertTrue(authority.hat() == address(freezeWethSpell));
assertTrue(
authority.canCall(
address(freezeWethSpell),
address(freezer),
freezer.freezeMarket.selector
)
);

vm.expectRevert(bytes("4")); // CALLER_NOT_RISK_OR_POOL_ADMIN
freezeWethSpell.freeze();
}

function test_freezeWethSpell() external {
_vote(address(freezeWethSpell));

vm.prank(SPARK_PROXY);
aclManager.addRiskAdmin(address(freezer));

assertEq(_isFrozen(WETH), false);

deal(WETH, sparkUser, 20 ether); // Deal enough for 2 supplies

// 1. 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);

// 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
freezeWethSpell.freeze();

assertEq(_isFrozen(WETH), true);

// 3. 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);

// User can't borrow
vm.expectRevert(bytes("28")); // RESERVE_FROZEN
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();

// 4. Simulate spell after freeze, unfreezing market
vm.prank(SPARK_PROXY);
poolConfig.setReserveFreeze(WETH, false);

assertEq(_isFrozen(WETH), false);

// 5. 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;

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);
}

function _isFrozen(address asset) internal view returns (bool isFrozen) {
( ,,,,,,,,, isFrozen ) = dataProvider.getReserveConfigurationData(asset);
}

}
14 changes: 14 additions & 0 deletions test/Interfaces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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);

function lock(uint256 amount) external;

function vote(address[] calldata slate) external;

function lift(address target) external;
}
59 changes: 57 additions & 2 deletions test/SparkLendFreezerMom.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -144,6 +146,60 @@ 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);

freezer.setAuthority(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);
}

}

contract EventTests is SparkLendFreezerMomUnitTestBase {

event FreezeMarket(address indexed reserve);
Expand Down Expand Up @@ -207,6 +263,5 @@ contract EventTests is SparkLendFreezerMomUnitTestBase {
emit FreezeMarket(asset2);
freezer.freezeAllMarkets();
}
}


}
15 changes: 15 additions & 0 deletions test/harnesses/SparkLendFreezerMomHarness.sol
Original file line number Diff line number Diff line change
@@ -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_)
SparkLendFreezerMom(poolConfigurator_, pool_) {}

function isAuthorizedExternal(address src, bytes4 sig) public view returns (bool) {
return super.isAuthorized(src, sig);
}

}

0 comments on commit 6544a9a

Please sign in to comment.