Стремясь к 100% охвату ветвей, я испытываю некоторые трудности при тестировании обоих условий проверки диапазона:
// Find the matching tier
for (uint256 i = 0; i < tiers.length; i++) {
if (tiers[i].minVal() <= cumReceivedInvestments && cumReceivedInvestments < tiers[i].maxVal()) {
return tiers[i];
}
}
Как показывает LCOV:
Чтобы проверить эти условия, я попробовал:
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.23 <0.9.0;
import { PRBTest } from "@prb/test/src/PRBTest.sol";
import { StdCheats } from "forge-std/src/StdCheats.sol";
import { DecentralisedInvestmentHelper } from "../../src/Helper.sol";
import { TierInvestment } from "../../src/TierInvestment.sol";
import { Tier } from "../../src/Tier.sol";
contract HelperTest is PRBTest, StdCheats {
TierInvestment internal validTierInvestment;
uint256 private cumReceivedInvestments;
Tier[] private _tiers;
Tier[] private someTiers;
DecentralisedInvestmentHelper private _helper;
/// @dev A function invoked before each test case is run.
function setUp() public virtual {
cumReceivedInvestments = 5;
// Specify the investment tiers in ether.
uint256 firstTierCeiling = 4 ether;
uint256 secondTierCeiling = 15 ether;
uint256 thirdTierCeiling = 30 ether;
// Start lowst tier at 2 wei, such that the tested cumulative investment
//amount can go below that at 1 wei.
Tier tier_0 = new Tier(2, firstTierCeiling, 10);
_tiers.push(tier_0);
Tier tier_1 = new Tier(firstTierCeiling, secondTierCeiling, 5);
_tiers.push(tier_1);
Tier tier_2 = new Tier(secondTierCeiling, thirdTierCeiling, 2);
_tiers.push(tier_2);
// Initialise contract helper.
_helper = new DecentralisedInvestmentHelper();
}
function testExceedInvestmentCeiling() public {
// vm.prank(address(validTierInvestment));
vm.expectRevert(bytes("The investment ceiling is reached."));
_helper.computeCurrentInvestmentTier(30 ether + 1 wei, _tiers);
}
function testNegativeInvestment() public {
// vm.prank(address(validTierInvestment));
vm.expectRevert(
bytes(
"Unexpected state: No matching tier found, the lowest investment tier starting point was larger than the cumulative received investments. All (Tier) arrays should start at 0."
)
);
_helper.computeCurrentInvestmentTier(1 wei, _tiers);
}
function testCanInvestInNextTier() public {
// True True for tier 0.
assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).multiple(), 10);
// False False for tier 0.
assertEq(_helper.computeCurrentInvestmentTier(10 ether + 1 wei, _tiers).multiple(), 5);
// Hits investment ceiling
// assertEq(_helper.computeCurrentInvestmentTier(30 ether+1 wei, _tiers).multiple(), 2);
// True True for tier 0, True True for tier 1 but tier 1 is not reached.,
assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).multiple(), 10);
// Hits investment ceiling before this can reach Tier 0.
// False True for tier 0, True True for tier 1
// assertEq(_helper.computeCurrentInvestmentTier(1 wei, _tiers).multiple(), 10);
}
function testGetRemainingAmountInCurrentTierBelow() public {
vm.expectRevert(bytes("Error: Tier's minimum value exceeds received investments."));
_helper.getRemainingAmountInCurrentTier(1 wei, _tiers[0]);
}
function testGetRemainingAmountInCurrentTierAbove() public {
vm.expectRevert(bytes("Error: Tier's maximum value is not larger than received investments."));
_helper.getRemainingAmountInCurrentTier(4 ether + 1 wei, _tiers[0]);
}
function testComputeRemainingInvestorPayoutNegativeFraction() public {
vm.expectRevert(bytes("investorFracNumerator is smaller than investorFracDenominator."));
_helper.computeRemainingInvestorPayout(0, 1, 0, 0);
}
}
Я считаю, что это подтверждает ситуацию:
tiers[i].minVal() <= cumReceivedInvestments = false
cumReceivedInvestments < tiers[i].maxVal() = true
для: tier 0 и cumReceivedInvestments = 10 ether+1 wei.
tiers[i].minVal() <= cumReceivedInvestments = true
cumReceivedInvestments < tiers[i].maxVal() = true
для: tier 0 и cumReceivedInvestments = 2 wei.
tiers[i].minVal() <= cumReceivedInvestments = false
cumReceivedInvestments < tiers[i].maxVal() = false
для: tier 0 и cumReceivedInvestments = 10+1 wei.
Однако я не думаю, что смогу осознать:
tiers[i].minVal() <= cumReceivedInvestments = true
cumReceivedInvestments < tiers[i].maxVal() = false
потому что объект Tier требует, чтобы maxVal было больше, чем minVal, поэтому, если первая строка истинна, по сути, cumReceivedInvestments < tiers[i].maxVal() также верна.
Я подумал, что если я выделю эту логику в отдельную функцию и брошу в нее все тестовые примеры, то смогу охватить все случаи. Это был не тот случай, и я думаю, что я неправильно предположил, что это тот случай, который я не могу осознать. Возможно, LCOV говорит, что по какой-то причине он не покрыт из-за цикла for. Для полноты вот отдельная функция:
function isInRange(uint256 minVal, uint256 maxVal, uint256 someVal) public view override returns (bool inRange) {
if (minVal <= someVal && someVal < maxVal) {
inRange = true;
} else {
inRange = false;
}
return inRange;
}
function computeCurrentInvestmentTier(
uint256 cumReceivedInvestments,
Tier[] memory tiers
) public view override returns (Tier currentTier) {
// Check for exceeding investment ceiling.
if (hasReachedInvestmentCeiling(cumReceivedInvestments, tiers)) {
revert ReachedInvestmentCeiling(cumReceivedInvestments, "Investment ceiling is reached.");
}
// Find the matching tier
uint256 nrOfTiers = tiers.length;
for (uint256 i = 0; i < nrOfTiers; ++i) {
if (isInRange(tiers[i].getMinVal(), tiers[i].getMaxVal(), cumReceivedInvestments)) {
currentTier = tiers[i];
return currentTier;
}
}
// Should not reach here with valid tiers
revert(
string(
abi.encodePacked(
"Unexpected state: No matching tier found, the lowest ",
"investment tier starting point was larger than the ",
"cumulative received investments. All (Tier) arrays should start at 0."
)
)
);
}
и протестируйте:
function testCanInvestInNextTier() public override {
// True True for tier 0.
assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).getMultiple(), 10);
assertTrue(_helper.isInRange(1, 3, 2));
// False False for tier 0.
assertEq(_helper.computeCurrentInvestmentTier(10 ether + 1 wei, _tiers).getMultiple(), 5);
assertFalse(_helper.isInRange(1, 2, 4));
// Hits investment ceiling
// assertEq(_helper.computeCurrentInvestmentTier(30 ether+1 wei, _tiers).getMultiple(), 2);
// True True for tier 0, True True for tier 1 but tier 1 is not reached.,
assertEq(_helper.computeCurrentInvestmentTier(2 wei, _tiers).getMultiple(), 10);
assertFalse(_helper.isInRange(1, 2, 0));
assertFalse(_helper.isInRange(3, 2, 1));
// Hits investment ceiling before this can reach Tier 0.
// False True for tier 0, True True for tier 1
// assertEq(_helper.computeCurrentInvestmentTier(1 wei, _tiers).getMultiple(), 10);
}
И сопровождающий отчет LCOV:
Предполагая, что мне нужно 100%-ное покрытие тестами, как мне решить эту проблему? Например, следует ли мне изменить условие if, следует ли мне переместить или изменить оператор require, или мой анализ неверен, и можно ли проверить все 4 условия условия True && False или, возможно, что-то еще?
Для полноты вот полная тестируемая функция:
function hasReachedInvestmentCeiling(uint256 cumReceivedInvestments, Tier[] memory tiers) public view returns (bool) {
return cumReceivedInvestments >= getInvestmentCeiling(tiers);
}
function computeCurrentInvestmentTier(
uint256 cumReceivedInvestments,
Tier[] memory tiers
) public view returns (Tier) {
// Check for exceeding investment ceiling.
require(!hasReachedInvestmentCeiling(cumReceivedInvestments, tiers), "The investment ceiling is reached.");
// Find the matching tier
for (uint256 i = 0; i < tiers.length; i++) {
if (tiers[i].minVal() <= cumReceivedInvestments && cumReceivedInvestments < tiers[i].maxVal()) {
return tiers[i];
}
}
// Should not reach here with valid tiers
revert(
"Unexpected state: No matching tier found, the lowest investment tier starting point was larger than the cumulative received investments. All (Tier) arrays should start at 0."
);
}





Вы можете переписать это как цикл while и затем выполнить две отдельные проверки. Я предполагаю, что массив уровней отсортирован и исправен, т. е. уровни не перекрываются и между ними нет дыр.
Для завершения цикла вы проверяете минимум, а для приращения — максимум.
Я не знаком с этим языком Sol, но он похож на C, поэтому я бы предложил что-то вроде этого:
uint256 i = 0;
while(tiers[i].minVal() <= cumReceivedInvestments) {
if (cumReceivedInvestments < tiers[i].maxVal()) {
i++;
}
}
return tiers[i];
Таким образом, каждая ветвь будет протестирована, и код по-прежнему достигнет той же цели.