sp-arithmetic: add checked functions and extend fuzzer coverage #8472
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
This has been in my backlog for quite a while (in a simpler form, see), Finally got it in the shape I wanted for peer review and a few pair of eyes on the TO TO/RESOLVE pieces.
In addition to the implementation and changes, I have done a Security review on the whole sp-arithmetic crate. Once this is merged, it can be marked as audited (completely) and I am fine to review future changes in the crate to do a differential security review and maintain the status of fully audited as time goes.
Closes: #1936
Integration
This PR adds more functions than it changes (removed nothing), once merged checked functions should be used instead of non checked ones when possible. The items in TO TODO/RESOLVE might introduce integration changes, I will update here once it is decided.
IMPLEMENTED
helpers_128bit.rs
:checked_multiply_by_rational_with_rounding
.ArithmeticError
.per_things.rs
:checked_*
versions ofsquare
,mul_floor
,mul_ceil
,overflow_prune_mul
,rational_mul_correction
saturating_reciprocal_mul
,saturating_reciprocal_mul_floor
,saturating_reciprocal_mul_ceil
andfrom_rational_with_rounding
to thePerThing
trait and their implementations withinimplement_per_thing
the macro where it applies.checked_int_div
andchecked_div_with_rounding
.checked_*
additions inchecked_reciprocal_mul_works
,checked_from_rational_works
,checked_arithmetic_works
andchecked_int_div_works
.ArithmeticError
.fixed_point.rs
:try_into_perbill
which is a checked version ofinto_perbill
.from_rational_with_rounding
.ArithmeticError
.rational.rs
:checked_to_den
andchecked_lcm
.checked_add
andchecked_sub
to follow the pattern of other checked versions.helpers_128bit
.ArithmeticError
.fuzzing
:per_thing_checked_arith.rs
: Covers generalPerThing
checked arithmetic operations (checked_mul_floor
,checked_mul_ceil
, reciprocal multiplications,checked_square
,checked_div_with_rounding
,checked_int_div
).fixed_utils.rs
: CoversFixedPointNumber
utility functions (trunc
,frac
,ceil
,floor
,round
).fixed_to_perbill.rs
: Covers conversions oftry_into_perbill
fixed_to_perthing.rs
: Covers conversions oftry_into_perthing
.fixed_rounding_ops.rs
: Specifically fuzzesconst_checked_mul_with_rounding
andchecked_rounding_div
forFixedPointNumber
types, testing all 8SignedRounding
modes.per_thing_checked_from_rational.rs
: CoversPerThing::checked_from_rational_with_rounding
comparing against the non-checked version and validating error handling.fixed_checked_ops.rs
: Covers coreFixedPointNumber
checked arithmetic operations (add
,sub
,mul
,div
,from_integer
,from_rational
,mul_int
,div_int
,sqrt
) forFixedI64
,FixedU64
,FixedI128
, andFixedU128
, usingBigInt
for oracle comparisons and overflow detection.checked_mul_by_rational.rs
: Covershelpers_128bit::checked_multiply_by_rational_with_rounding
with different rounding modes and error conditions (DivisionByZero, Overflow).Review Notes
I have introduced multiple checked functions in the main library where the was a risk/clarity of panic, as well as proper use of
ArithmeticError
and consistent import namespaces. The new checked functions are not complex as they should mimic the same behavior, the difference is relaying on safe functions and do not panic.You will see in
per_thing_checked_arith.rs
an alternative approach I decided to try, and is the use of an "oracle" to tell what the expected outcome of an operation should be. Fuzzing is great at finding inputs that cause crashes (panics), but it doesn't inherently know if the result of a calculation is correct, especially for complex math. I think is important to be confident that they produce the correct numerical results according to mathematical rules and the specified rounding behavior.I decided to use
BigFraction
/BigInt
as they provide independent implementations of arbitrary-precision arithmetic, they aren't bound by the fixed precision or potential overflow limits of theu128
,FixedU64
,Permill
, etc., types we are testing, I believe this would let compute most of the values with high-precision (I might be wrong and this backfires to me in the future :D ).In older test, we test mathematical properties (like
(a/b)*b <= a
when rounding down, as seen inper_thing_mult_fraction.rs
), this is also valuable but doesn't verify the exact output value like an oracle does which sometimes is important to test against. Infixed_checked_ops.rs
the use of oracle paradigm is used as well, however, to properly test the behavior of each function requires more thought on the cases and vertices. I consider the tests infixed_checked_ops.rs
as a baseline and to be improved as time goes, left comments for future references.TO TODO/RESOLVE
fixed_point.rs
:checked_from_rational
,checked_mul_int
,checked_div
,checked_mul
can be changed tochecked_multiply_by_rational_with_rounding
version but it means changing the return fromOption<>
toResult<>
, or keep backwards compability and wrap theResult<>
back toOption<>
.per_things.rs
:checked_reciprocal_mul_works
andchecked_arithmetic_works
fail only in PerU16 variations. I need to figure out why exactly is that happening only for this variant. This is the test output in case someone has an idea:Checklist
T
required)