From cceb7f618c4d0c17cc834622ba8bd708a38951ad Mon Sep 17 00:00:00 2001 From: 0x2me Date: Mon, 6 May 2024 15:39:13 +0100 Subject: [PATCH] Threshold check (#229) * fix: threshold check in SD59x18 exp2 function * refactor: alphabetical ordering docs: improve writing refactor: change order of checks style: fix formatting issues test: order by value test: use MIN_SD59x18 * fix: Update threshold check in SD59x18 exp function, add EXP_MIN_THRESHOLD constant and add tests * fix: increase max_test_rejects to 100_000 * docs: improve explanation --------- Co-authored-by: Paul Razvan Berg --- foundry.toml | 2 +- src/sd59x18/Constants.sol | 8 ++++++++ src/sd59x18/Math.sol | 12 ++++++++++-- test/unit/sd59x18/math/exp/exp.t.sol | 10 +++++----- test/unit/sd59x18/math/exp2/exp2.t.sol | 11 +++++------ 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/foundry.toml b/foundry.toml index d5ef26cf..7b2d35af 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,7 +13,7 @@ test = "test" [profile.ci] - fuzz = { runs = 10_000 } + fuzz = { runs = 10_000, max_test_rejects = 100_000 } verbosity = 4 [fmt] diff --git a/src/sd59x18/Constants.sol b/src/sd59x18/Constants.sol index 94d4603b..ec24e62f 100644 --- a/src/sd59x18/Constants.sol +++ b/src/sd59x18/Constants.sol @@ -12,10 +12,18 @@ SD59x18 constant E = SD59x18.wrap(2_718281828459045235); int256 constant uEXP_MAX_INPUT = 133_084258667509499440; SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT); +/// @dev Any value less than this returns 0 in {exp}. +int256 constant uEXP_MIN_THRESHOLD = -41_446531673892822322; +SD59x18 constant EXP_MIN_THRESHOLD = SD59x18.wrap(uEXP_MIN_THRESHOLD); + /// @dev The maximum input permitted in {exp2}. int256 constant uEXP2_MAX_INPUT = 192e18 - 1; SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT); +/// @dev Any value less than this returns 0 in {exp2}. +int256 constant uEXP2_MIN_THRESHOLD = -59_794705707972522261; +SD59x18 constant EXP2_MIN_THRESHOLD = SD59x18.wrap(uEXP2_MIN_THRESHOLD); + /// @dev Half the UNIT number. int256 constant uHALF_UNIT = 0.5e18; SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT); diff --git a/src/sd59x18/Math.sol b/src/sd59x18/Math.sol index 3fd3fa7b..361838c4 100644 --- a/src/sd59x18/Math.sol +++ b/src/sd59x18/Math.sol @@ -6,6 +6,8 @@ import "./Errors.sol" as Errors; import { uEXP_MAX_INPUT, uEXP2_MAX_INPUT, + uEXP_MIN_THRESHOLD, + uEXP2_MIN_THRESHOLD, uHALF_UNIT, uLOG2_10, uLOG2_E, @@ -168,6 +170,12 @@ function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) { function exp(SD59x18 x) pure returns (SD59x18 result) { int256 xInt = x.unwrap(); + // Any input less than the threshold returns zero. + // This check also prevents an overflow for very small numbers. + if (xInt < uEXP_MIN_THRESHOLD) { + return ZERO; + } + // This check prevents values greater than 192e18 from being passed to {exp2}. if (xInt > uEXP_MAX_INPUT) { revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x); @@ -201,8 +209,8 @@ function exp(SD59x18 x) pure returns (SD59x18 result) { function exp2(SD59x18 x) pure returns (SD59x18 result) { int256 xInt = x.unwrap(); if (xInt < 0) { - // The inverse of any number less than this is truncated to zero. - if (xInt < -59_794705707972522261) { + // The inverse of any number less than the threshold is truncated to zero. + if (xInt < uEXP2_MIN_THRESHOLD) { return ZERO; } diff --git a/test/unit/sd59x18/math/exp/exp.t.sol b/test/unit/sd59x18/math/exp/exp.t.sol index 401c9b6d..e1b12cf2 100644 --- a/test/unit/sd59x18/math/exp/exp.t.sol +++ b/test/unit/sd59x18/math/exp/exp.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.19 <0.9.0; import { sd } from "src/sd59x18/Casting.sol"; -import { E, EXP_MAX_INPUT, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol"; +import { E, EXP_MAX_INPUT, EXP_MIN_THRESHOLD, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol"; import { PRBMath_SD59x18_Exp_InputTooBig } from "src/sd59x18/Errors.sol"; import { exp } from "src/sd59x18/Math.sol"; import { SD59x18 } from "src/sd59x18/ValueType.sol"; @@ -10,8 +10,6 @@ import { SD59x18 } from "src/sd59x18/ValueType.sol"; import { SD59x18_Unit_Test } from "../../SD59x18.t.sol"; contract Exp_Unit_Test is SD59x18_Unit_Test { - SD59x18 internal constant THRESHOLD = SD59x18.wrap(-41_446531673892822322); - function test_Exp_Zero() external pure { SD59x18 x = ZERO; SD59x18 actual = exp(x); @@ -27,7 +25,7 @@ contract Exp_Unit_Test is SD59x18_Unit_Test { delete sets; sets.push(set({ x: MIN_SD59x18 })); sets.push(set({ x: MIN_WHOLE_SD59x18 })); - sets.push(set({ x: THRESHOLD - sd(1) })); + sets.push(set({ x: EXP_MIN_THRESHOLD - sd(1) })); return sets; } @@ -38,7 +36,9 @@ contract Exp_Unit_Test is SD59x18_Unit_Test { function negativeAndGteThreshold_Sets() internal returns (Set[] memory) { delete sets; - sets.push(set({ x: THRESHOLD, expected: 0.000000000000000001e18 })); + sets.push(set({ x: MIN_SD59x18, expected: 0 })); + sets.push(set({ x: EXP_MIN_THRESHOLD - sd(1), expected: 0 })); + sets.push(set({ x: EXP_MIN_THRESHOLD, expected: 0.000000000000000001e18 })); sets.push(set({ x: -33.333333e18, expected: 0.000000000000003338e18 })); sets.push(set({ x: -20.82e18, expected: 0.0000000009077973e18 })); sets.push(set({ x: -16e18, expected: 0.000000112535174719e18 })); diff --git a/test/unit/sd59x18/math/exp2/exp2.t.sol b/test/unit/sd59x18/math/exp2/exp2.t.sol index a79450c0..69b58fc8 100644 --- a/test/unit/sd59x18/math/exp2/exp2.t.sol +++ b/test/unit/sd59x18/math/exp2/exp2.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.19 <0.9.0; import { sd } from "src/sd59x18/Casting.sol"; -import { E, EXP2_MAX_INPUT, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol"; +import { E, EXP2_MAX_INPUT, EXP2_MIN_THRESHOLD, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol"; import { PRBMath_SD59x18_Exp2_InputTooBig } from "src/sd59x18/Errors.sol"; import { exp2 } from "src/sd59x18/Math.sol"; import { SD59x18 } from "src/sd59x18/ValueType.sol"; @@ -10,9 +10,6 @@ import { SD59x18 } from "src/sd59x18/ValueType.sol"; import { SD59x18_Unit_Test } from "../../SD59x18.t.sol"; contract Exp2_Unit_Test is SD59x18_Unit_Test { - /// @dev Any input smaller than this makes the result zero. - SD59x18 internal constant THRESHOLD = SD59x18.wrap(-59_794705707972522261); - function test_Exp2_Zero() external pure { SD59x18 x = ZERO; SD59x18 actual = exp2(x); @@ -28,7 +25,7 @@ contract Exp2_Unit_Test is SD59x18_Unit_Test { delete sets; sets.push(set({ x: MIN_SD59x18, expected: 0 })); sets.push(set({ x: MIN_WHOLE_SD59x18, expected: 0 })); - sets.push(set({ x: THRESHOLD - sd(1), expected: 0 })); + sets.push(set({ x: EXP2_MIN_THRESHOLD - sd(1), expected: 0 })); return sets; } @@ -39,7 +36,9 @@ contract Exp2_Unit_Test is SD59x18_Unit_Test { function negativeAndGteMinPermitted_Sets() internal returns (Set[] memory) { delete sets; - sets.push(set({ x: THRESHOLD, expected: 0.000000000000000001e18 })); + sets.push(set({ x: MIN_SD59x18, expected: 0 })); + sets.push(set({ x: EXP2_MIN_THRESHOLD - sd(1), expected: 0 })); + sets.push(set({ x: EXP2_MIN_THRESHOLD, expected: 0.000000000000000001e18 })); sets.push(set({ x: -33.333333e18, expected: 0.000000000092398923e18 })); sets.push(set({ x: -20.82e18, expected: 0.000000540201132438e18 })); sets.push(set({ x: -16e18, expected: 0.0000152587890625e18 }));