feat: Enforce EVM-stringent validation on proposal action ABI inputs - #720
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Hardens the ProposalActionsDecoder edit-mode form validation so ABI inputs are validated to EVM/ABI constraints (signed ints, bit-width bounds, and strict EIP-55 checksum checking) to prevent cases where invalid inputs silently fall back to data: 0x due to encodeFunctionData errors being swallowed.
Changes:
- Add signed
int*validation and numeric bit-width range checks (signed/unsigned), and enable strict checksum validation foraddress. - Update numeric input sanitization to disallow
-for unsigned number types while preserving it for signed types. - Add new validation copy strings and a changeset entry for the minor release.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderUtils.ts | Adds signed int validation, range checking, and strict address validation. |
| src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderUtils.test.ts | Adds/extends unit tests for new numeric validation branches. |
| src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderTextField/proposalActionsDecoderTextFieldEdit.tsx | Updates numeric sanitization to treat signed/unsigned differently for -. |
| src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderTextField/proposalActionsDecoderTextFieldEdit.test.tsx | Updates component tests for the sanitizer behavior. |
| src/modules/assets/copy/modulesCopy.ts | Adds signedNumber and numberRange validation copy strings. |
| .changeset/evm-stringent-abi-validation.md | Declares a minor version bump and documents the validation hardening. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
thekidnamedkd
force-pushed
the
fix/evm-stringent-abi-validation
branch
from
July 13, 2026 13:02
538684e to
112a64d
Compare
harryburger
requested changes
Jul 13, 2026
harryburger
previously approved these changes
Jul 13, 2026
harryburger
approved these changes
Jul 13, 2026
harryburger
approved these changes
Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
In relation to #719 -- this hardens
ProposalActionsDecoderform validation based on findings from the review of #719. The principle applied: validate the shape of ABI types exactly as strictly as the ABI spec defines them — no stricter, no looser (#719 fixed a too-strict case; this fixes the too-loose ones).Why
The decoder's live encoding wraps
encodeFunctionDatain a silent try/catch (proposalActionsDecoder.tsx). When form validation passes but viem throws, thedatafield silently stays0x— and a submitted action withdata: 0xisn't a failed action, it's a different action (a bare call / ETH transfer to the target). In a governance context that output gets voted on, not double-checked. Three validation gaps could reach that state (all verified empirically against viem 2.52.2):int*types had no validation branch at all — only the required check. A lone-passed validation; viem throwsCannot convert - to a BigInt; the error was swallowed. (Also:BigInt('') === 0n, so only the required check stood between an empty int and silently encoding as zero.)uint8with value300passed the^[0-9]*$regex; viem throwsnot in safe 8-bit unsigned integer range; swallowed.addressUtils.isAddressdefaulted tostrict: false, so a mixed-case address with a wrong EIP-55 checksum validated. That discards the checksum's entire purpose: catching typos/corruption on the one field where a mistake is irrecoverable. All-lowercase addresses still pass (verified — viem's strict mode only enforces the checksum when the input is mixed-case).Changes
proposalActionsDecoderUtils: newvalidateSignedNumber(format) andvalidateNumberRange(BigInt bounds per bit-width, signed and unsigned, default 256);validateValuenow coversint*and checks ranges after format; address validation uses{ strict: true }.ProposalActionsDecoderTextFieldEdit: the numeric input sanitizer no longer allows a leading-for unsigned types (it previously did, and only the regex validator caught it afterwards).signedNumberandnumberRangeunderproposalActionsDecoder.validation(minor bump — additive copy API, same as previous copy additions e.g. feat(APP-823): Decoded action form input UX improvements #701).With these in place, no value that passes validation can make
encodeFunctionDatathrow, which closes the silent-0xchannel from the input side. The try/catch remains as intended for transient keystroke states.Not included (deliberately)
bytesfields still require an explicit0xfor empty bytes rather than accepting a blank field — that's the canonical representation (viem'sHexcontract; JSON-RPCDATArequires the prefix) and blank-vs-empty ambiguity on calldata is worth keeping explicit.How to test: Storybook →
ProposalActions.Decoder→ArrayTypestory withmode: EDIT: paste a mixed-case address with one letter's case flipped → checksum error appears; all-lowercase or correctly-checksummed → accepted. Unit tests cover the int/range branches (uint8255/256,int8±127/128/−129,uint256boundary, lone-, unparsable values).Type of change
Developer Checklist:
Review Checklist: