From 7bead943bb4cc9d0c05edee80deb288d052d9f92 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Sun, 1 Sep 2024 00:18:07 +0100 Subject: [PATCH] feat: add foundry test for bitmaps --- .gitignore | 6 +- .gitmodules | 3 + foundry.toml | 10 + lib/forge-std | 1 + package.json | 5 +- src/test/CalldataLogic.t.sol | 293 +++++++++++++ src/test/PercentageMath.t.sol | 39 ++ src/test/ReserveConfiguration.t.sol | 618 ++++++++++++++++++++++++++++ src/test/UserConfiguration.t.sol | 264 ++++++++++++ src/test/WadRayMul.t.sol | 80 ++++ 10 files changed, 1317 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 foundry.toml create mode 160000 lib/forge-std create mode 100644 src/test/CalldataLogic.t.sol create mode 100644 src/test/PercentageMath.t.sol create mode 100644 src/test/ReserveConfiguration.t.sol create mode 100644 src/test/UserConfiguration.t.sol create mode 100644 src/test/WadRayMul.t.sol diff --git a/.gitignore b/.gitignore index 1560df062..82f860591 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,8 @@ deployments/ **.last_conf* certora-logs certora_debug_log.txt -resource_errors.json \ No newline at end of file +resource_errors.json + +out +lcov.info +.foundry \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..4c1b977a3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 000000000..8189e66ff --- /dev/null +++ b/foundry.toml @@ -0,0 +1,10 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] +fuzz_runs = 10000 +optimizer = true + +solc = "0.8.10" + +# See more config options https://github.com/gakonst/foundry/tree/master/config diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 000000000..1714bee72 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d diff --git a/package.json b/package.json index 913e971f6..a6e9ac2de 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "node": ">=16.0.0" }, "scripts": { + "setup:foundry": "foundryup && forge install", + "install:foundry": "curl -L https://foundry.paradigm.xyz | bash", "size": "npm run compile && npm run hardhat size-contracts", "run-env": "npm i && tail -f /dev/null", "hardhat": "hardhat", @@ -23,6 +25,7 @@ "prettier:write": "prettier -w .", "coverage": ". ./setup-test-env.sh && COVERAGE=true npx hardhat coverage --temp temp-artifacts --testfiles test-suites/emptyrun.coverage.ts && rm -rf coverage.json coverage/ && COVERAGE=true npx hardhat coverage --temp temp-artifacts --testfiles 'test-suites/*.spec.ts'", "test": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/*.spec.ts", + "test-fuzz": "forge test", "test-scenarios": ". ./setup-test-env.sh && npx hardhat test test-suites/__setup.spec.ts test-suites/scenario.spec.ts", "test-l2pool": ". ./setup-test-env.sh && npx hardhat test test-suites/__setup.spec.ts test-suites/pool-l2.spec.ts", "test-subgraph:scenarios": ". ./setup-test-env.sh && hardhat --network hardhatevm_docker test test-suites/__setup.spec.ts test-suites/subgraph-scenarios.spec.ts", @@ -88,4 +91,4 @@ "lint-staged": { "*.{ts,js,md,sol}": "prettier --write" } -} +} \ No newline at end of file diff --git a/src/test/CalldataLogic.t.sol b/src/test/CalldataLogic.t.sol new file mode 100644 index 000000000..b430a896c --- /dev/null +++ b/src/test/CalldataLogic.t.sol @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.10; + +import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.sol'; +import {ReserveConfiguration} from './../../contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; + +import {CalldataLogic} from './../../contracts/protocol/libraries/logic/CalldataLogic.sol'; +import {L2Encoder} from './../../contracts/misc/L2Encoder.sol'; + +import {Test} from 'forge-std/Test.sol'; + +contract CalldataLogicTest is Test { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + mapping(uint256 => address) reservesList; + + uint256 constant RESERVE_COUNT = 128; + + function setUp() public { + for (uint256 i = 0; i < RESERVE_COUNT; i++) { + reservesList[i] = address(uint160(400 + i)); + } + } + + function testDecodeSupplyParams(uint8 assetId, uint128 amount, uint16 referralCode) public view { + vm.assume(assetId < RESERVE_COUNT); + + bytes32 args; + assembly { + args := add(assetId, add(shl(16, amount), shl(144, referralCode))) + } + + (address _asset, uint256 _amount, uint16 _referralCode) = CalldataLogic.decodeSupplyParams( + reservesList, + args + ); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_amount, amount); + assertEq(_referralCode, referralCode); + } + + function testDecodeSupplyWithPermitParams( + uint8 assetId, + uint128 amount, + uint16 referralCode, + uint32 deadLine, + uint8 permitV + ) public view { + vm.assume(assetId < RESERVE_COUNT); + + bytes32 args; + assembly { + args := add( + assetId, + add( + shl(16, amount), + add(shl(144, referralCode), add(shl(160, deadLine), shl(192, permitV))) + ) + ) + } + + ( + address _asset, + uint256 _amount, + uint16 _referralCode, + uint256 _deadLine, + uint8 _permitV + ) = CalldataLogic.decodeSupplyWithPermitParams(reservesList, args); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_amount, amount); + assertEq(_referralCode, referralCode); + assertEq(_deadLine, deadLine); + assertEq(_permitV, permitV); + } + + function testDecodeWithdrawParams(uint8 assetId, uint128 amount) public view { + vm.assume(assetId < RESERVE_COUNT); + bytes32 args; + assembly { + args := add(assetId, shl(16, amount)) + } + + (address _asset, uint256 _amount) = CalldataLogic.decodeWithdrawParams(reservesList, args); + + uint256 expectedAmount = amount == type(uint128).max ? type(uint256).max : uint256(amount); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_amount, expectedAmount); + } + + function testDecodeBorrowParams( + uint8 assetId, + uint128 amount, + bool stableRateMode, + uint16 referralCode + ) public view { + vm.assume(assetId < RESERVE_COUNT); + uint256 interestRateMode = stableRateMode ? 1 : 2; + + bytes32 args; + assembly { + args := add( + assetId, + add(shl(16, amount), add(shl(144, interestRateMode), shl(152, referralCode))) + ) + } + + ( + address _asset, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode + ) = CalldataLogic.decodeBorrowParams(reservesList, args); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_amount, amount); + assertEq(_interestRateMode, interestRateMode); + assertEq(_referralCode, referralCode); + } + + function testDecodeRepayParams(uint8 assetId, uint128 amount, bool stableRateMode) public view { + vm.assume(assetId < RESERVE_COUNT); + uint256 interestRateMode = stableRateMode ? 1 : 2; + uint256 expectedAmount = amount == type(uint128).max ? type(uint256).max : amount; + + bytes32 args; + assembly { + args := add(assetId, add(shl(16, amount), shl(144, interestRateMode))) + } + + (address _asset, uint256 _amount, uint256 _interestRateMode) = CalldataLogic.decodeRepayParams( + reservesList, + args + ); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_amount, expectedAmount); + assertEq(_interestRateMode, interestRateMode); + } + + struct DecodeRepayWithPermitHelper { + uint256 expectedAmount; + address _asset; + uint256 _amount; + uint256 _interestRateMode; + uint256 _deadline; + uint8 _permitV; + } + + function testDecodeRepayWithPermitParams( + uint8 assetId, + uint128 amount, + bool stableRateMode, + uint32 deadline, + uint8 permitV + ) public view { + vm.assume(assetId < RESERVE_COUNT); + DecodeRepayWithPermitHelper memory vars; + + vars.expectedAmount = amount == type(uint128).max ? type(uint256).max : amount; + + bytes32 args; + assembly { + args := add( + assetId, + add( + shl(16, amount), + add(shl(144, sub(2, stableRateMode)), add(shl(152, deadline), shl(184, permitV))) + ) + ) + } + + ( + vars._asset, + vars._amount, + vars._interestRateMode, + vars._deadline, + vars._permitV + ) = CalldataLogic.decodeRepayWithPermitParams(reservesList, args); + + assertEq(vars._asset, reservesList[assetId]); + assertNotEq(vars._asset, address(0)); + assertEq(vars._amount, vars.expectedAmount); + assertEq(vars._interestRateMode, stableRateMode ? 1 : 2); + assertEq(vars._deadline, uint256(deadline)); + assertEq(vars._permitV, permitV); + } + + function testDecodeSwapBorrowRateModeParams(uint8 assetId, bool stableRateMode) public view { + vm.assume(assetId < RESERVE_COUNT); + uint256 interestRateMode = stableRateMode ? 1 : 2; + bytes32 args; + assembly { + args := add(assetId, shl(16, interestRateMode)) + } + + (address _asset, uint256 _interestRateMode) = CalldataLogic.decodeSwapBorrowRateModeParams( + reservesList, + args + ); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_interestRateMode, interestRateMode); + } + + function testDecodeRebalanceStableBorrowRateParams(uint8 assetId, address user) public view { + vm.assume(assetId < RESERVE_COUNT); + bytes32 args; + assembly { + args := add(assetId, shl(16, user)) + } + + (address _asset, address _user) = CalldataLogic.decodeRebalanceStableBorrowRateParams( + reservesList, + args + ); + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_user, user); + } + + function testDecodeSetUserUseReserveAsCollateralParams( + uint8 assetId, + bool useAsCollateral + ) public view { + vm.assume(assetId < RESERVE_COUNT); + bytes32 args; + assembly { + args := add(assetId, shl(16, useAsCollateral)) + } + + (address _asset, bool _useAsCollateral) = CalldataLogic + .decodeSetUserUseReserveAsCollateralParams(reservesList, args); + + assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); + assertEq(_useAsCollateral, useAsCollateral); + } + + struct DecodeLiquidationCallParamsHelper { + uint256 expectedDebtToCover; + address collateralAsset; + address debtAsset; + address user; + uint256 debtToCover; + bool receiveAToken; + } + + function testDecodeLiquidationCallParams( + uint8 collateralAssetId, + uint8 debtAssetId, + address user, + uint128 debtToCover, + bool receiveAToken + ) public view { + vm.assume(collateralAssetId < RESERVE_COUNT && debtAssetId < RESERVE_COUNT); + DecodeLiquidationCallParamsHelper memory vars; + + bytes32 args1; + bytes32 args2; + + vars.expectedDebtToCover = debtToCover == type(uint128).max ? type(uint256).max : debtToCover; + + assembly { + args1 := add(add(collateralAssetId, shl(16, debtAssetId)), shl(32, user)) + args2 := add(debtToCover, shl(128, receiveAToken)) + } + + ( + vars.collateralAsset, + vars.debtAsset, + vars.user, + vars.debtToCover, + vars.receiveAToken + ) = CalldataLogic.decodeLiquidationCallParams(reservesList, args1, args2); + + assertEq(vars.collateralAsset, reservesList[collateralAssetId]); + assertNotEq(vars.collateralAsset, address(0)); + assertEq(vars.debtAsset, reservesList[debtAssetId]); + assertNotEq(vars.debtAsset, address(0)); + assertEq(vars.user, user); + assertEq(vars.debtToCover, vars.expectedDebtToCover); + assertEq(vars.receiveAToken, receiveAToken); + } +} diff --git a/src/test/PercentageMath.t.sol b/src/test/PercentageMath.t.sol new file mode 100644 index 000000000..942e08492 --- /dev/null +++ b/src/test/PercentageMath.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.10; + +import {PercentageMath} from './../../contracts/protocol/libraries/math/PercentageMath.sol'; + +import {Test} from 'forge-std/Test.sol'; + +contract PercentageMathTest is Test { + using PercentageMath for uint256; + + function testPercentMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - PercentageMath.HALF_PERCENTAGE_FACTOR) / b; + vm.assume(safe); + assertEq( + a.percentMul(b), + (a * b + PercentageMath.HALF_PERCENTAGE_FACTOR) / PercentageMath.PERCENTAGE_FACTOR + ); + } + + function testPercentDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / PercentageMath.PERCENTAGE_FACTOR; + vm.assume(safe); + assertEq(a.percentDiv(b), (a * PercentageMath.PERCENTAGE_FACTOR + b / 2) / b); + } + + /// NEGATIVES /// + + function testFailPercentMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - PercentageMath.HALF_PERCENTAGE_FACTOR) / b; + vm.assume(!safe); + a.percentMul(b); + } + + function testFailPercentDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / PercentageMath.PERCENTAGE_FACTOR; + vm.assume(!safe); + a.percentDiv(b); + } +} diff --git a/src/test/ReserveConfiguration.t.sol b/src/test/ReserveConfiguration.t.sol new file mode 100644 index 000000000..537926e1e --- /dev/null +++ b/src/test/ReserveConfiguration.t.sol @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.10; + +import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.sol'; +import {ReserveConfiguration} from './../../contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; +import {Errors} from './../../contracts/protocol/libraries/helpers/Errors.sol'; + +import {Test} from 'forge-std/Test.sol'; + +contract ReserveConfigurationTest is Test { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + function testSetLtv(uint256 data, uint256 ltv) public pure { + ltv = bound(ltv, 0, type(uint16).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data & ReserveConfiguration.LTV_MASK + }); + config.setLtv(ltv); + assertEq(config.data & ~ReserveConfiguration.LTV_MASK, ltv); + } + + function testRevertSetLtv(uint256 data, uint256 ltv) public { + ltv = bound(ltv, ReserveConfiguration.MAX_VALID_LTV + 1, type(uint256).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data & ReserveConfiguration.LTV_MASK + }); + vm.expectRevert(bytes(Errors.INVALID_LTV)); + config.setLtv(ltv); + } + + function testGetLtv(uint256 data, uint256 ltv) public pure { + ltv = bound(ltv, 0, type(uint16).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LTV_MASK) | ltv + }); + assertEq(config.getLtv(), ltv); + } + + function testSetLiquidationThreshold(uint256 data, uint256 threshold) public pure { + threshold = bound(threshold, 0, type(uint16).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) + }); + + config.setLiquidationThreshold(threshold); + + assertEq( + (config.data & ~ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) >> + ReserveConfiguration.LIQUIDATION_THRESHOLD_START_BIT_POSITION, + threshold + ); + } + + function testRevertSetLiquidationThreshold(uint256 data, uint256 threshold) public { + threshold = bound( + threshold, + ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD + 1, + type(uint256).max + ); + + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) + }); + + vm.expectRevert(bytes(Errors.INVALID_LIQ_THRESHOLD)); + config.setLiquidationThreshold(threshold); + } + + function testGetLiquidationThreshold(uint256 data, uint256 threshold) public pure { + threshold = bound(threshold, 0, ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) | + (threshold << ReserveConfiguration.LIQUIDATION_THRESHOLD_START_BIT_POSITION) + }); + assertEq(config.getLiquidationThreshold(), threshold); + } + + function testSetLiquidationBonus(uint256 data, uint256 bonus) public pure { + bonus = bound(bonus, 0, ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) + }); + + config.setLiquidationBonus(bonus); + + assertEq( + (config.data & ~ReserveConfiguration.LIQUIDATION_BONUS_MASK) >> + ReserveConfiguration.LIQUIDATION_BONUS_START_BIT_POSITION, + bonus + ); + } + + function testRevertSetLiquidationBonus(uint256 data, uint256 bonus) public { + bonus = bound(bonus, ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS + 1, type(uint256).max); + + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) + }); + + vm.expectRevert(bytes(Errors.INVALID_LIQ_BONUS)); + config.setLiquidationBonus(bonus); + } + + function testGetLiquidationBonus(uint256 data, uint256 bonus) public pure { + bonus = bound(bonus, 0, ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) | + (bonus << ReserveConfiguration.LIQUIDATION_BONUS_START_BIT_POSITION) + }); + assertEq(config.getLiquidationBonus(), bonus); + } + + function testSetDecimals(uint256 data, uint256 decimals) public pure { + decimals = bound(decimals, 0, ReserveConfiguration.MAX_VALID_DECIMALS); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DECIMALS_MASK) + }); + + config.setDecimals(decimals); + + assertEq( + (config.data & ~ReserveConfiguration.DECIMALS_MASK) >> + ReserveConfiguration.RESERVE_DECIMALS_START_BIT_POSITION, + decimals + ); + } + + function testRevertSetDecimals(uint256 data, uint256 decimals) public { + decimals = bound(decimals, ReserveConfiguration.MAX_VALID_DECIMALS + 1, type(uint256).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DECIMALS_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_DECIMALS)); + config.setDecimals(decimals); + } + + function testGetDecimals(uint256 data, uint256 decimals) public pure { + decimals = bound(decimals, 0, ReserveConfiguration.MAX_VALID_DECIMALS); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DECIMALS_MASK) | + (decimals << ReserveConfiguration.RESERVE_DECIMALS_START_BIT_POSITION) + }); + assertEq(config.getDecimals(), decimals); + } + + function testSetActive(uint256 data, bool active) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.ACTIVE_MASK) + }); + config.setActive(active); + uint256 activeBit = active ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.ACTIVE_MASK) >> + ReserveConfiguration.IS_ACTIVE_START_BIT_POSITION, + activeBit + ); + } + + function testGetActive(uint256 data, bool active) public pure { + uint256 activeBit = active ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.ACTIVE_MASK) | + (activeBit << ReserveConfiguration.IS_ACTIVE_START_BIT_POSITION) + }); + assertEq(config.getActive(), active); + } + + function testSetFrozen(uint256 data, bool frozen) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.FROZEN_MASK) + }); + config.setFrozen(frozen); + uint256 frozenBit = frozen ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.FROZEN_MASK) >> + ReserveConfiguration.IS_FROZEN_START_BIT_POSITION, + frozenBit + ); + } + + function testGetFrozen(uint256 data, bool frozen) public pure { + uint256 frozenBit = frozen ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.FROZEN_MASK) | + (frozenBit << ReserveConfiguration.IS_FROZEN_START_BIT_POSITION) + }); + assertEq(config.getFrozen(), frozen); + } + + function testSetPaused(uint256 data, bool paused) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.PAUSED_MASK) + }); + config.setPaused(paused); + uint256 pausedBit = paused ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.PAUSED_MASK) >> + ReserveConfiguration.IS_PAUSED_START_BIT_POSITION, + pausedBit + ); + } + + function testGetPaused(uint256 data, bool paused) public pure { + uint256 pausedBit = paused ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.PAUSED_MASK) | + (pausedBit << ReserveConfiguration.IS_PAUSED_START_BIT_POSITION) + }); + assertEq(config.getPaused(), paused); + } + + function testSetBorrowableInIsolation(uint256 data, bool enabled) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) + }); + config.setBorrowableInIsolation(enabled); + uint256 isolationBorrowAbleBit = enabled ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) >> + ReserveConfiguration.BORROWABLE_IN_ISOLATION_START_BIT_POSITION, + isolationBorrowAbleBit + ); + } + + function testGetBorrowableInIsolation(uint256 data, bool enabled) public pure { + uint256 isolationBorrowAble = enabled ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) | + (isolationBorrowAble << ReserveConfiguration.BORROWABLE_IN_ISOLATION_START_BIT_POSITION) + }); + assertEq(config.getBorrowableInIsolation(), enabled); + } + + function testSetSiloedBorrowing(uint256 data, bool enabled) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SILOED_BORROWING_MASK) + }); + config.setSiloedBorrowing(enabled); + uint256 siloedBorrowingBit = enabled ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.SILOED_BORROWING_MASK) >> + ReserveConfiguration.SILOED_BORROWING_START_BIT_POSITION, + siloedBorrowingBit + ); + } + + function testGetSiloedBorrowing(uint256 data, bool enabled) public pure { + uint256 siloedBorrowingBit = enabled ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SILOED_BORROWING_MASK) | + (siloedBorrowingBit << ReserveConfiguration.SILOED_BORROWING_START_BIT_POSITION) + }); + assertEq(config.getSiloedBorrowing(), enabled); + } + + function testSetBorrowingEnabled(uint256 data, bool enabled) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWING_MASK) + }); + config.setBorrowingEnabled(enabled); + uint256 borrowingBit = enabled ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.BORROWING_MASK) >> + ReserveConfiguration.BORROWING_ENABLED_START_BIT_POSITION, + borrowingBit + ); + } + + function testGetBorrowingEnabled(uint256 data, bool enabled) public pure { + uint256 borrowingBit = enabled ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWING_MASK) | + (borrowingBit << ReserveConfiguration.BORROWING_ENABLED_START_BIT_POSITION) + }); + assertEq(config.getBorrowingEnabled(), enabled); + } + + function testSetStableBorrowingEnabled(uint256 data, bool enabled) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.STABLE_BORROWING_MASK) + }); + config.setStableRateBorrowingEnabled(enabled); + uint256 borrowingBit = enabled ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.STABLE_BORROWING_MASK) >> + ReserveConfiguration.STABLE_BORROWING_ENABLED_START_BIT_POSITION, + borrowingBit + ); + } + + function testGetStableBorrowingEnabled(uint256 data, bool enabled) public pure { + uint256 borrowingBit = enabled ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.STABLE_BORROWING_MASK) | + (borrowingBit << ReserveConfiguration.STABLE_BORROWING_ENABLED_START_BIT_POSITION) + }); + assertEq(config.getStableRateBorrowingEnabled(), enabled); + } + + function testSetReserveFactor(uint256 data, uint256 reserveFactor) public pure { + reserveFactor = bound(reserveFactor, 0, ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) + }); + + config.setReserveFactor(reserveFactor); + + assertEq( + (config.data & ~ReserveConfiguration.RESERVE_FACTOR_MASK) >> + ReserveConfiguration.RESERVE_FACTOR_START_BIT_POSITION, + reserveFactor + ); + } + + function testRevertSetReserveFactor(uint256 data, uint256 reserveFactor) public { + reserveFactor = bound( + reserveFactor, + ReserveConfiguration.MAX_VALID_RESERVE_FACTOR + 1, + type(uint256).max + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_RESERVE_FACTOR)); + config.setReserveFactor(reserveFactor); + } + + function testGetReserveFactor(uint256 data, uint256 reserveFactor) public pure { + reserveFactor = bound(reserveFactor, 0, ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) | + (reserveFactor << ReserveConfiguration.RESERVE_FACTOR_START_BIT_POSITION) + }); + assertEq(config.getReserveFactor(), reserveFactor); + } + + function testSetBorrowCap(uint256 data, uint256 borrowCap) public pure { + borrowCap = bound(borrowCap, 0, ReserveConfiguration.MAX_VALID_BORROW_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROW_CAP_MASK) + }); + + config.setBorrowCap(borrowCap); + + assertEq( + (config.data & ~ReserveConfiguration.BORROW_CAP_MASK) >> + ReserveConfiguration.BORROW_CAP_START_BIT_POSITION, + borrowCap + ); + } + + function testRevertSetBorrowCap(uint256 data, uint256 borrowCap) public { + borrowCap = bound(borrowCap, ReserveConfiguration.MAX_VALID_BORROW_CAP + 1, type(uint256).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROW_CAP_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_BORROW_CAP)); + config.setBorrowCap(borrowCap); + } + + function testGetBorrowCap(uint256 data, uint256 borrowCap) public pure { + borrowCap = bound(borrowCap, 0, ReserveConfiguration.MAX_VALID_BORROW_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROW_CAP_MASK) | + (borrowCap << ReserveConfiguration.BORROW_CAP_START_BIT_POSITION) + }); + assertEq(config.getBorrowCap(), borrowCap); + } + + function testSetSupplyCap(uint256 data, uint256 supplyCap) public pure { + supplyCap = bound(supplyCap, 0, ReserveConfiguration.MAX_VALID_SUPPLY_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) + }); + + config.setSupplyCap(supplyCap); + + assertEq( + (config.data & ~ReserveConfiguration.SUPPLY_CAP_MASK) >> + ReserveConfiguration.SUPPLY_CAP_START_BIT_POSITION, + supplyCap + ); + } + + function testRevertSetSupplyCap(uint256 data, uint256 supplyCap) public { + supplyCap = bound(supplyCap, ReserveConfiguration.MAX_VALID_SUPPLY_CAP + 1, type(uint256).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_SUPPLY_CAP)); + config.setSupplyCap(supplyCap); + } + + function testGetSupplyCap(uint256 data, uint256 supplyCap) public pure { + supplyCap = bound(supplyCap, 0, ReserveConfiguration.MAX_VALID_SUPPLY_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) | + (supplyCap << ReserveConfiguration.SUPPLY_CAP_START_BIT_POSITION) + }); + assertEq(config.getSupplyCap(), supplyCap); + } + + function testSetDebtCeiling(uint256 data, uint256 debtCeiling) public pure { + debtCeiling = bound(debtCeiling, 0, ReserveConfiguration.MAX_VALID_DEBT_CEILING); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DEBT_CEILING_MASK) + }); + + config.setDebtCeiling(debtCeiling); + + assertEq( + (config.data & ~ReserveConfiguration.DEBT_CEILING_MASK) >> + ReserveConfiguration.DEBT_CEILING_START_BIT_POSITION, + debtCeiling + ); + } + + function testRevertSetDebtCeiling(uint256 data, uint256 debtCeiling) public { + debtCeiling = bound( + debtCeiling, + ReserveConfiguration.MAX_VALID_DEBT_CEILING + 1, + type(uint256).max + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DEBT_CEILING_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_DEBT_CEILING)); + config.setDebtCeiling(debtCeiling); + } + + function testGetDebtCeiling(uint256 data, uint256 debtCeiling) public pure { + debtCeiling = bound(debtCeiling, 0, ReserveConfiguration.MAX_VALID_DEBT_CEILING); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DEBT_CEILING_MASK) | + (debtCeiling << ReserveConfiguration.DEBT_CEILING_START_BIT_POSITION) + }); + assertEq(config.getDebtCeiling(), debtCeiling); + } + + function testSetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public pure { + liquidationProtocolFee = bound( + liquidationProtocolFee, + 0, + ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) + }); + + config.setLiquidationProtocolFee(liquidationProtocolFee); + + assertEq( + (config.data & ~ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) >> + ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_START_BIT_POSITION, + liquidationProtocolFee + ); + } + + function testRevertSetLiquidationProtocolFee( + uint256 data, + uint256 liquidationProtocolFee + ) public { + liquidationProtocolFee = bound( + liquidationProtocolFee, + ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE + 1, + type(uint256).max + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_LIQUIDATION_PROTOCOL_FEE)); + config.setLiquidationProtocolFee(liquidationProtocolFee); + } + + function testGetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public pure { + liquidationProtocolFee = bound( + liquidationProtocolFee, + 0, + ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) | + (liquidationProtocolFee << ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_START_BIT_POSITION) + }); + assertEq(config.getLiquidationProtocolFee(), liquidationProtocolFee); + } + + function testSetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public pure { + unbackedMintCap = bound(unbackedMintCap, 0, ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.UNBACKED_MINT_CAP_MASK) + }); + + config.setUnbackedMintCap(unbackedMintCap); + + assertEq( + (config.data & ~ReserveConfiguration.UNBACKED_MINT_CAP_MASK) >> + ReserveConfiguration.UNBACKED_MINT_CAP_START_BIT_POSITION, + unbackedMintCap + ); + } + + function testRevertSetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public { + unbackedMintCap = bound( + unbackedMintCap, + ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP + 1, + type(uint256).max + ); + vm.assume(unbackedMintCap > ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.UNBACKED_MINT_CAP_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_UNBACKED_MINT_CAP)); + config.setUnbackedMintCap(unbackedMintCap); + } + + function testGetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public pure { + unbackedMintCap = bound(unbackedMintCap, 0, ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.UNBACKED_MINT_CAP_MASK) | + (unbackedMintCap << ReserveConfiguration.UNBACKED_MINT_CAP_START_BIT_POSITION) + }); + assertEq(config.getUnbackedMintCap(), unbackedMintCap); + } + + function testSetEModeCategory(uint256 data, uint256 _category) public pure { + uint256 category = bound(_category, 0, ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) + }); + + config.setEModeCategory(category); + + assertEq( + (config.data & ~ReserveConfiguration.EMODE_CATEGORY_MASK) >> + ReserveConfiguration.EMODE_CATEGORY_START_BIT_POSITION, + category + ); + } + + function testRevertSetEModeCategory(uint256 data, uint256 category) public { + category = bound( + category, + ReserveConfiguration.MAX_VALID_EMODE_CATEGORY + 1, + type(uint256).max + ); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) + }); + vm.expectRevert(bytes(Errors.INVALID_EMODE_CATEGORY)); + config.setEModeCategory(category); + } + + function testGetEModeCategory(uint256 data, uint256 category) public pure { + category = bound(category, 0, ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) | + (category << ReserveConfiguration.EMODE_CATEGORY_START_BIT_POSITION) + }); + assertEq(config.getEModeCategory(), category); + } + + function testGetFlags(uint256 data) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data + }); + + (bool active, bool frozen, bool borrowing, bool stableBorrowing, bool paused) = config + .getFlags(); + + assertEq(active, config.getActive()); + assertEq(frozen, config.getFrozen()); + assertEq(borrowing, config.getBorrowingEnabled()); + assertEq(stableBorrowing, config.getStableRateBorrowingEnabled()); + assertEq(paused, config.getPaused()); + } + + function testGetParams(uint256 data) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data + }); + + ( + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + uint256 decimals, + uint256 reserveFactor, + uint256 category + ) = config.getParams(); + + assertEq(ltv, config.getLtv()); + assertEq(liquidationThreshold, config.getLiquidationThreshold()); + assertEq(liquidationBonus, config.getLiquidationBonus()); + assertEq(decimals, config.getDecimals()); + assertEq(reserveFactor, config.getReserveFactor()); + assertEq(category, config.getEModeCategory()); + } + + function testGetCaps(uint256 data) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data + }); + (uint256 borrowCap, uint256 supplyCap) = config.getCaps(); + + assertEq(borrowCap, config.getBorrowCap()); + assertEq(supplyCap, config.getSupplyCap()); + } + + function testSetFlashloanEnabled(uint256 data) public pure { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data + }); + bool isFlashLoanEnabled = config.getFlashLoanEnabled(); + config.setFlashLoanEnabled(!isFlashLoanEnabled); + assertEq(config.getFlashLoanEnabled(), !isFlashLoanEnabled); + } +} diff --git a/src/test/UserConfiguration.t.sol b/src/test/UserConfiguration.t.sol new file mode 100644 index 000000000..2f52fd091 --- /dev/null +++ b/src/test/UserConfiguration.t.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.10; + +import {UserConfiguration} from './../../contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.sol'; +import {ReserveConfiguration} from './../../contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; +import {Errors} from './../../contracts/protocol/libraries/helpers/Errors.sol'; +import {Test} from 'forge-std/Test.sol'; + +contract UserConfigurationTest is Test { + using UserConfiguration for DataTypes.UserConfigurationMap; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + DataTypes.UserConfigurationMap internal config; + + mapping(address => DataTypes.ReserveData) internal reservesData; + mapping(uint256 => address) internal reservesList; + + function testSetBorrowing(uint256 data, uint8 index, bool borrowing) public { + config = DataTypes.UserConfigurationMap({data: data}); + + uint256 reserveIndex = index % ReserveConfiguration.MAX_RESERVES_COUNT; + config.setBorrowing(reserveIndex, borrowing); + + uint256 bitToCheck = 1 << (reserveIndex * 2); + bool isBorrowing = (config.data & UserConfiguration.BORROWING_MASK) & bitToCheck > 0; + + assertEq(borrowing, isBorrowing); + } + + function testRevertSetBorrowing(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + + vm.expectRevert(bytes(Errors.INVALID_RESERVE_INDEX)); + config.setBorrowing(ReserveConfiguration.MAX_RESERVES_COUNT, true); + } + + function testSetAsCollateral(uint256 data, uint8 index, bool usingAsCollateral) public { + config = DataTypes.UserConfigurationMap({data: data}); + + uint256 reserveIndex = index % ReserveConfiguration.MAX_RESERVES_COUNT; + config.setUsingAsCollateral(reserveIndex, usingAsCollateral); + + uint256 bitToCheck = 1 << (reserveIndex * 2 + 1); + bool isUsingAsCollateral = (config.data & UserConfiguration.COLLATERAL_MASK) & bitToCheck > 0; + + assertEq(usingAsCollateral, isUsingAsCollateral); + } + + function testRevertSetAsCollateral(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + + vm.expectRevert(bytes(Errors.INVALID_RESERVE_INDEX)); + config.setUsingAsCollateral(ReserveConfiguration.MAX_RESERVES_COUNT, true); + } + + function testIsUsingAsCollateralOrBorrowing(uint256 data, uint8 index) public { + config = DataTypes.UserConfigurationMap({data: data}); + + uint256 reserveIndex = index % ReserveConfiguration.MAX_RESERVES_COUNT; + uint256 bitToCheckBorrowing = 1 << (reserveIndex * 2); + uint256 bitToCheckCollateral = 1 << (reserveIndex * 2 + 1); + + bool isBorrowing = config.data & UserConfiguration.BORROWING_MASK & bitToCheckBorrowing > 0; + bool isUsingAsCollateral = config.data & + UserConfiguration.COLLATERAL_MASK & + bitToCheckCollateral > + 0; + + assertEq( + config.isUsingAsCollateralOrBorrowing(reserveIndex), + isUsingAsCollateral || isBorrowing + ); + } + + function testRevertIsUsingAsCollateralOrBorrowing(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + + vm.expectRevert(bytes(Errors.INVALID_RESERVE_INDEX)); + config.isUsingAsCollateralOrBorrowing(ReserveConfiguration.MAX_RESERVES_COUNT); + } + + function testIsBorrowing(uint256 data, uint8 index) public { + config = DataTypes.UserConfigurationMap({data: data}); + uint256 reserveIndex = index % ReserveConfiguration.MAX_RESERVES_COUNT; + uint256 bitToCheck = 1 << (reserveIndex * 2); + bool isBorrowing = (config.data & UserConfiguration.BORROWING_MASK) & bitToCheck > 0; + assertEq(config.isBorrowing(reserveIndex), isBorrowing); + } + + function testRevertIsBorrowing(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + vm.expectRevert(bytes(Errors.INVALID_RESERVE_INDEX)); + config.isBorrowing(ReserveConfiguration.MAX_RESERVES_COUNT); + } + + function testIsUsingAsCollateral(uint256 data, uint8 index) public { + config = DataTypes.UserConfigurationMap({data: data}); + uint256 reserveIndex = index % ReserveConfiguration.MAX_RESERVES_COUNT; + uint256 bitToCheck = 1 << (reserveIndex * 2 + 1); + bool isUsingAsCollateral = (config.data & UserConfiguration.COLLATERAL_MASK) & bitToCheck > 0; + assertEq(config.isUsingAsCollateral(reserveIndex), isUsingAsCollateral); + } + + function testRevertIsUsingAsCollateral(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + vm.expectRevert(bytes(Errors.INVALID_RESERVE_INDEX)); + config.isUsingAsCollateral(ReserveConfiguration.MAX_RESERVES_COUNT); + } + + function testIsUsingAsCollateralOne(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + assertEq( + config.isUsingAsCollateralOne(), + _countBitsOn(data & UserConfiguration.COLLATERAL_MASK) == 1 + ); + } + + function testIsUsingAsCollateralAny(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + assertEq( + config.isUsingAsCollateralAny(), + _countBitsOn(data & UserConfiguration.COLLATERAL_MASK) > 0 + ); + } + + function testIsBorrowingOne(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + assertEq(config.isBorrowingOne(), _countBitsOn(data & UserConfiguration.BORROWING_MASK) == 1); + } + + function testIsBorrowingAny(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + assertEq(config.isBorrowingAny(), _countBitsOn(data & UserConfiguration.BORROWING_MASK) > 0); + } + + function testIsEmpty(uint256 data) public { + config = DataTypes.UserConfigurationMap({data: data}); + assertEq(config.isEmpty(), data == 0); + } + + function testGetIsolationModeState( + address[6] calldata assets, + uint256[6] calldata assetConfigurationMaps, + uint40[6] calldata debtCeilings, + uint8 collateralCount, + uint8 borrowCount + ) public { + vm.assume(collateralCount <= 3 && borrowCount <= 3); + if (collateralCount == 0) { + borrowCount = 0; + } + + bool expectedIsolated = collateralCount == 1 && + debtCeilings[0] > 0 && + debtCeilings[0] < type(uint32).max; + + uint256 reservesCount = 0; + config = DataTypes.UserConfigurationMap({data: 0}); + + for (uint256 i = 0; i < collateralCount + borrowCount; i++) { + reservesList[reservesCount++] = assets[i]; + DataTypes.ReserveConfigurationMap memory assetConfig = DataTypes.ReserveConfigurationMap({ + data: assetConfigurationMaps[i] & ReserveConfiguration.DEBT_CEILING_MASK + }); + + if (i < collateralCount) { + if (expectedIsolated) { + assetConfig.setDebtCeiling(debtCeilings[i]); + } + config.setUsingAsCollateral(i, true); + } else { + config.setBorrowing(i, true); + } + reservesData[assets[i]].configuration = assetConfig; + } + + (bool isolated, address asset, uint256 ceiling) = config.getIsolationModeState( + reservesData, + reservesList + ); + + uint256 expectedCeiling = expectedIsolated ? debtCeilings[0] : 0; + address expectedAsset = expectedIsolated ? assets[0] : address(0); + + assertEq(isolated, expectedIsolated); + assertEq(asset, expectedAsset); + assertEq(ceiling, expectedCeiling); + } + + function testGetSiloedBorrowingState( + address[6] calldata assets, + uint256[6] calldata assetConfigurationMaps, + uint8 collateralCount, + uint8 borrowCount, + bool siloedBorrowing + ) public { + vm.assume(collateralCount <= 3 && borrowCount <= 3); + if (collateralCount == 0) { + borrowCount = 0; + } + if (siloedBorrowing) { + borrowCount = 1; + } + + uint256 reservesCount = 0; + config = DataTypes.UserConfigurationMap({data: 0}); + + for (uint256 i = 0; i < collateralCount + borrowCount; i++) { + reservesList[reservesCount++] = assets[i]; + DataTypes.ReserveConfigurationMap memory assetConfig = DataTypes.ReserveConfigurationMap({ + data: assetConfigurationMaps[i] & ReserveConfiguration.SILOED_BORROWING_MASK + }); + + if (i < collateralCount) { + config.setUsingAsCollateral(i, true); + } else { + config.setBorrowing(i, true); + if (siloedBorrowing) { + assetConfig.setSiloedBorrowing(true); + } + } + reservesData[assets[i]].configuration = assetConfig; + } + + (bool siloed, address siloedAsset) = config.getSiloedBorrowingState(reservesData, reservesList); + + assertEq(siloed, siloedBorrowing); + assertEq(siloedAsset, siloedBorrowing ? assets[collateralCount] : address(0)); + } + + function test_getFirstAssetIdByMask(uint256 data, bool collateralMask) public { + config = DataTypes.UserConfigurationMap({data: data}); + + uint256 mask = ( + collateralMask ? UserConfiguration.COLLATERAL_MASK : UserConfiguration.BORROWING_MASK + ); + + uint256 masked = data & mask; + uint256 id; + + for (uint256 i = 0; i < 256; i++) { + uint256 indexFlag = 2 ** i; + if (masked & indexFlag == indexFlag) { + // Divide i by 2 because we got both the collateral and the lending flags + id = i / 2; + break; + } + } + + assertEq(config._getFirstAssetIdByMask(mask), id); + } + + function _countBitsOn(uint256 a) internal pure returns (uint256) { + uint256 counter = 0; + while (a > 0) { + if (a & 1 == 1) { + counter++; + } + a >>= 1; + } + return counter; + } +} diff --git a/src/test/WadRayMul.t.sol b/src/test/WadRayMul.t.sol new file mode 100644 index 000000000..d4370f395 --- /dev/null +++ b/src/test/WadRayMul.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.10; + +import {WadRayMath} from './../../contracts/protocol/libraries/math/WadRayMath.sol'; +import {Test} from 'forge-std/Test.sol'; + +contract WadRayMathTest is Test { + using WadRayMath for uint256; + + function testWadMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - WadRayMath.HALF_WAD) / b; + vm.assume(safe); + assertEq(a.wadMul(b), (a * b + WadRayMath.HALF_WAD) / WadRayMath.WAD); + } + + function testWadDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / WadRayMath.WAD; + vm.assume(safe); + assertEq(a.wadDiv(b), (a * WadRayMath.WAD + b / 2) / b); + } + + function testRayMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - WadRayMath.HALF_RAY) / b; + vm.assume(safe); + assertEq(a.rayMul(b), (a * b + WadRayMath.HALF_RAY) / WadRayMath.RAY); + } + + function testRayDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / WadRayMath.RAY; + vm.assume(safe); + assertEq(a.rayDiv(b), (a * WadRayMath.RAY + b / 2) / b); + } + + function testRayToWad(uint256 a) public pure { + uint256 remainder = a % WadRayMath.WAD_RAY_RATIO; + uint256 expectedResult = a / WadRayMath.WAD_RAY_RATIO; + if (remainder >= WadRayMath.WAD_RAY_RATIO / 2) { + expectedResult++; + } + assertEq(a.rayToWad(), expectedResult); + } + + function testWadToRay(uint256 a) public pure { + bool safe = a <= type(uint256).max / WadRayMath.WAD_RAY_RATIO; + vm.assume(safe); + assertEq(a.wadToRay(), a * WadRayMath.WAD_RAY_RATIO); + } + + /// NEGATIVES /// + + function testFailWadDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / WadRayMath.WAD; + vm.assume(!safe); + a.wadDiv(b); + } + + function testFailWadMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - WadRayMath.HALF_WAD) / b; + vm.assume(!safe); + a.wadMul(b); + } + + function testFailRayMul(uint256 a, uint256 b) public pure { + bool safe = b == 0 || a <= (type(uint256).max - WadRayMath.HALF_RAY) / b; + vm.assume(!safe); + a.rayMul(b); + } + + function testFailRayDiv(uint256 a, uint256 b) public pure { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / WadRayMath.RAY; + vm.assume(!safe); + a.rayDiv(b); + } + + function testFailWadToRay(uint256 a) public pure { + bool safe = a <= type(uint256).max / WadRayMath.WAD_RAY_RATIO; + vm.assume(!safe); + a.wadToRay(); + } +}