From ffaada5fbd25aad95639850b96062904566b8a13 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Fri, 25 Feb 2022 14:26:53 +0000 Subject: [PATCH 01/12] feat: Initial addition of foundry fuzzing - Using foundry to fuzz the UserConfiguration --- .gitignore | 4 +- .gitmodules | 3 + foundry.toml | 8 ++ package.json | 1 + src/test/UserConfiguration.t.sol | 170 +++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 foundry.toml create mode 100644 src/test/UserConfiguration.t.sol diff --git a/.gitignore b/.gitignore index 0b724ca7c..f8e359232 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ coverage .coverage_cache .coverage_contracts coverage.json -deployments/ \ No newline at end of file +deployments/ + +out/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e12471968 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/ds-test"] + path = lib/ds-test + url = https://github.com/dapphub/ds-test diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 000000000..bb2d68944 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,8 @@ +[default] +src = 'src' +out = 'out' +libs = ['lib'] +remappings = ['ds-test/=lib/ds-test/src/'] +fuzz_runs = 10000 + +# See more config options https://github.com/gakonst/foundry/tree/master/config \ No newline at end of file diff --git a/package.json b/package.json index 2cb1d5ac5..d5f56443b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "prettier:write": "prettier --write 'tasks/**/*.ts' 'contracts/**/*.sol' 'helpers/**/*.ts' 'test-suites/**/*.ts' 'market-config/**/*.ts'", "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", "ci:test": ". ./setup-test-env.sh && npm run test", "ci:clean": "rm -rf ./artifacts ./cache ./types ./temp-artifacts", diff --git a/src/test/UserConfiguration.t.sol b/src/test/UserConfiguration.t.sol new file mode 100644 index 000000000..533508e7c --- /dev/null +++ b/src/test/UserConfiguration.t.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import 'ds-test/test.sol'; + +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'; + +contract UserConfigTestHelper is DSTest { + function assertEq(bool a, bool b) internal { + if (a != b) { + emit log('Error: a == b not satisfied [bool]'); + fail(); + } + } + + function assertEq( + bool a, + bool b, + string memory err + ) internal { + if (a != b) { + emit log_named_string('Error', err); + assertEq(a, b); + } + } + + function _countBitsOn(uint256 a) internal returns (uint256) { + uint256 counter = 0; + while (a > 0) { + if (a & 1 == 1) { + counter++; + } + a >>= 1; + } + return counter; + } +} + +contract ContractTest is UserConfigTestHelper { + using UserConfiguration for DataTypes.UserConfigurationMap; + + DataTypes.UserConfigurationMap internal config; + + 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 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 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 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 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 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() public { + require(false, 'TO BE IMPLEMENTED'); + } + + function testGetSiloedBorrowingState() public { + require(false, 'TO BE IMPLEMENTED'); + } + + function Test_getFirstAssetIdByMask(uint256 data, bool collateralMask) internal { + config = DataTypes.UserConfigurationMap({data: data}); + + uint256 mask = ( + collateralMask ? UserConfiguration.COLLATERAL_MASK : UserConfiguration.BORROWING_MASK + ); + + uint256 mapped = data & mask; + + uint256 id; + + while ((mapped >>= 1) & 1 == 0) { + id++; + } + id /= 2; + + assertEq(config._getFirstAssetIdByMask(mask), id); + } +} From d6d4ccc0d6a6cdd7373cfa9d6174261c059aa32c Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Fri, 25 Feb 2022 15:26:21 +0000 Subject: [PATCH 02/12] forge install: ds-test --- src/test/RayWadMul.t.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/test/RayWadMul.t.sol diff --git a/src/test/RayWadMul.t.sol b/src/test/RayWadMul.t.sol new file mode 100644 index 000000000..3f6223eba --- /dev/null +++ b/src/test/RayWadMul.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import 'ds-test/test.sol'; + +import {UserConfiguration} from './../../contracts/protocol/libraries/configuration/UserConfiguration.sol'; +import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.sol'; +import {WadRayMul} from './../../contracts/protocol/libraries/configuration/WadRayMul.sol'; + +contract RayWadMulTest is DSTest { + using WadRayMul for uint256; + + function testWadMul(uint256 a, uint256 b) public {} +} From 9fbda74ecc6a804cde842566e538b3e1f27ea301 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Fri, 25 Feb 2022 16:52:46 +0000 Subject: [PATCH 03/12] fix: Add fuzzer tet for WadRayMath --- src/Vm.sol | 105 +++++++++++++++++++++++++++++++++++++++ src/test/RayWadMul.t.sol | 14 ------ src/test/WadRayMul.t.sol | 85 +++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 src/Vm.sol delete mode 100644 src/test/RayWadMul.t.sol create mode 100644 src/test/WadRayMul.t.sol diff --git a/src/Vm.sol b/src/Vm.sol new file mode 100644 index 000000000..b2cbe273b --- /dev/null +++ b/src/Vm.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +interface Vm { + // Set block.timestamp + function warp(uint256) external; + + // Set block.number + function roll(uint256) external; + + // Set block.basefee + function fee(uint256) external; + + // Loads a storage slot from an address + function load(address account, bytes32 slot) external returns (bytes32); + + // Stores a value to an address' storage slot + function store( + address account, + bytes32 slot, + bytes32 value + ) external; + + // Signs data + function sign(uint256 privateKey, bytes32 digest) + external + returns ( + uint8 v, + bytes32 r, + bytes32 s + ); + + // Computes address for a given private key + function addr(uint256 privateKey) external returns (address); + + // Performs a foreign function call via terminal + function ffi(string[] calldata) external returns (bytes memory); + + // Sets the *next* call's msg.sender to be the input address + function prank(address) external; + + // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called + function startPrank(address) external; + + // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input + function prank(address, address) external; + + // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input + function startPrank(address, address) external; + + // Resets subsequent calls' msg.sender to be `address(this)` + function stopPrank() external; + + // Sets an address' balance + function deal(address who, uint256 newBalance) external; + + // Sets an address' code + function etch(address who, bytes calldata code) external; + + // Expects an error on next call + function expectRevert(bytes calldata) external; + + function expectRevert(bytes4) external; + + // Record all storage reads and writes + function record() external; + + // Gets all accessed reads and write slot from a recording session, for a given address + function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); + + // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). + // Call this function, then emit an event, then call a function. Internally after the call, we check if + // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) + function expectEmit( + bool, + bool, + bool, + bool + ) external; + + // Mocks a call to an address, returning specified data. + // Calldata can either be strict or a partial match, e.g. if you only + // pass a Solidity selector to the expected calldata, then the entire Solidity + // function will be mocked. + function mockCall( + address, + bytes calldata, + bytes calldata + ) external; + + // Clears all mocked calls + function clearMockedCalls() external; + + // Expect a call to an address with the specified calldata. + // Calldata can either be strict or a partial match + function expectCall(address, bytes calldata) external; + + function getCode(string calldata) external returns (bytes memory); + + // Label an address in test traces + function label(address addr, string memory label) external; + + // When fuzzing, generate new inputs if conditional not met + function assume(bool) external; +} diff --git a/src/test/RayWadMul.t.sol b/src/test/RayWadMul.t.sol deleted file mode 100644 index 3f6223eba..000000000 --- a/src/test/RayWadMul.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; - -import 'ds-test/test.sol'; - -import {UserConfiguration} from './../../contracts/protocol/libraries/configuration/UserConfiguration.sol'; -import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.sol'; -import {WadRayMul} from './../../contracts/protocol/libraries/configuration/WadRayMul.sol'; - -contract RayWadMulTest is DSTest { - using WadRayMul for uint256; - - function testWadMul(uint256 a, uint256 b) public {} -} diff --git a/src/test/WadRayMul.t.sol b/src/test/WadRayMul.t.sol new file mode 100644 index 000000000..6e61b72e4 --- /dev/null +++ b/src/test/WadRayMul.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import 'ds-test/test.sol'; + +import {WadRayMath} from './../../contracts/protocol/libraries/math/WadRayMath.sol'; + +import {Vm} from './../Vm.sol'; + +contract WadRayMathTest is DSTest { + using WadRayMath for uint256; + + Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + function testWadMul(uint256 a, uint256 b) public { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / WadRayMath.RAY; + VM.assume(!safe); + a.rayDiv(b); + } + + function testFailWadToRay(uint256 a) public { + bool safe = a <= type(uint256).max / WadRayMath.WAD_RAY_RATIO; + VM.assume(!safe); + a.wadToRay(); + } +} From dea51e5b24151f5e9a4ba99017be9f66f35ca76f Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Fri, 25 Feb 2022 16:59:40 +0000 Subject: [PATCH 04/12] fix: Add fuzzer test for PercentageMath --- src/test/PercentageMath.t.sol | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/test/PercentageMath.t.sol diff --git a/src/test/PercentageMath.t.sol b/src/test/PercentageMath.t.sol new file mode 100644 index 000000000..3e5be58fe --- /dev/null +++ b/src/test/PercentageMath.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import 'ds-test/test.sol'; +import {PercentageMath} from './../../contracts/protocol/libraries/math/PercentageMath.sol'; + +import {Vm} from './../Vm.sol'; + +contract PercentageMathTest is DSTest { + using PercentageMath for uint256; + + Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + function testPercentMul(uint256 a, uint256 b) public { + 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 { + 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 { + 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 { + bool safe = b != 0 && a <= (type(uint256).max - b / 2) / PercentageMath.PERCENTAGE_FACTOR; + VM.assume(!safe); + a.percentDiv(b); + } +} From b622d46c6f320fa99e11b87ef8f315ef17259dd5 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Mon, 28 Feb 2022 14:55:35 +0000 Subject: [PATCH 05/12] forge install: ds-test --- src/test/ReserveConfiguration.t.sol | 244 ++++++++++++++++++++++++++++ src/test/TestHelper.sol | 35 ++++ 2 files changed, 279 insertions(+) create mode 100644 src/test/ReserveConfiguration.t.sol create mode 100644 src/test/TestHelper.sol diff --git a/src/test/ReserveConfiguration.t.sol b/src/test/ReserveConfiguration.t.sol new file mode 100644 index 000000000..e4ed12185 --- /dev/null +++ b/src/test/ReserveConfiguration.t.sol @@ -0,0 +1,244 @@ +// 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 {Vm} from './../Vm.sol'; +import {Errors} from './../../contracts/protocol/libraries/helpers/Errors.sol'; + +// TODO: There is an error with the revert messages. For now, just assume they are correct. Not sure if because of libraries or what it is +import {TestHelper} from './TestHelper.sol'; + +contract ReserveConfigurationTest is TestHelper { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + function testSetLtv(uint256 data, uint256 ltv) public { + VM.assume(ltv <= ReserveConfiguration.MAX_VALID_LTV); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data & ReserveConfiguration.LTV_MASK + }); + config.setLtv(ltv); + assertEq(config.data & ~ReserveConfiguration.LTV_MASK, ltv); + } + + function testFailSetLtvTooHigh(uint256 data, uint256 ltv) public { + VM.assume(ltv > ReserveConfiguration.MAX_VALID_LTV); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data & ReserveConfiguration.LTV_MASK + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_LTV)); + config.setLtv(ltv); + } + + function testGetLtv(uint256 data, uint256 ltv) public { + // Notice that the mask may support values that is significantly larger than the max value. + VM.assume(ltv <= 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 { + VM.assume(threshold <= ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD); + + 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 testFailSetLiquidationThreshold(uint256 data, uint256 threshold) public { + VM.assume(threshold > ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD); + + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) + }); + + //TODO: VM.expectRevert(bytes(Errors.INVALID_LIQ_THRESHOLD)); + config.setLiquidationThreshold(threshold); + } + + function testGetLiquidationThreshold(uint256 data, uint256 threshold) public { + VM.assume(threshold <= type(uint16).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) | + (threshold << ReserveConfiguration.LIQUIDATION_THRESHOLD_START_BIT_POSITION) + }); + assertEq(config.getLiquidationThreshold(), threshold); + } + + function testSetDecimals(uint256 data, uint256 decimals) public { + VM.assume(decimals <= type(uint8).max); + 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 testFailSetDecimals(uint256 data, uint256 decimals) public { + VM.assume(decimals > type(uint8).max); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DECIMALS_MASK) + }); + + config.setDecimals(decimals); + } + + function testGetDecimals(uint256 data, uint256 decimals) public { + VM.assume(decimals <= type(uint8).max); + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 borrowAble) public { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) + }); + config.setBorrowableInIsolation(borrowAble); + uint256 isolationBorrowAbleBit = borrowAble ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) >> + ReserveConfiguration.BORROWABLE_IN_ISOLATION_START_BIT_POSITION, + isolationBorrowAbleBit + ); + } + + function testGetBorrowableInIsolation(uint256 data, bool borrowAble) public { + uint256 isolationBorrowAble = borrowAble ? 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(), borrowAble); + } + + function testSetSiloedBorrowing(uint256 data, bool borrowAble) public { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SILOED_BORROWING_MASK) + }); + config.setSiloedBorrowing(borrowAble); + uint256 siloedBorrowingBit = borrowAble ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.SILOED_BORROWING_MASK) >> + ReserveConfiguration.SILOED_BORROWING_START_BIT_POSITION, + siloedBorrowingBit + ); + } + + function testGetSiloedBorrowing(uint256 data, bool borrowAble) public { + uint256 siloedBorrowingBit = borrowAble ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SILOED_BORROWING_MASK) | + (siloedBorrowingBit << ReserveConfiguration.SILOED_BORROWING_START_BIT_POSITION) + }); + assertEq(config.getSiloedBorrowing(), borrowAble); + } + + function testSetBorrowingEnabled(uint256 data, bool borrowAble) public { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWING_MASK) + }); + config.setBorrowingEnabled(borrowAble); + uint256 borrowingBit = borrowAble ? 1 : 0; + assertEq( + (config.data & ~ReserveConfiguration.BORROWING_MASK) >> + ReserveConfiguration.BORROWING_ENABLED_START_BIT_POSITION, + borrowingBit + ); + } + + function testGetBorrowingEnabled(uint256 data, bool borrowAble) public { + uint256 borrowingBit = borrowAble ? 1 : 0; + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROWING_MASK) | + (borrowingBit << ReserveConfiguration.BORROWING_ENABLED_START_BIT_POSITION) + }); + assertEq(config.getBorrowingEnabled(), borrowAble); + } +} diff --git a/src/test/TestHelper.sol b/src/test/TestHelper.sol new file mode 100644 index 000000000..b801c1b0d --- /dev/null +++ b/src/test/TestHelper.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import 'ds-test/test.sol'; + +contract TestHelper is DSTest { + function assertEq(bool a, bool b) internal { + if (a != b) { + emit log('Error: a == b not satisfied [bool]'); + fail(); + } + } + + function assertEq( + bool a, + bool b, + string memory err + ) internal { + if (a != b) { + emit log_named_string('Error', err); + assertEq(a, b); + } + } + + function _countBitsOn(uint256 a) internal returns (uint256) { + uint256 counter = 0; + while (a > 0) { + if (a & 1 == 1) { + counter++; + } + a >>= 1; + } + return counter; + } +} From 19a20a80aa909c703305cbadd5d21721b2d875db Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Mon, 28 Feb 2022 16:41:22 +0000 Subject: [PATCH 06/12] test: Add fuzz tests for ReserveConfiguration --- package.json | 2 + src/test/ReserveConfiguration.t.sol | 377 ++++++++++++++++++++++++++-- src/test/UserConfiguration.t.sol | 35 +-- 3 files changed, 361 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 320160c9c..53ff61ad4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "node": ">=16.0.0" }, "scripts": { + "setup:foundry": "foundryup && git submodule update --init", + "install:foundry": "curl -L https://foundry.paradigm.xyz | bash", "size": "npm run compile && npm run hardhat size-contracts", "auth-registry": "npx dotenv-cli -- bash -c 'npm config set //npm.pkg.github.com/:_authToken \"$NODE_AUTH_TOKEN\"'", "run-env": "npm run auth-registry && npm i && tail -f /dev/null", diff --git a/src/test/ReserveConfiguration.t.sol b/src/test/ReserveConfiguration.t.sol index e4ed12185..f43c9d9cc 100644 --- a/src/test/ReserveConfiguration.t.sol +++ b/src/test/ReserveConfiguration.t.sol @@ -6,9 +6,10 @@ import {ReserveConfiguration} from './../../contracts/protocol/libraries/configu import {Vm} from './../Vm.sol'; import {Errors} from './../../contracts/protocol/libraries/helpers/Errors.sol'; -// TODO: There is an error with the revert messages. For now, just assume they are correct. Not sure if because of libraries or what it is import {TestHelper} from './TestHelper.sol'; +// TODO: There is an error with the revert messages. For now, we are checking after reverts only, +// as the integration tests are already catching the revert messages contract ReserveConfigurationTest is TestHelper { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; @@ -77,6 +78,42 @@ contract ReserveConfigurationTest is TestHelper { assertEq(config.getLiquidationThreshold(), threshold); } + function testSetLiquidationBonus(uint256 data, uint256 bonus) public { + VM.assume(bonus <= 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 testFailSetLiquidationBonus(uint256 data, uint256 bonus) public { + VM.assume(bonus > ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); + + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) + }); + + //TODO: VM.expectRevert(bytes(Errors.INVALID_LIQ_THRESHOLD)); + config.setLiquidationBonus(bonus); + } + + function testGetLiquidationBonus(uint256 data, uint256 bonus) public { + VM.assume(bonus <= type(uint16).max); + 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 { VM.assume(decimals <= type(uint8).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ @@ -97,7 +134,7 @@ contract ReserveConfigurationTest is TestHelper { DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.DECIMALS_MASK) }); - + //TODO: VM.expectRevert(bytes(Errors.INVALID_DECIMALS)); config.setDecimals(decimals); } @@ -176,12 +213,12 @@ contract ReserveConfigurationTest is TestHelper { assertEq(config.getPaused(), paused); } - function testSetBorrowableInIsolation(uint256 data, bool borrowAble) public { + function testSetBorrowableInIsolation(uint256 data, bool enabled) public { DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) }); - config.setBorrowableInIsolation(borrowAble); - uint256 isolationBorrowAbleBit = borrowAble ? 1 : 0; + config.setBorrowableInIsolation(enabled); + uint256 isolationBorrowAbleBit = enabled ? 1 : 0; assertEq( (config.data & ~ReserveConfiguration.BORROWABLE_IN_ISOLATION_MASK) >> ReserveConfiguration.BORROWABLE_IN_ISOLATION_START_BIT_POSITION, @@ -189,21 +226,21 @@ contract ReserveConfigurationTest is TestHelper { ); } - function testGetBorrowableInIsolation(uint256 data, bool borrowAble) public { - uint256 isolationBorrowAble = borrowAble ? 1 : 0; + function testGetBorrowableInIsolation(uint256 data, bool enabled) public { + 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(), borrowAble); + assertEq(config.getBorrowableInIsolation(), enabled); } - function testSetSiloedBorrowing(uint256 data, bool borrowAble) public { + function testSetSiloedBorrowing(uint256 data, bool enabled) public { DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.SILOED_BORROWING_MASK) }); - config.setSiloedBorrowing(borrowAble); - uint256 siloedBorrowingBit = borrowAble ? 1 : 0; + config.setSiloedBorrowing(enabled); + uint256 siloedBorrowingBit = enabled ? 1 : 0; assertEq( (config.data & ~ReserveConfiguration.SILOED_BORROWING_MASK) >> ReserveConfiguration.SILOED_BORROWING_START_BIT_POSITION, @@ -211,21 +248,21 @@ contract ReserveConfigurationTest is TestHelper { ); } - function testGetSiloedBorrowing(uint256 data, bool borrowAble) public { - uint256 siloedBorrowingBit = borrowAble ? 1 : 0; + function testGetSiloedBorrowing(uint256 data, bool enabled) public { + 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(), borrowAble); + assertEq(config.getSiloedBorrowing(), enabled); } - function testSetBorrowingEnabled(uint256 data, bool borrowAble) public { + function testSetBorrowingEnabled(uint256 data, bool enabled) public { DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.BORROWING_MASK) }); - config.setBorrowingEnabled(borrowAble); - uint256 borrowingBit = borrowAble ? 1 : 0; + config.setBorrowingEnabled(enabled); + uint256 borrowingBit = enabled ? 1 : 0; assertEq( (config.data & ~ReserveConfiguration.BORROWING_MASK) >> ReserveConfiguration.BORROWING_ENABLED_START_BIT_POSITION, @@ -233,12 +270,312 @@ contract ReserveConfigurationTest is TestHelper { ); } - function testGetBorrowingEnabled(uint256 data, bool borrowAble) public { - uint256 borrowingBit = borrowAble ? 1 : 0; + function testGetBorrowingEnabled(uint256 data, bool enabled) public { + 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(), borrowAble); + assertEq(config.getBorrowingEnabled(), enabled); + } + + function testSetStableBorrowingEnabled(uint256 data, bool enabled) public { + 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 { + 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 { + VM.assume(reserveFactor <= 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 testFailSetReserveFactor(uint256 data, uint256 reserveFactor) public { + VM.assume(reserveFactor > ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_RESERVE_FACTOR)); + config.setReserveFactor(reserveFactor); + } + + function testGetReserveFactor(uint256 data, uint256 reserveFactor) public { + VM.assume(reserveFactor <= type(uint16).max); + 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 { + VM.assume(borrowCap <= 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 testFailSetBorrowCap(uint256 data, uint256 borrowCap) public { + VM.assume(borrowCap > ReserveConfiguration.MAX_VALID_BORROW_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.BORROW_CAP_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_BORROW_CAP)); + config.setBorrowCap(borrowCap); + } + + function testGetBorrowCap(uint256 data, uint256 borrowCap) public { + VM.assume(borrowCap < 2**36); + 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 { + VM.assume(supplyCap <= 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 testFailSetSupplyCap(uint256 data, uint256 supplyCap) public { + VM.assume(supplyCap > ReserveConfiguration.MAX_VALID_SUPPLY_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_SUPPLY_CAP)); + config.setSupplyCap(supplyCap); + } + + function testGetSupplyCap(uint256 data, uint256 supplyCap) public { + VM.assume(supplyCap < 2**36); + 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 { + VM.assume(debtCeiling <= 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 testFailSetDebtCeiling(uint256 data, uint256 debtCeiling) public { + VM.assume(debtCeiling > ReserveConfiguration.MAX_VALID_DEBT_CEILING); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.DEBT_CEILING_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_DEBT_CEILING)); + config.setDebtCeiling(debtCeiling); + } + + function testGetDebtCeiling(uint256 data, uint256 debtCeiling) public { + VM.assume(debtCeiling < 2**36); + 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 { + VM.assume(liquidationProtocolFee <= 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 testFailSetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public { + VM.assume(liquidationProtocolFee > ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.INVALID_LIQUIDATION_PROTOCOL_FEE)); + config.setLiquidationProtocolFee(liquidationProtocolFee); + } + + function testGetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public { + VM.assume(liquidationProtocolFee < type(uint16).max); + 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 { + VM.assume(unbackedMintCap <= 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 testFailSetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public { + VM.assume(unbackedMintCap > ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.UNBACKED_MINT_CAP_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.MAX_VALID_UNBACKED_MINT_CAP)); + config.setUnbackedMintCap(unbackedMintCap); + } + + function testGetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public { + VM.assume(unbackedMintCap < type(uint16).max); + 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 { + VM.assume(category <= 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 testFailSetEModeCategory(uint256 data, uint256 category) public { + VM.assume(category > ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) + }); + //TODO: VM.expectRevert(bytes(Errors.MAX_VALID_EMODE_CATEGORY)); + config.setEModeCategory(category); + } + + function testGetEModeCategory(uint256 data, uint256 category) public { + VM.assume(category < type(uint8).max); + 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 { + 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 { + 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 { + DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ + data: data + }); + (uint256 borrowCap, uint256 supplyCap) = config.getCaps(); + + assertEq(borrowCap, config.getBorrowCap()); + assertEq(supplyCap, config.getSupplyCap()); } } diff --git a/src/test/UserConfiguration.t.sol b/src/test/UserConfiguration.t.sol index 533508e7c..754425caa 100644 --- a/src/test/UserConfiguration.t.sol +++ b/src/test/UserConfiguration.t.sol @@ -1,44 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.10; -import 'ds-test/test.sol'; - 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'; -contract UserConfigTestHelper is DSTest { - function assertEq(bool a, bool b) internal { - if (a != b) { - emit log('Error: a == b not satisfied [bool]'); - fail(); - } - } - - function assertEq( - bool a, - bool b, - string memory err - ) internal { - if (a != b) { - emit log_named_string('Error', err); - assertEq(a, b); - } - } - - function _countBitsOn(uint256 a) internal returns (uint256) { - uint256 counter = 0; - while (a > 0) { - if (a & 1 == 1) { - counter++; - } - a >>= 1; - } - return counter; - } -} +import {TestHelper} from './TestHelper.sol'; -contract ContractTest is UserConfigTestHelper { +contract ContractTest is TestHelper { using UserConfiguration for DataTypes.UserConfigurationMap; DataTypes.UserConfigurationMap internal config; From f8f43e7b7b841428578ba0a5e930b32c1d95156d Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Tue, 1 Mar 2022 10:59:18 +0000 Subject: [PATCH 07/12] feat: Add fuzz test for CalldataLogic --- foundry.toml | 1 + src/test/CalldataLogic.t.sol | 284 +++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 src/test/CalldataLogic.t.sol diff --git a/foundry.toml b/foundry.toml index bb2d68944..caa4286b3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,5 +4,6 @@ out = 'out' libs = ['lib'] remappings = ['ds-test/=lib/ds-test/src/'] fuzz_runs = 10000 +optimizer = false # See more config options https://github.com/gakonst/foundry/tree/master/config \ 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..0b0146466 --- /dev/null +++ b/src/test/CalldataLogic.t.sol @@ -0,0 +1,284 @@ +// 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 {Vm} from './../Vm.sol'; +import {TestHelper} from './TestHelper.sol'; + +// TODO: There is an error with the revert messages. For now, we are checking after reverts only, +// as the integration tests are already catching the revert messages +contract CalldataLogicTest is TestHelper { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + mapping(uint256 => address) reservesList; + + function setUp() public { + for (uint256 i = 0; i < 128; i++) { + reservesList[i] = address(uint160(400 + i)); + } + } + + function testDecodeSupplyParams( + uint16 assetId, + uint128 amount, + uint16 referralCode + ) public { + VM.assume(assetId < 128); + + 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]); + assertEq(_amount, amount); + assertEq(_referralCode, referralCode); + } + + function testDecodeSupplyWithPermitParams( + uint16 assetId, + uint128 amount, + uint16 referralCode, + uint256 deadLine, + uint8 permitV + ) public { + VM.assume(assetId < 128 && deadLine < type(uint32).max); + + 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]); + assertEq(_amount, amount); + assertEq(_referralCode, referralCode); + assertEq(_deadLine, deadLine); + assertEq(_permitV, permitV); + } + + function testDecodeWithdrawParams(uint16 assetId, uint128 amount) public { + 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]); + assertEq(_amount, expectedAmount); + } + + function testDecodeBorrowParams( + uint16 assetId, + uint128 amount, + bool stableRateMode, + uint16 referralCode + ) public { + 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]); + assertEq(_amount, amount); + assertEq(_interestRateMode, interestRateMode); + assertEq(_referralCode, referralCode); + } + + function testDecodeRepayParams( + uint16 assetId, + uint128 amount, + bool stableRateMode + ) public { + 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]); + assertEq(_amount, expectedAmount); + assertEq(_interestRateMode, interestRateMode); + } + + struct DecodeRepayWithPermitHelper { + uint256 expectedAmount; + address _asset; + uint256 _amount; + uint256 _interestRateMode; + uint256 _deadline; + uint8 _permitV; + } + + function testDecodeRepayWithPermitParams( + uint16 assetId, + uint128 amount, + bool stableRateMode, + uint32 deadline, + uint8 permitV + ) public { + 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]); + assertEq(vars._amount, vars.expectedAmount); + assertEq(vars._interestRateMode, stableRateMode ? 1 : 2); + assertEq(vars._deadline, uint256(deadline)); + assertEq(vars._permitV, permitV); + } + + function testDecodeSwapBorrowRateModeParams(uint16 assetId, bool stableRateMode) public { + 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]); + assertEq(_interestRateMode, interestRateMode); + } + + function testDecodeRebalanceStableBorrowRateParams(uint16 assetId, address user) public { + bytes32 args; + assembly { + args := add(assetId, shl(16, user)) + } + + (address _asset, address _user) = CalldataLogic.decodeRebalanceStableBorrowRateParams( + reservesList, + args + ); + assertEq(_asset, reservesList[assetId]); + assertEq(_user, user); + } + + function testDecodeSetUserUseReserveAsCollateralParams(uint16 assetId, bool useAsCollateral) + public + { + bytes32 args; + assembly { + args := add(assetId, shl(16, useAsCollateral)) + } + + (address _asset, bool _useAsCollateral) = CalldataLogic + .decodeSetUserUseReserveAsCollateralParams(reservesList, args); + + assertEq(_asset, reservesList[assetId]); + assertEq(_useAsCollateral, useAsCollateral); + } + + struct DecodeLiquidationCallParamsHelper { + uint256 expectedDebtToCover; + address collateralAsset; + address debtAsset; + address user; + uint256 debtToCover; + bool receiveAToken; + } + + function testDecodeLiquidationCallParams( + uint16 collateralAssetId, + uint16 debtAssetId, + address user, + uint128 debtToCover, + bool receiveAToken + ) public { + 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]); + assertEq(vars.debtAsset, reservesList[debtAssetId]); + assertEq(vars.user, user); + assertEq(vars.debtToCover, vars.expectedDebtToCover); + assertEq(vars.receiveAToken, receiveAToken); + } +} From 24f8d491aaf8284d9704687fe67ec6d1d981b4d5 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Tue, 1 Mar 2022 11:50:06 +0000 Subject: [PATCH 08/12] fix: Implement test for isolationModeState and SiloedBorrowingState --- src/test/UserConfiguration.t.sol | 100 +++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/src/test/UserConfiguration.t.sol b/src/test/UserConfiguration.t.sol index 754425caa..268cee83b 100644 --- a/src/test/UserConfiguration.t.sol +++ b/src/test/UserConfiguration.t.sol @@ -6,12 +6,19 @@ import {DataTypes} from './../../contracts/protocol/libraries/types/DataTypes.so import {ReserveConfiguration} from './../../contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; import {TestHelper} from './TestHelper.sol'; +import {Vm} from './../Vm.sol'; -contract ContractTest is TestHelper { +contract UserConfigurationTest is TestHelper { using UserConfiguration for DataTypes.UserConfigurationMap; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); DataTypes.UserConfigurationMap internal config; + mapping(address => DataTypes.ReserveData) internal reservesData; + mapping(uint256 => address) internal reservesList; + function testSetBorrowing( uint256 data, uint8 index, @@ -110,12 +117,94 @@ contract ContractTest is TestHelper { assertEq(config.isEmpty(), data == 0); } - function testGetIsolationModeState() public { - require(false, 'TO BE IMPLEMENTED'); + 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() public { - require(false, 'TO BE IMPLEMENTED'); + 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) internal { @@ -126,7 +215,6 @@ contract ContractTest is TestHelper { ); uint256 mapped = data & mask; - uint256 id; while ((mapped >>= 1) & 1 == 0) { From f3ce5fa4cbf645e0f6a647bb32bdd45b36e51524 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Tue, 1 Mar 2022 11:57:57 +0000 Subject: [PATCH 09/12] Create ds-test --- lib/ds-test | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/ds-test diff --git a/lib/ds-test b/lib/ds-test new file mode 160000 index 000000000..0a5da56b0 --- /dev/null +++ b/lib/ds-test @@ -0,0 +1 @@ +Subproject commit 0a5da56b0d65960e6a994d2ec8245e6edd38c248 From 2a79cb323d115c26ec8ab3107ea96b5909cb4f2c Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Mon, 7 Mar 2022 11:02:13 +0000 Subject: [PATCH 10/12] fix: Use `HEVM_ADDRESS` from DSTest. --- src/test/CalldataLogic.t.sol | 2 +- src/test/PercentageMath.t.sol | 3 +-- src/test/ReserveConfiguration.t.sol | 2 +- src/test/UserConfiguration.t.sol | 2 +- src/test/WadRayMul.t.sol | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/CalldataLogic.t.sol b/src/test/CalldataLogic.t.sol index 0b0146466..a711580ef 100644 --- a/src/test/CalldataLogic.t.sol +++ b/src/test/CalldataLogic.t.sol @@ -15,7 +15,7 @@ import {TestHelper} from './TestHelper.sol'; contract CalldataLogicTest is TestHelper { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Vm constant VM = Vm(HEVM_ADDRESS); mapping(uint256 => address) reservesList; diff --git a/src/test/PercentageMath.t.sol b/src/test/PercentageMath.t.sol index 3e5be58fe..3410c25a6 100644 --- a/src/test/PercentageMath.t.sol +++ b/src/test/PercentageMath.t.sol @@ -9,8 +9,7 @@ import {Vm} from './../Vm.sol'; contract PercentageMathTest is DSTest { using PercentageMath for uint256; - Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - +Vm constant VM = Vm(HEVM_ADDRESS); function testPercentMul(uint256 a, uint256 b) public { bool safe = b == 0 || a <= (type(uint256).max - PercentageMath.HALF_PERCENTAGE_FACTOR) / b; VM.assume(safe); diff --git a/src/test/ReserveConfiguration.t.sol b/src/test/ReserveConfiguration.t.sol index f43c9d9cc..c7431c122 100644 --- a/src/test/ReserveConfiguration.t.sol +++ b/src/test/ReserveConfiguration.t.sol @@ -13,7 +13,7 @@ import {TestHelper} from './TestHelper.sol'; contract ReserveConfigurationTest is TestHelper { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); +Vm constant VM = Vm(HEVM_ADDRESS); function testSetLtv(uint256 data, uint256 ltv) public { VM.assume(ltv <= ReserveConfiguration.MAX_VALID_LTV); diff --git a/src/test/UserConfiguration.t.sol b/src/test/UserConfiguration.t.sol index 268cee83b..13b466ff3 100644 --- a/src/test/UserConfiguration.t.sol +++ b/src/test/UserConfiguration.t.sol @@ -12,7 +12,7 @@ contract UserConfigurationTest is TestHelper { using UserConfiguration for DataTypes.UserConfigurationMap; using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Vm constant VM = Vm(HEVM_ADDRESS); DataTypes.UserConfigurationMap internal config; diff --git a/src/test/WadRayMul.t.sol b/src/test/WadRayMul.t.sol index 6e61b72e4..4ef771120 100644 --- a/src/test/WadRayMul.t.sol +++ b/src/test/WadRayMul.t.sol @@ -10,7 +10,7 @@ import {Vm} from './../Vm.sol'; contract WadRayMathTest is DSTest { using WadRayMath for uint256; - Vm constant VM = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Vm constant VM = Vm(HEVM_ADDRESS); function testWadMul(uint256 a, uint256 b) public { bool safe = b == 0 || a <= (type(uint256).max - WadRayMath.HALF_WAD) / b; From 8a7705c5a3bd6adfd8054670ab64470dbb7c27bf Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Mon, 7 Mar 2022 11:14:57 +0000 Subject: [PATCH 11/12] fix: Add nonEq zero address for assets + reduce fuzz rejects --- src/test/CalldataLogic.t.sol | 51 +++++++++++++++++++++++++----------- src/test/TestHelper.sol | 9 +++++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/test/CalldataLogic.t.sol b/src/test/CalldataLogic.t.sol index a711580ef..ff21b01f3 100644 --- a/src/test/CalldataLogic.t.sol +++ b/src/test/CalldataLogic.t.sol @@ -19,18 +19,20 @@ contract CalldataLogicTest is TestHelper { mapping(uint256 => address) reservesList; + uint256 constant RESERVE_COUNT = 128; + function setUp() public { - for (uint256 i = 0; i < 128; i++) { + for (uint256 i = 0; i < RESERVE_COUNT; i++) { reservesList[i] = address(uint160(400 + i)); } } function testDecodeSupplyParams( - uint16 assetId, + uint8 assetId, uint128 amount, uint16 referralCode ) public { - VM.assume(assetId < 128); + VM.assume(assetId < RESERVE_COUNT); bytes32 args; assembly { @@ -43,18 +45,19 @@ contract CalldataLogicTest is TestHelper { ); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_amount, amount); assertEq(_referralCode, referralCode); } function testDecodeSupplyWithPermitParams( - uint16 assetId, + uint8 assetId, uint128 amount, uint16 referralCode, - uint256 deadLine, + uint32 deadLine, uint8 permitV ) public { - VM.assume(assetId < 128 && deadLine < type(uint32).max); + VM.assume(assetId < RESERVE_COUNT); bytes32 args; assembly { @@ -76,13 +79,15 @@ contract CalldataLogicTest is TestHelper { ) = 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(uint16 assetId, uint128 amount) public { + function testDecodeWithdrawParams(uint8 assetId, uint128 amount) public { + VM.assume(assetId < RESERVE_COUNT); bytes32 args; assembly { args := add(assetId, shl(16, amount)) @@ -93,15 +98,17 @@ contract CalldataLogicTest is TestHelper { uint256 expectedAmount = amount == type(uint128).max ? type(uint256).max : uint256(amount); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_amount, expectedAmount); } function testDecodeBorrowParams( - uint16 assetId, + uint8 assetId, uint128 amount, bool stableRateMode, uint16 referralCode ) public { + VM.assume(assetId < RESERVE_COUNT); uint256 interestRateMode = stableRateMode ? 1 : 2; bytes32 args; @@ -120,16 +127,18 @@ contract CalldataLogicTest is TestHelper { ) = CalldataLogic.decodeBorrowParams(reservesList, args); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_amount, amount); assertEq(_interestRateMode, interestRateMode); assertEq(_referralCode, referralCode); } function testDecodeRepayParams( - uint16 assetId, + uint8 assetId, uint128 amount, bool stableRateMode ) public { + VM.assume(assetId < RESERVE_COUNT); uint256 interestRateMode = stableRateMode ? 1 : 2; uint256 expectedAmount = amount == type(uint128).max ? type(uint256).max : amount; @@ -144,6 +153,7 @@ contract CalldataLogicTest is TestHelper { ); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_amount, expectedAmount); assertEq(_interestRateMode, interestRateMode); } @@ -158,12 +168,13 @@ contract CalldataLogicTest is TestHelper { } function testDecodeRepayWithPermitParams( - uint16 assetId, + uint8 assetId, uint128 amount, bool stableRateMode, uint32 deadline, uint8 permitV ) public { + VM.assume(assetId < RESERVE_COUNT); DecodeRepayWithPermitHelper memory vars; vars.expectedAmount = amount == type(uint128).max ? type(uint256).max : amount; @@ -188,13 +199,15 @@ contract CalldataLogicTest is TestHelper { ) = 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(uint16 assetId, bool stableRateMode) public { + function testDecodeSwapBorrowRateModeParams(uint8 assetId, bool stableRateMode) public { + VM.assume(assetId < RESERVE_COUNT); uint256 interestRateMode = stableRateMode ? 1 : 2; bytes32 args; assembly { @@ -207,10 +220,12 @@ contract CalldataLogicTest is TestHelper { ); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_interestRateMode, interestRateMode); } - function testDecodeRebalanceStableBorrowRateParams(uint16 assetId, address user) public { + function testDecodeRebalanceStableBorrowRateParams(uint8 assetId, address user) public { + VM.assume(assetId < RESERVE_COUNT); bytes32 args; assembly { args := add(assetId, shl(16, user)) @@ -221,12 +236,14 @@ contract CalldataLogicTest is TestHelper { args ); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_user, user); } - function testDecodeSetUserUseReserveAsCollateralParams(uint16 assetId, bool useAsCollateral) + function testDecodeSetUserUseReserveAsCollateralParams(uint8 assetId, bool useAsCollateral) public { + VM.assume(assetId < RESERVE_COUNT); bytes32 args; assembly { args := add(assetId, shl(16, useAsCollateral)) @@ -236,6 +253,7 @@ contract CalldataLogicTest is TestHelper { .decodeSetUserUseReserveAsCollateralParams(reservesList, args); assertEq(_asset, reservesList[assetId]); + assertNotEq(_asset, address(0)); assertEq(_useAsCollateral, useAsCollateral); } @@ -249,12 +267,13 @@ contract CalldataLogicTest is TestHelper { } function testDecodeLiquidationCallParams( - uint16 collateralAssetId, - uint16 debtAssetId, + uint8 collateralAssetId, + uint8 debtAssetId, address user, uint128 debtToCover, bool receiveAToken ) public { + VM.assume(collateralAssetId < RESERVE_COUNT && debtAssetId < RESERVE_COUNT); DecodeLiquidationCallParamsHelper memory vars; bytes32 args1; @@ -276,7 +295,9 @@ contract CalldataLogicTest is TestHelper { ) = 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/TestHelper.sol b/src/test/TestHelper.sol index b801c1b0d..50b19e658 100644 --- a/src/test/TestHelper.sol +++ b/src/test/TestHelper.sol @@ -4,6 +4,15 @@ pragma solidity 0.8.10; import 'ds-test/test.sol'; contract TestHelper is DSTest { + function assertNotEq(address a, address b) internal { + if (a == b) { + emit log('Error: a != b not satisfied [address]'); + emit log_named_address(' Expected', b); + emit log_named_address(' Actual', a); + fail(); + } + } + function assertEq(bool a, bool b) internal { if (a != b) { emit log('Error: a == b not satisfied [bool]'); From e812718117782d0d36a5977b139e343dd02dcfa5 Mon Sep 17 00:00:00 2001 From: Lasse Herskind Date: Mon, 7 Mar 2022 17:19:19 +0000 Subject: [PATCH 12/12] fix: Replace `assume` with bounds in ReserveConfiguration. --- src/test/ReserveConfiguration.t.sol | 103 ++++++++++++++++++---------- src/test/TestHelper.sol | 26 +++++++ 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/src/test/ReserveConfiguration.t.sol b/src/test/ReserveConfiguration.t.sol index c7431c122..ff6739367 100644 --- a/src/test/ReserveConfiguration.t.sol +++ b/src/test/ReserveConfiguration.t.sol @@ -13,10 +13,10 @@ import {TestHelper} from './TestHelper.sol'; contract ReserveConfigurationTest is TestHelper { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; -Vm constant VM = Vm(HEVM_ADDRESS); + Vm constant VM = Vm(HEVM_ADDRESS); function testSetLtv(uint256 data, uint256 ltv) public { - VM.assume(ltv <= ReserveConfiguration.MAX_VALID_LTV); + ltv = bound(ltv, 0, type(uint16).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: data & ReserveConfiguration.LTV_MASK }); @@ -25,7 +25,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetLtvTooHigh(uint256 data, uint256 ltv) public { - VM.assume(ltv > ReserveConfiguration.MAX_VALID_LTV); + ltv = bound(ltv, ReserveConfiguration.MAX_VALID_LTV + 1, type(uint256).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: data & ReserveConfiguration.LTV_MASK }); @@ -34,8 +34,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetLtv(uint256 data, uint256 ltv) public { - // Notice that the mask may support values that is significantly larger than the max value. - VM.assume(ltv <= type(uint16).max); + ltv = bound(ltv, 0, type(uint16).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LTV_MASK) | ltv }); @@ -43,8 +42,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetLiquidationThreshold(uint256 data, uint256 threshold) public { - VM.assume(threshold <= ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD); - + threshold = bound(threshold, 0, type(uint16).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) }); @@ -59,7 +57,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetLiquidationThreshold(uint256 data, uint256 threshold) public { - VM.assume(threshold > ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD); + threshold = bound( + threshold, + ReserveConfiguration.MAX_VALID_LIQUIDATION_THRESHOLD + 1, + type(uint256).max + ); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LIQUIDATION_THRESHOLD_MASK) @@ -70,7 +72,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetLiquidationThreshold(uint256 data, uint256 threshold) public { - VM.assume(threshold <= type(uint16).max); + 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) @@ -79,8 +81,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetLiquidationBonus(uint256 data, uint256 bonus) public { - VM.assume(bonus <= ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); - + bonus = bound(bonus, 0, ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) }); @@ -95,7 +96,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetLiquidationBonus(uint256 data, uint256 bonus) public { - VM.assume(bonus > ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS); + bonus = bound(bonus, ReserveConfiguration.MAX_VALID_LIQUIDATION_BONUS + 1, type(uint256).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LIQUIDATION_BONUS_MASK) @@ -106,7 +107,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetLiquidationBonus(uint256 data, uint256 bonus) public { - VM.assume(bonus <= type(uint16).max); + 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) @@ -115,7 +116,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetDecimals(uint256 data, uint256 decimals) public { - VM.assume(decimals <= type(uint8).max); + decimals = bound(decimals, 0, ReserveConfiguration.MAX_VALID_DECIMALS); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.DECIMALS_MASK) }); @@ -130,7 +131,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetDecimals(uint256 data, uint256 decimals) public { - VM.assume(decimals > type(uint8).max); + decimals = bound(decimals, ReserveConfiguration.MAX_VALID_DECIMALS + 1, type(uint256).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.DECIMALS_MASK) }); @@ -139,7 +140,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetDecimals(uint256 data, uint256 decimals) public { - VM.assume(decimals <= type(uint8).max); + 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) @@ -302,7 +303,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetReserveFactor(uint256 data, uint256 reserveFactor) public { - VM.assume(reserveFactor <= ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); + reserveFactor = bound(reserveFactor, 0, ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) }); @@ -317,7 +318,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetReserveFactor(uint256 data, uint256 reserveFactor) public { - VM.assume(reserveFactor > ReserveConfiguration.MAX_VALID_RESERVE_FACTOR); + reserveFactor = bound( + reserveFactor, + ReserveConfiguration.MAX_VALID_RESERVE_FACTOR + 1, + type(uint256).max + ); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.RESERVE_FACTOR_MASK) }); @@ -326,7 +331,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetReserveFactor(uint256 data, uint256 reserveFactor) public { - VM.assume(reserveFactor <= type(uint16).max); + 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) @@ -335,7 +340,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetBorrowCap(uint256 data, uint256 borrowCap) public { - VM.assume(borrowCap <= ReserveConfiguration.MAX_VALID_BORROW_CAP); + borrowCap = bound(borrowCap, 0, ReserveConfiguration.MAX_VALID_BORROW_CAP); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.BORROW_CAP_MASK) }); @@ -350,7 +355,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetBorrowCap(uint256 data, uint256 borrowCap) public { - VM.assume(borrowCap > ReserveConfiguration.MAX_VALID_BORROW_CAP); + borrowCap = bound(borrowCap, ReserveConfiguration.MAX_VALID_BORROW_CAP + 1, type(uint256).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.BORROW_CAP_MASK) }); @@ -359,7 +364,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetBorrowCap(uint256 data, uint256 borrowCap) public { - VM.assume(borrowCap < 2**36); + 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) @@ -368,7 +373,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetSupplyCap(uint256 data, uint256 supplyCap) public { - VM.assume(supplyCap <= ReserveConfiguration.MAX_VALID_SUPPLY_CAP); + supplyCap = bound(supplyCap, 0, ReserveConfiguration.MAX_VALID_SUPPLY_CAP); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) }); @@ -383,7 +388,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetSupplyCap(uint256 data, uint256 supplyCap) public { - VM.assume(supplyCap > ReserveConfiguration.MAX_VALID_SUPPLY_CAP); + supplyCap = bound(supplyCap, ReserveConfiguration.MAX_VALID_SUPPLY_CAP + 1, type(uint256).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.SUPPLY_CAP_MASK) }); @@ -392,7 +397,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetSupplyCap(uint256 data, uint256 supplyCap) public { - VM.assume(supplyCap < 2**36); + 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) @@ -401,7 +406,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetDebtCeiling(uint256 data, uint256 debtCeiling) public { - VM.assume(debtCeiling <= ReserveConfiguration.MAX_VALID_DEBT_CEILING); + debtCeiling = bound(debtCeiling, 0, ReserveConfiguration.MAX_VALID_DEBT_CEILING); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.DEBT_CEILING_MASK) }); @@ -416,7 +421,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetDebtCeiling(uint256 data, uint256 debtCeiling) public { - VM.assume(debtCeiling > ReserveConfiguration.MAX_VALID_DEBT_CEILING); + debtCeiling = bound( + debtCeiling, + ReserveConfiguration.MAX_VALID_DEBT_CEILING + 1, + type(uint256).max + ); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.DEBT_CEILING_MASK) }); @@ -425,7 +434,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetDebtCeiling(uint256 data, uint256 debtCeiling) public { - VM.assume(debtCeiling < 2**36); + 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) @@ -434,7 +443,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public { - VM.assume(liquidationProtocolFee <= ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE); + liquidationProtocolFee = bound( + liquidationProtocolFee, + 0, + ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE + ); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.LIQUIDATION_PROTOCOL_FEE_MASK) }); @@ -449,7 +462,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public { - VM.assume(liquidationProtocolFee > ReserveConfiguration.MAX_VALID_LIQUIDATION_PROTOCOL_FEE); + 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) }); @@ -458,7 +475,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetLiquidationProtocolFee(uint256 data, uint256 liquidationProtocolFee) public { - VM.assume(liquidationProtocolFee < type(uint16).max); + 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) @@ -467,7 +488,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public { - VM.assume(unbackedMintCap <= ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); + unbackedMintCap = bound(unbackedMintCap, 0, ReserveConfiguration.MAX_VALID_UNBACKED_MINT_CAP); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.UNBACKED_MINT_CAP_MASK) }); @@ -482,6 +503,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetUnbackedMintCap(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) @@ -491,7 +517,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetUnbackedMintCap(uint256 data, uint256 unbackedMintCap) public { - VM.assume(unbackedMintCap < type(uint16).max); + 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) @@ -500,7 +526,8 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testSetEModeCategory(uint256 data, uint256 category) public { - VM.assume(category <= ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + category = bound(category, 0, ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + uint256 category = bound(category, 0, type(uint8).max); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) }); @@ -515,7 +542,11 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testFailSetEModeCategory(uint256 data, uint256 category) public { - VM.assume(category > ReserveConfiguration.MAX_VALID_EMODE_CATEGORY); + category = bound( + category, + ReserveConfiguration.MAX_VALID_EMODE_CATEGORY + 1, + type(uint256).max + ); DataTypes.ReserveConfigurationMap memory config = DataTypes.ReserveConfigurationMap({ data: (data & ReserveConfiguration.EMODE_CATEGORY_MASK) }); @@ -524,7 +555,7 @@ Vm constant VM = Vm(HEVM_ADDRESS); } function testGetEModeCategory(uint256 data, uint256 category) public { - VM.assume(category < type(uint8).max); + 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) diff --git a/src/test/TestHelper.sol b/src/test/TestHelper.sol index 50b19e658..10b08e60d 100644 --- a/src/test/TestHelper.sol +++ b/src/test/TestHelper.sol @@ -41,4 +41,30 @@ contract TestHelper is DSTest { } return counter; } + + /// From https://github.com/Rari-Capital/solmate/blob/67d907c82f50649f8168061dcfae8617110da361/src/test/utils/DSTestPlus.sol#L116-L135 + function bound( + uint256 x, + uint256 min, + uint256 max + ) internal pure returns (uint256 result) { + require(max >= min, 'MAX_LESS_THAN_MIN'); + + uint256 size = max - min; + + if (max != type(uint256).max) size++; // Make the max inclusive. + if (size == 0) return min; // Using max would be equivalent as well. + // Ensure max is inclusive in cases where x != 0 and max is at uint max. + if (max == type(uint256).max && x != 0) x--; // Accounted for later. + + if (x < min) x += size * (((min - x) / size) + 1); + result = min + ((x - min) % size); + + // Account for decrementing x to make max inclusive. + if (max == type(uint256).max && x != 0) result++; + } + + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } }