Closes #725
Adds the panic-free sat_* family to credence_math so UX/aggregation paths can never revert a transaction on an internal i128 * bps overflow. The new helpers clamp to i128::MIN / i128::MAX on overflow and silently return 0 when the denominator is zero. Drive-by also fixes six pre-existing compile defects in credence_errors that the new math→errors dep edge surfaced (see ### Drive-by: credence_errors section for the full list and the two wire-code renumberings).
pub const PERCENT_DENOMINATOR: i128 = 100;(paired with the existingBPS_DENOMINATOR = 10_000).- Panic-free
sat_*family (24/hour ops, dashboards, off-chain scoring):sat_mul_div_i128(a, b, denom, mode) -> i128—#[inline],# Examplesdoctest that locks in saturation +denom == 0 → 0.sat_mul_bps,sat_bps(alias),sat_mul_bps_u64,sat_bps_u64(alias),sat_bps_round_up,sat_split_bps.sat_percent,sat_mul_percent(alias),sat_percent_u64,sat_mul_percent_u64(alias),sat_percent_round_up,sat_split_percent.
- Panicking percentage siblings (new in this PR; mirror the existing
bpsfamily):percent(amount, percentage, mul_msg, _div_msg) -> i128percent_u64(amount, percentage, mul_msg) -> u64percent_round_up(amount, percentage, msg) -> i128split_percent(amount, percentage, mul_msg, div_msg, sub_msg) -> (i128, i128)
| input | mul_div_i128 (existing) |
sat_mul_div_i128 (new) |
|---|---|---|
denom == 0 |
panics with msg |
returns 0 |
| final overflow | panics with msg |
clamps to i128::MIN/MAX |
| otherwise | returns a * b / denom rounded |
same |
Inline tests lock the contract: sat_mul_div_returns_zero_on_zero_denom (covers the denom == 0 → 0 branch), sat_helper_saturates_to_both_bounds (exercises the mag >= max_neg branch with i128::MIN, not just i128::MAX), sat_helper_u64_saturates, sat_helper_round_up_matches_panicking_family_for_in_range_values, sat_split_bps_and_percent, percent_signature_remains_four_args.
percent's trailing_div_msgparameter andsplit_percent'sdiv_msgparameter are intentionally retained as no-op forward-compat placeholders so existing call sites keep compiling. Do not drop them.- The new
sat_*helpers do not have a default rounding mode — callers must pass an explicitRounding::Down/Up/Nearesttosat_mul_div_i128.
The new math→errors dep edge surfaced six pre-existing compile defects in credence_errors that had been silently sitting on main because no other crate reached for ContractError over that path. Surgical fixes:
- Removed duplicate
SignatureExpired = 109declaration (Authorization block). Every match arm andcategory()points atSignatureExpired = 222(Bond block), so222is the surviving wire-stable value. - Renumbered
InvariantViolationfrom a duplicate218to the lowest free Bond code (230). No callsite can have been relying on the duplicate-discriminant state. Wire-bearing; explicitly noted here for downstream indexers/alerts. - Renumbered
EmergencyDrainNotPermittedfrom a duplicate113to the free Auth code (114).AdminSuspendedretains its original113. Wire-bearing. - Consolidated a split
category()Bond arm soBatchTooLarge/EmptyBatchcoverage is one arm. - Added missing arms in
description()forBatchTooLarge/EmptyBatchand missing arms inis_recoverable()forBatchTooLarge/EmptyBatch/UnsupportedDecimals/UnauthorizedToken/EmergencyDrainNotPermitted. - Removed references to the undeclared variant
ContractError::DuplicateIdempotencyKeyfromdescription()andis_recoverable(); aTODO(#follow-up)breadcrumb is left at the enum anchor so future contributors re-add the three arms (category, description, is_recoverable) when the variant is formally declared with a wire-stable code.
The post-repair rustdoc on InvariantViolation = 230 and EmergencyDrainNotPermitted = 114 honestly call out the wire-code repair rather than restating the stale "Wire-stable: do not renumber" directive.
cargo build -p credence_math— clean.cargo test -p credence_math— 29 unit tests + 5 doc-tests pass.cargo clippy -p credence_math --all-targets -- -D warnings— clean.cargo build -p credence_math --target wasm32-unknown-unknown --release— clean.
cargo clippy --workspace --all-targets -- -D warningsstill fails because of pre-existing issues in:contracts/credence_errors/src/test_errors.rs(E0004 missingUnauthorizedToken/EmergencyDrainNotPermitted/SlippageExceeded; unreachable_patterns onContractError::DelegationNotExpired).contracts/credence_bond/— pre-existing syntax-level state inearly_exit_penalty.rsandrolling_bond.rs. These predate this branch and need a separate follow-up.
pub use-style alias polish:sat_bps,sat_bps_u64,sat_mul_percent,sat_mul_percent_u64are currentlypub fnforwarders;pub use … as …;would be zero-cost. Cosmetic.
- Matches the summary above (sat_mul_bps, sat_percent, and companions, plus the necessary
credence_mathdeps and docs). ✅ - No regression in the existing test suite (29 unit + 5 doctests pass). ✅
- Documented where it is observable (
docs/decimal-handling.md— new## Percentage Chainsand## Saturating Helpers (sat_*)sections, plus a### Migration notessub-section). ✅ - Lint, type-check, and tests all pass locally for the affected crate. ✅
- PR description references this issue with
Closes #725. ✅