// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {DSTest} from "ds-test/test.sol";
import {Hevm} from "./Hevm.sol";
/// @notice Extended testing framework for DappTools projects.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/test/utils/DSTestPlus.sol)
contract DSTestPlus is DSTest {
Hevm internal constant hevm = Hevm(HEVM_ADDRESS);
address internal constant DEAD_ADDRESS = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF;
string private checkpointLabel;
uint256 private checkpointGasLeft = 1; // Start the slot warm.
modifier brutalizeMemory(bytes memory brutalizeWith) {
/// @solidity memory-safe-assembly
assembly {
// Fill the 64 bytes of scratch space with the data.
pop(
staticcall(
gas(), // Pass along all the gas in the call.
0x04, // Call the identity precompile address.
brutalizeWith, // Offset is the bytes' pointer.
64, // Copy enough to only fill the scratch space.
0, // Store the return value in the scratch space.
64 // Scratch space is only 64 bytes in size, we don't want to write further.
)
)
let size := add(mload(brutalizeWith), 32) // Add 32 to include the 32 byte length slot.
// Fill the free memory pointer's destination with the data.
pop(
staticcall(
gas(), // Pass along all the gas in the call.
0x04, // Call the identity precompile address.
brutalizeWith, // Offset is the bytes' pointer.
size, // We want to pass the length of the bytes.
mload(0x40), // Store the return value at the free memory pointer.
size // Since the precompile just returns its input, we reuse size.
)
)
}
_;
}
function startMeasuringGas(string memory label) internal virtual {
checkpointLabel = label;
checkpointGasLeft = gasleft();
}
function stopMeasuringGas() internal virtual {
uint256 checkpointGasLeft2 = gasleft();
// Subtract 100 to account for the warm SLOAD in startMeasuringGas.
uint256 gasDelta = checkpointGasLeft - checkpointGasLeft2 - 100;
emit log_named_uint(string(abi.encodePacked(checkpointLabel, " Gas")), gasDelta);
}
function fail(string memory err) internal virtual {
emit log_named_string("Error", err);
fail();
}
function assertFalse(bool data) internal virtual {
assertTrue(!data);
}
function assertUint128Eq(uint128 a, uint128 b) internal virtual {
assertEq(uint256(a), uint256(b));
}
function assertUint64Eq(uint64 a, uint64 b) internal virtual {
assertEq(uint256(a), uint256(b));
}
function assertUint96Eq(uint96 a, uint96 b) internal virtual {
assertEq(uint256(a), uint256(b));
}
function assertUint32Eq(uint32 a, uint32 b) internal virtual {
assertEq(uint256(a), uint256(b));
}
function assertBoolEq(bool a, bool b) internal virtual {
b ? assertTrue(a) : assertFalse(a);
}
function assertApproxEq(
uint256 a,
uint256 b,
uint256 maxDelta
) internal virtual {
uint256 delta = a > b ? a - b : b - a;
if (delta > maxDelta) {
emit log("Error: a ~= b not satisfied [uint]");
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
emit log_named_uint(" Max Delta", maxDelta);
emit log_named_uint(" Delta", delta);
fail();
}
}
function assertRelApproxEq(
uint256 a,
uint256 b,
uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100%
) internal virtual {
if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too.
uint256 percentDelta = ((a > b ? a - b : b - a) * 1e18) / b;
if (percentDelta > maxPercentDelta) {
emit log("Error: a ~= b not satisfied [uint]");
emit log_named_uint(" Expected", b);
emit log_named_uint(" Actual", a);
emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18);
emit log_named_decimal_uint(" % Delta", percentDelta, 18);
fail();
}
}
function assertBytesEq(bytes memory a, bytes memory b) internal virtual {
if (keccak256(a) != keccak256(b)) {
emit log("Error: a == b not satisfied [bytes]");
emit log_named_bytes(" Expected", b);
emit log_named_bytes(" Actual", a);
fail();
}
}
function assertUintArrayEq(uint256[] memory a, uint256[] memory b) internal virtual {
require(a.length == b.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < a.length; i++) {
assertEq(a[i], b[i]);
}
}
function bound(
uint256 x,
uint256 min,
uint256 max
) internal virtual returns (uint256 result) {
require(max >= min, "MAX_LESS_THAN_MIN");
uint256 size = max - min;
if (size == 0) result = min;
else if (size == type(uint256).max) result = x;
else {
++size; // Make max inclusive.
uint256 mod = x % size;
result = min + mod;
}
emit log_named_uint("Bound Result", result);
}
function min3(
uint256 a,
uint256 b,
uint256 c
) internal pure returns (uint256) {
return a > b ? (b > c ? c : b) : (a > c ? c : a);
}
function min2(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? b : a;
}
}