Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve test setup again again #469

Merged
merged 12 commits into from
Feb 8, 2023
10 changes: 2 additions & 8 deletions .github/actions/forge-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ runs:
with:
version: nightly

- name: Foundry fork cache
uses: actions/cache@v3
with:
path: ~/.foundry/cache
key: foundry-${{ hashFiles(format('config/{0}.json', inputs.network)) }} # where fork block numbers & RPC are stored
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved

- name: Foundry compilation cache
uses: actions/cache@v3
with:
Expand All @@ -49,7 +43,7 @@ runs:
shell: bash
env:
NETWORK: ${{ inputs.network }}
FOUNDRY_FUZZ_RUNS: 32768
FOUNDRY_FUZZ_RUNS: 16384

- name: Run internal tests
run: make test-internal
Expand All @@ -63,7 +57,7 @@ runs:
shell: bash
env:
NETWORK: ${{ inputs.network }}
FOUNDRY_FUZZ_RUNS: 128
FOUNDRY_FUZZ_RUNS: 64

- name: Generate lcov coverage report
if: ${{ inputs.codecovToken != '' }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ install:
forge install

contracts:
FOUNDRY_TEST=/dev/null forge build --via-ir --sizes --force
FOUNDRY_TEST=/dev/null forge build --via-ir --extra-output-files irOptimized --sizes --force


test:
Expand Down
6 changes: 0 additions & 6 deletions config/avalanche-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
"rpc": "https://rpc.ankr.com/avalanche",
"chainId": 43114,
"testBlock": 24800000,
"testMarkets": [
"DAI",
"USDC",
"WBTC",
"WETH"
],
"addressesProvider": "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
"DAI": "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70",
"FRAX": "0xD24C2Ad096400B6FBcd2ad8B24E7acBc21A1da64",
Expand Down
1 change: 1 addition & 0 deletions test/helpers/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
import {Types} from "src/libraries/Types.sol";
import {Events} from "src/libraries/Events.sol";
import {Errors} from "src/libraries/Errors.sol";
import {Constants} from "src/libraries/Constants.sol";
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";

import {Math} from "@morpho-utils/math/Math.sol";
Expand Down
79 changes: 48 additions & 31 deletions test/helpers/ForkTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {IPool, IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPool.sol"
import {IVariableDebtToken} from "@aave-v3-core/interfaces/IVariableDebtToken.sol";
import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol";

import {Types} from "src/libraries/Types.sol";
import {Events} from "src/libraries/Events.sol";
import {Errors} from "src/libraries/Errors.sol";
import {TestConfig, TestConfigLib} from "test/helpers/TestConfigLib.sol";
import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol";
import {Errors as AaveErrors} from "@aave-v3-core/protocol/libraries/helpers/Errors.sol";
import {ReserveConfiguration} from "@aave-v3-core/protocol/libraries/configuration/ReserveConfiguration.sol";

import {PriceOracleSentinelMock} from "test/mocks/PriceOracleSentinelMock.sol";
import {AaveOracleMock} from "test/mocks/AaveOracleMock.sol";
import {PoolAdminMock} from "test/mocks/PoolAdminMock.sol";
import "./BaseTest.sol";
Expand Down Expand Up @@ -53,15 +53,17 @@ contract ForkTest is BaseTest {
address internal aclAdmin;
AaveOracleMock internal oracle;
PoolAdminMock internal poolAdmin;
PriceOracleSentinelMock oracleSentinel;

uint256 snapshotId = type(uint256).max;

constructor() {
_initConfig();
_loadConfig();

_mockOracle();
_mockPoolAdmin();
_mockOracle();
_mockOracleSentinel();

_setBalances(address(this), type(uint256).max);
}
Expand All @@ -87,29 +89,40 @@ contract ForkTest is BaseTest {
function _loadConfig() internal {
forkId = config.createFork();

addressesProvider = IPoolAddressesProvider(config.getAddress("addressesProvider"));
addressesProvider = IPoolAddressesProvider(config.getAddressesProvider());
pool = IPool(addressesProvider.getPool());

aclAdmin = addressesProvider.getACLAdmin();
aclManager = IACLManager(addressesProvider.getACLManager());
poolConfigurator = IPoolConfigurator(addressesProvider.getPoolConfigurator());
poolDataProvider = IPoolDataProvider(addressesProvider.getPoolDataProvider());

dai = config.getAddress("DAI");
frax = config.getAddress("FRAX");
mai = config.getAddress("MAI");
usdc = config.getAddress("USDC");
usdt = config.getAddress("USDT");
aave = config.getAddress("AAVE");
btcb = config.getAddress("BTCb");
link = config.getAddress("LINK");
sAvax = config.getAddress("sAVAX");
wavax = config.getAddress("WAVAX");
wbtc = config.getAddress("WBTC");
weth = config.getAddress("WETH");
wNative = config.getAddress("wrappedNative");

allUnderlyings = config.getTestMarkets();
dai = config.getAddress("$.DAI");
frax = config.getAddress("$.FRAX");
mai = config.getAddress("$.MAI");
usdc = config.getAddress("$.USDC");
usdt = config.getAddress("$.USDT");
aave = config.getAddress("$.AAVE");
btcb = config.getAddress("$.BTCb");
link = config.getAddress("$.LINK");
sAvax = config.getAddress("$.sAVAX");
wavax = config.getAddress("$.WAVAX");
wbtc = config.getAddress("$.WBTC");
weth = config.getAddress("$.WETH");
wNative = config.getAddress("$.wrappedNative");

allUnderlyings.push(dai);
allUnderlyings.push(frax);
allUnderlyings.push(mai);
allUnderlyings.push(usdc);
allUnderlyings.push(usdt);
allUnderlyings.push(aave);
allUnderlyings.push(btcb);
allUnderlyings.push(link);
allUnderlyings.push(sAvax);
allUnderlyings.push(wavax);
allUnderlyings.push(wbtc);
allUnderlyings.push(weth);
}

function _label() internal virtual {
Expand Down Expand Up @@ -137,26 +150,30 @@ contract ForkTest is BaseTest {
vm.label(wNative, "wNative");
}

function _mockOracle() internal {
oracle = new AaveOracleMock(IAaveOracle(addressesProvider.getPriceOracle()), pool.getReservesList());

vm.store(
address(addressesProvider),
keccak256(abi.encode(bytes32("PRICE_ORACLE"), 2)),
bytes32(uint256(uint160(address(oracle))))
);
}

function _mockPoolAdmin() internal {
poolAdmin = new PoolAdminMock(poolConfigurator);

vm.startPrank(aclAdmin);
aclManager.addPoolAdmin(address(poolAdmin));
aclManager.addEmergencyAdmin(address(poolAdmin));
aclManager.addRiskAdmin(address(poolAdmin));
aclManager.addEmergencyAdmin(address(poolAdmin));
vm.stopPrank();
}

function _mockOracle() internal {
oracle = new AaveOracleMock(IAaveOracle(addressesProvider.getPriceOracle()), pool.getReservesList());

vm.prank(aclAdmin);
addressesProvider.setPriceOracle(address(oracle));
}

function _mockOracleSentinel() internal {
oracleSentinel = new PriceOracleSentinelMock(address(addressesProvider));

vm.prank(aclAdmin);
addressesProvider.setPriceOracleSentinel(address(oracleSentinel));
}

function _setBalances(address user, uint256 balance) internal {
deal(dai, user, balance);
deal(frax, user, balance);
Expand Down
57 changes: 41 additions & 16 deletions test/helpers/IntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ contract IntegrationTest is ForkTest {
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
using TestMarketLib for TestMarket;

uint8 internal constant E_MODE_CATEGORY_ID = 0;
uint256 internal constant INITIAL_BALANCE = 10_000_000_000 ether;
uint256 internal constant MIN_USD_AMOUNT = 0.0001e8; // AaveV3 base currency is USD, 8 decimals on all L2s.
uint256 internal constant MAX_USD_AMOUNT = 500_000_000e8; // AaveV3 base currency is USD, 8 decimals on all L2s.

// AaveV3 base currency is USD, 8 decimals on all L2s.
uint256 internal constant MIN_USD_AMOUNT = 0.01e8; // 0.01$
uint256 internal constant MAX_USD_AMOUNT = 500_000_000e8; // 500m$

IMorpho internal morpho;
IPositionsManager internal positionsManager;
Expand All @@ -42,6 +45,7 @@ contract IntegrationTest is ForkTest {
mapping(address => TestMarket) internal testMarkets;

address[] internal underlyings;
address[] internal collateralUnderlyings;
address[] internal borrowableUnderlyings;

function setUp() public virtual override {
Expand Down Expand Up @@ -80,8 +84,8 @@ contract IntegrationTest is ForkTest {
}

function _deploy() internal {
positionsManager = new PositionsManager(address(addressesProvider), 0);
morphoImpl = new Morpho(address(addressesProvider), 0);
positionsManager = new PositionsManager(address(addressesProvider), E_MODE_CATEGORY_ID);
morphoImpl = new Morpho(address(addressesProvider), E_MODE_CATEGORY_ID);

proxyAdmin = new ProxyAdmin();
morphoProxy = new TransparentUpgradeableProxy(payable(address(morphoImpl)), address(proxyAdmin), "");
Expand Down Expand Up @@ -134,19 +138,22 @@ contract IntegrationTest is ForkTest {
market.supplyCap = type(uint256).max;
market.borrowCap = type(uint256).max;

market.isBorrowable = reserve.configuration.getBorrowingEnabled() && !reserve.configuration.getSiloedBorrowing()
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
&& !reserve.configuration.getBorrowableInIsolation()
&& (E_MODE_CATEGORY_ID == 0 || E_MODE_CATEGORY_ID == reserve.configuration.getEModeCategory());

vm.label(reserve.aTokenAddress, string.concat("a", market.symbol));
vm.label(reserve.variableDebtTokenAddress, string.concat("d", market.symbol));
vm.label(reserve.variableDebtTokenAddress, string.concat("vd", market.symbol));
vm.label(reserve.stableDebtTokenAddress, string.concat("sd", market.symbol));
}

function _createMarket(address underlying, uint16 reserveFactor, uint16 p2pIndexCursor) internal {
(TestMarket storage market, DataTypes.ReserveData memory reserve) =
_initMarket(underlying, reserveFactor, p2pIndexCursor);

underlyings.push(underlying);
if (
market.ltv > 0 && reserve.configuration.getBorrowingEnabled() && !reserve.configuration.getSiloedBorrowing()
&& !reserve.configuration.getBorrowableInIsolation() && market.borrowGap() > 0
) borrowableUnderlyings.push(underlying);
if (market.ltv > 0) collateralUnderlyings.push(underlying);
if (market.isBorrowable) borrowableUnderlyings.push(underlying);

morpho.createMarket(market.underlying, market.reserveFactor, market.p2pIndexCursor);
}
Expand Down Expand Up @@ -179,7 +186,25 @@ contract IntegrationTest is ForkTest {
return bound(amount, market.minAmount, Math.min(market.maxAmount, market.supplyGap()));
}

/// @dev Bounds the input between 0 and the maximum borrowable quantity, without exceeding the market's liquidity nor its borrow cap.
/// @dev Bounds the input so that the amount returned can collateralize a debt between
/// the minimum & the maximum USD amount expected in tests, without exceeding the market's supply cap.
function _boundCollateral(TestMarket storage collateralMarket, uint256 amount, TestMarket storage borrowedMarket)
internal
view
returns (uint256)
{
return bound(
amount,
collateralMarket.minBorrowCollateral(borrowedMarket, borrowedMarket.minAmount),
Math.min(
collateralMarket.minBorrowCollateral(borrowedMarket, borrowedMarket.maxAmount),
collateralMarket.supplyGap()
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
)
);
}

/// @dev Bounds the input between the minimum USD amount expected in tests
/// and the maximum borrowable quantity, without exceeding the market's liquidity nor its borrow cap.
function _boundBorrow(TestMarket storage market, uint256 amount) internal view returns (uint256) {
return bound(
amount, market.minAmount, Math.min(market.maxAmount, Math.min(market.liquidity(), market.borrowGap()))
Expand All @@ -200,7 +225,7 @@ contract IntegrationTest is ForkTest {
vm.prank(borrower);
borrowed = morpho.borrow(market.underlying, amount, onBehalf, receiver, maxIterations);

_deposit(dai, testMarkets[dai].minCollateral(market, borrowed), address(morpho)); // Make Morpho solvent with default collateral market, having no supply cap.
_deposit(dai, testMarkets[dai].minBorrowCollateral(market, borrowed), address(morpho)); // Make Morpho able to borrow again with dai collateral.

oracle.setAssetPrice(market.underlying, market.price);
}
Expand All @@ -216,7 +241,7 @@ contract IntegrationTest is ForkTest {

try promoter.borrow(market.underlying, borrowed) {
market.resetPreviousIndex(address(morpho)); // Enable borrow/repay in same block.
_deposit(dai, testMarkets[dai].minCollateral(market, borrowed), address(morpho)); // Make Morpho solvent with default collateral market, having no supply cap.
_deposit(dai, testMarkets[dai].minBorrowCollateral(market, borrowed), address(morpho)); // Make Morpho able to borrow again with dai collateral.
} catch {
borrowed = 0;
}
Expand Down Expand Up @@ -250,8 +275,8 @@ contract IntegrationTest is ForkTest {
// Set the supply cap as exceeded.
_setSupplyCap(market, market.totalSupply());

user.approve(market.underlying, amount);
user.repay(market.underlying, amount, onBehalf);
hacker.approve(market.underlying, amount);
hacker.repay(market.underlying, amount, onBehalf);

return amount;
}
Expand All @@ -271,8 +296,8 @@ contract IntegrationTest is ForkTest {
// Set the max iterations to 0 upon repay to skip demotion and fallback to supply delta.
morpho.setDefaultMaxIterations(Types.MaxIterations({repay: 0, withdraw: 10}));

user.approve(market.underlying, amount);
user.repay(market.underlying, amount, onBehalf);
hacker.approve(market.underlying, amount);
hacker.repay(market.underlying, amount, onBehalf);

return amount;
}
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/InternalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import "./ForkTest.sol";
contract InternalTest is ForkTest, MorphoStorage {
using TestConfigLib for TestConfig;

constructor() MorphoStorage(_initConfig().getAddress("addressesProvider"), 0) {}
constructor() MorphoStorage(_initConfig().getAddressesProvider(), 0) {}
}
29 changes: 14 additions & 15 deletions test/helpers/TestConfigLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ library TestConfigLib {

Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

string public constant RPC_PATH = "$.rpc";
string public constant CHAIN_ID_PATH = "$.chainId";
string public constant TEST_BLOCK_PATH = "$.testBlock";
string public constant USES_RPC_PREFIX_PATH = "$.usesRpcPrefix";
string public constant ADDRESSES_PROVIDER_PATH = "$.addressesProvider";
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved

function load(TestConfig storage config, string memory network) internal returns (TestConfig storage) {
string memory root = vm.projectRoot();
string memory path = string(abi.encodePacked(root, "/config/", network, ".json"));
string memory path = string.concat(root, "/config/", network, ".json");

config.json = vm.readFile(path);

Expand All @@ -26,24 +32,17 @@ library TestConfigLib {
return config.json.readAddress(string(abi.encodePacked(key)));
}

function getTestMarkets(TestConfig storage config) internal view returns (address[] memory) {
string[] memory marketNames = config.json.readStringArray(string(abi.encodePacked("testMarkets")));
address[] memory markets = new address[](marketNames.length);

for (uint256 i; i < markets.length; i++) {
markets[i] = getAddress(config, marketNames[i]);
}

return markets;
function getAddressesProvider(TestConfig storage config) internal view returns (address) {
return getAddress(config, ADDRESSES_PROVIDER_PATH);
}

function createFork(TestConfig storage config) internal returns (uint256 forkId) {
bool rpcPrefixed = stdJson.readBool(config.json, string(abi.encodePacked("usesRpcPrefix")));
bool rpcPrefixed = stdJson.readBool(config.json, USES_RPC_PREFIX_PATH);
string memory endpoint = rpcPrefixed
? string(abi.encodePacked(config.json.readString(string(abi.encodePacked("rpc"))), vm.envString("ALCHEMY_KEY")))
: config.json.readString(string(abi.encodePacked("rpc")));
? string.concat(config.json.readString(RPC_PATH), vm.envString("ALCHEMY_KEY"))
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
: config.json.readString(RPC_PATH);

forkId = vm.createSelectFork(endpoint, config.json.readUint(string(abi.encodePacked("testBlock"))));
vm.chainId(config.json.readUint(string(abi.encodePacked("chainId"))));
forkId = vm.createSelectFork(endpoint, config.json.readUint(TEST_BLOCK_PATH));
vm.chainId(config.json.readUint(CHAIN_ID_PATH));
}
}
Loading