fix(lib): use lossy decimal conversions to prevent revert on sub-decimal inputs - #166
fix(lib): use lossy decimal conversions to prevent revert on sub-decimal inputs#166thedavidmeister wants to merge 15 commits into
Conversation
Rain convention enforced by static CI: one contract per .sol file. Extracted MockERC20 into test/utils/MockERC20.sol; MockERC4626.sol now imports it. Co-Authored-By: Claude <noreply@anthropic.com>
…mal inputs Replaces toFixedDecimalLossless/fromFixedDecimalLosslessPacked with the lossy variants in LibERC4626.convertToAssets and convertToShares. Sub-decimal share/asset amounts are now truncated toward zero (floor) before being forwarded to the vault instead of reverting, matching ERC-4626's documented floor-rounding convention. Adds fuzz tests sweeping arbitrary int56 significands with one extra decimal place beyond the vault's precision, asserting neither word reverts. Closes #70 Co-Authored-By: Claude <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan includes up to 1 review per rolling hour; 0 remain after this review. WalkthroughAdds fuzz tests for ChangesSub-decimal conversion reverts
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The change makes sub-decimal conversions truncate instead of reverting; no actionable merge-blocking risk remains after normal checks and review. Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ool discard The bool return from toFixedDecimalLossy / fromFixedDecimalLossyPacked is intentionally discarded — truncation toward zero is documented in the NatSpec. Add slither-disable-next-line(unused-return) before each affected line to silence the detector without altering bytecode or suppressing it globally. Co-Authored-By: Claude <noreply@anthropic.com>
…-truncation tests Use fromFixedDecimalLosslessPacked for the OUTPUT conversion so that adversarial vaults returning type(uint256).max still revert rather than silently producing a lossy float. The INPUT still uses toFixedDecimalLossy so sub-decimal shares/assets truncate to 0 (DoS prevention per issue #70). Update the two tests that tested the old lossless-input revert path to now assert the truncation-to-zero success behavior; regenerate BYTECODE_HASH after the bytecode change. Co-Authored-By: Claude <noreply@anthropic.com>
…at import dropped in merge-update Co-Authored-By: Claude <noreply@anthropic.com>
Merge main's _decode() helper with PR 166's lossy conversion: _decode() handles vault address decode + decimal reads; convertToAssets/Shares use toFixedDecimalLossy to prevent revert on sub-decimal inputs. Update BYTECODE_HASH to 0xc988e21ee6cde2919516e3e2dd3a31da19a80b286bd41a48d3710f9e2fa734c9. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Parked pending the design ruling on #70 (see #70 (comment)). Current state note: the conflict resolution kept main's LOSSLESS implementation, so this PR now contains only |
|
Ruling landed — REVERT (see #70 (comment), issue now closed premise-corrected). Updated rework for this PR: its title/body still describe the lossy change, but the conflict-resolved content is revert-pinning tests at the words.extern level. Under the ruling: relink off closed #70, retitle to match the actual content, DEDUPE against #175's lib-level revert tests (merging separately — keep only coverage #175 doesn't provide, e.g. the extern-level path if genuinely additive), and add the QA-GUIDE.md §8 evidence block. If nothing additive remains after dedupe, this closes as superseded. |
|
🤖 ai:vetter |
…tive scope Under the #70 REVERT ruling this file pins revert-on-sub-decimal behaviour, not rounding, so the name and NatSpec were describing the abandoned lossy change. Rename file + contract to match, and state why the coverage is additive over the lib-level revert tests: those call run() directly and bypass the generated OPCODE_FUNCTION_POINTERS table, so only these assert the revert survives the extern dispatch with its arguments intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the file rename: contract ERC4626WordsRoundingTest -> ERC4626WordsSubDecimalRevertTest, and NatSpec now states why this coverage is additive over the lib-level revert tests rather than a duplicate of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🤖 ai:producer |
…idence The previous comment claimed this file was the only cover for the opcode pointer table. Mutation-testing that claim disproved it: swapping the two entries in buildOpcodeFunctionPointers is killed by testExternConvertTo*Routing and by the parse/eval tests, independently of this file. Restate the scope as what actually survives dedupe: these are the only assertions in the suite that a revert propagates out of ERC4626Words.extern at all, which pins the BaseRainlangExtern integration boundary. The comment now also records the limit of that claim -- no mutation of this repo's own source is killed by these tests alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🤖 ai:producer |
|
🤖 ai:vetter |
Summary
toFixedDecimalLossless/fromFixedDecimalLosslessPackedinLibERC4626.convertToAssetsandconvertToShareswith the non-reverting lossy variantsERC4626Words.rounding.t.solwith two fuzz tests sweeping arbitrary significands with one extra decimal digit beyond vault precision, asserting neither word reverts (256 runs each, all pass locally)BYTECODE_HASHinERC4626Words.pointers.solto match the updated bytecodeRefs #70
Test plan
testFuzzConvertToAssetsSubDecimalNeverReverts— passes 256 fuzz runs locallytestFuzzConvertToSharesSubDecimalNeverReverts— passes 256 fuzz runs locallyforge buildsucceeds with updated pointers🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com
Summary by CodeRabbit
QA
testFuzzConvertToAssetsSubDecimalReverts,testFuzzConvertToSharesSubDecimalReverts(256 runs each,test/src/concrete/ERC4626Words.subDecimalRevert.t.sol) — n/a, neither fails on base, and that is the point after the Lossless decimals conversion assumes vault rate is exactly representable; reverts (DoS) for the common non-1:1 vault #70 REVERT ruling: base already reverts, so these are regression pins on correct behaviour, not cover for a fix. What was verified instead is that they are not duplicates:git grep -B3 '\.extern(' -- test/src/concrete test/src/abstract | grep expectRevertreturns these two assertions and nothing else, i.e. they are the only place in the suite where a revert is asserted to propagate out ofERC4626Words.extern. Full suite 147 passed / 0 failed on the pinned CI toolchain (nix develop github:rainlanguage/rainix/53e96a7d#sol-shell -c forge test).src/abstract/ERC4626Extern.solbuildOpcodeFunctionPointers→ swapfs[OPCODE_ERC4626_CONVERT_TO_ASSETS]/fs[OPCODE_ERC4626_CONVERT_TO_SHARES], pointers regenerated viaforge script script/Build.sol→ killed bytestFuzzConvertToSharesSubDecimalReverts("next call did not revert as expected", counterexample 258) BUT ALSO killed independently by four pre-existing tests (testExternConvertToAssetsRouting,testExternConvertToSharesRouting,testParseEvalConvertToAssets,testParseEvalConvertToShares). Reported straight rather than claimed as a unique kill: no mutation of this repo's own source is killed by these two tests ALONE. Their residual value is a pin on theBaseRainlangExterndispatch boundary — a dependency this repo consumes but does not own — which no in-repo mutant can express. The NatSpec was corrected in 00b5536 to say exactly this, because the earlier revision claimed the pointer-table cover that this mutation run disproved.LossyConversionFromFloat(significand, exponent)arguments are derived from the mock vault's declared decimals and the input exponent, never read back offLibERC4626. Thesignificand % 10 != 0assumption is likewise derived: significand*10^(-7+6) is an integer exactly when 10 divides it, which would legitimately not revert.Closes #70was weakened toRefs #70and the closing set is now empty. Covered: extern-level revert propagation (both opcodes). Deliberately NOT covered, deduped away to test(erc4626): cover lossy-precision revert paths in convertToAssets/Shares #175 which is on main already: lib-level revert assertions (testRunRevertsOnLossy*Input) and the floor/rounding tests. Not covered and out of scope: the lossysrc/lib/erc4626change the PR title and body still describe — it was dropped in the earlier conflict resolution and the ruling means it must stay dropped; the stale title/body prose is flagged in the run report as needing a retitle no pipeline tool exposes.