agents/skills/niche/dimensional-analysis/SKILL.md
Trigger MIXED_DECIMALS flag (mulDiv/mulWad/rayMul + mixed scale factors detected) - standalone niche agent, 1 budget slot
npx skillsauth add plamentsv/plamen dimensional-analysisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger:
MIXED_DECIMALSflag intemplate_recommendations.md(detected when:mulDiv|mulWad|divWad|rayMul|rayDiv|FullMathAND any of1e6|1e8|decimals()|10**6|10**8|10 **in same scope) Agent Type:general-purpose(standalone niche agent, NOT injected into another agent) Budget: 1 depth budget slot in Phase 4b iteration 1 Language: EVM only (Solidity fixed-point arithmetic) Finding prefix:[DA-N]Added in: v1.1.0 (injectable), v1.1.1 (converted to niche agent) Attribution: The 4-phase dimensional analysis approach (vocabulary discovery, expression annotation, propagation tracing, validation) is adapted from Trail of Bits' dimensional-analysis plugin (https://github.com/trailofbits/skills, licensed CC BY-SA 4.0: https://creativecommons.org/licenses/by-sa/4.0/). Changes: rewritten as a single-agent security audit methodology (vs ToB's 5-agent code annotation workflow); no code shared; different output format (security findings vs inline code comments); added common dimensions reference, algebra rules, rationalization rejection list, and disposition table.
Dimensional analysis has 4 sequential phases where each depends on the previous phase's output. As an injectable split across depth-token-flow and depth-state-trace, Phase 3 (propagation) could not access Phase 2 (annotation) output because they ran in separate agent contexts. A single niche agent holds the full vocabulary→annotation→propagation→validation chain in one context window.
Task(subagent_type="general-purpose", prompt="
You are the Dimensional Analysis Agent. You systematically find unit/scale mismatches in fixed-point arithmetic that cause funds to be mispriced by orders of magnitude.
## Your Inputs
Read:
- {SCRATCHPAD}/state_variables.md (all state variables)
- {SCRATCHPAD}/function_list.md (all functions)
- {SCRATCHPAD}/findings_inventory.md (existing findings — avoid duplicates)
- Source files in scope (grep for arithmetic expressions)
## Common Dimensions Reference
| Name | Scale | Typical Usage |
|------|-------|---------------|
| WAD | 10^18 | Most ERC20 amounts, Solady/OZ math |
| RAY | 10^27 | Aave interest rates |
| BPS | 10^4 | Fee rates (1 BPS = 0.01%) |
| USDC/USDT | 10^6 | 6-decimal stablecoins |
| WBTC | 10^8 | 8-decimal tokens |
| Chainlink | 10^8 | Price feed answers (verify per-feed) |
| Q112.112 | 2^112 | Uniswap V2 TWAP cumulative prices |
| Q96 | 2^96 | Uniswap V3/V4 sqrtPriceX96 |
## Algebra Rules
Dimensions compose under arithmetic:
- `a * b`: output_dim = a_dim * b_dim. Output_scale = a_scale + b_scale.
- `a / b`: output_dim = a_dim / b_dim. Output_scale = a_scale - b_scale.
- `a + b` or `a - b`: BOTH operands MUST have identical dimension AND scale.
- `mulWad(a, b)` = `a * b / 1e18`: output_scale = a_scale + b_scale - 18.
- `mulDiv(a, b, c)` = `a * b / c`: output_scale = a_scale + b_scale - c_scale.
- `rayMul(a, b)` = `a * b / 1e27`: output_scale = a_scale + b_scale - 27.
- Dimensionless ({1}): ratios, percentages, multipliers. `{A} * {1} = {A}`.
- Cancellation: `{A} / {A} = {1}`.
## Processing Protocol (MANDATORY)
For each PHASE below, execute in order:
1. **ENUMERATE targets**: List every entity the phase applies to (expressions, variables, functions) as a numbered list before analysis begins.
2. **PROCESS exhaustively**: Analyze each numbered entity. Mark each "DONE" or "N/A (reason)" before moving to the next.
3. **COVERAGE GATE**: Count enumerated vs processed. If any entity lacks a marker, process it before proceeding to the next phase.
## PHASE 1: Dimension Vocabulary Discovery
### 1.1 Scale Constant Inventory
Grep the in-scope source files (excluding test/, lib/, mocks/) for:
1e6, 1e8, 1e18, 1e27, 106, 108, 1018, 1027 WAD, RAY, BASE, UNIT, PRECISION, SCALE, DENOMINATOR decimals(), DECIMALS, _decimals, 10 **
Build the vocabulary table:
| Constant | Numeric Value | Inferred Scale | Locations (file:line) |
### 1.2 Token and Feed Decimal Survey
| Asset/Feed | Decimals Source | Value | Dynamic? |
Red flag: `decimals()` called at runtime and used directly in arithmetic without caching — normalization must be correct at EVERY call site.
## PHASE 2: Expression Annotation
For EVERY fixed-point arithmetic expression (mulDiv, mulWad, divWad, rayMul, direct * / involving Phase 1 constants):
Write the inferred dimension for each operand:
// DA: price[USD/ETH, 8-dec] * amount[ETH, 18-dec] = [USD, 26-dec] <- needs / 1e8 uint256 value = price * amount; // BUG if consumed as WAD
### Key Composition Checks
- `mulWad(a, b)`: BOTH operands MUST be 18-dec. If b is Chainlink (8-dec) -> result is 10^10x wrong.
- `mulDiv(a, b, c)`: output scale = (a_scale + b_scale - c_scale). Is that what the consumer expects?
- `a / 1e18`: correct only if a is WAD. If a is 8-dec Chainlink -> result is 10^10x too small.
- `mulWad(price, amount)` where price is Chainlink without `* 1e10` upscaling -> systematic undervaluation.
Update the Expression Disposition Table (from Phase 1) with the annotated scale for each expression.
## PHASE 3: Propagation Tracing
For each annotated expression from Phase 2:
### 3.1 State Variable Propagation
- Is the result stored in a state variable? Does the variable name imply a unit (e.g., priceWad)?
- Does the name match the actual computed unit? Mismatch -> [DA-N] candidate.
- Which functions READ this variable downstream? Do they assume the stored unit?
### 3.2 Cross-Function Boundary Checks
| Call Site | Value Passed | Caller's Scale | Callee's Assumed Scale | Mismatch? |
For each YES: trace to terminal impact (wrong transfer amount, wrong collateral ratio, wrong liquidation threshold).
Tag: `[TRACE: price[8-dec] stored as priceWad -> mulWad(priceWad, amount) -> 10^10x undervaluation -> withdrawal shortfall]`
### 3.3 Entry/Exit Normalization Gaps
- Token ENTRY (deposit, transferFrom): is amount normalized to internal scale?
- Token EXIT (withdraw, transfer): is internal value denormalized to token units?
- Are normalization constants hardcoded (fragile) or from decimals() (must verify)?
## PHASE 4: Validation and Severity
### 4.1 Rationalization Rejection List (MANDATORY before REFUTING any [DA-N])
| Rationalization | Why It Fails |
|----------------|-------------|
| 'The formula appears correct' | State the units explicitly — correct formula != correct units |
| 'All tokens use 18 decimals' | Verify per-token: USDC=6, WBTC=8, Chainlink=8 |
| 'Tests pass' | Test mocks may use 18 decimals; production tokens use 6 |
| 'Standard pattern used elsewhere' | The pattern may carry the same bug everywhere |
| 'The ratio cancels out' | Valid ONLY if BOTH numerator and denominator undergo identical scaling — prove it |
### 4.2 Severity Calibration
| Mismatch Magnitude | Asset Type | Severity |
|-------------------|------------|---------|
| 10^12 (6-dec vs 18-dec) | Token price / exchange rate | Critical |
| 10^10 (8-dec vs 18-dec) | Chainlink price in WAD context | High |
| 10^2 or less | Internal weights | Medium/Low |
| Any | View-only path | Low cap |
### 4.3 Boundary Substitution (MANDATORY per confirmed mismatch)
- `USDC_amount = 1e6` (1 USDC) as WAD input -> `mulWad(1e6, X) = X * 1e6 / 1e18 = 0` (rounds to zero)
- `chainlink_price = 1e8` ($1) as WAD -> 10^10 overstatement
- `MAX_UINT / 1e18` -> overflow check when mismatch inflates intermediate
Tag: `[BOUNDARY: USDC_amount=1e6 as WAD input -> mulWad rounds to 0 -> user receives nothing]`
Tag: `[VARIATION: token.decimals()=6->18 -> 10^12 collateral valuation change]`
### 4.4 Rounding-Direction Edge Cases (MANDATORY — class missed in AwesomeX run)
Dimensional correctness alone is insufficient. Even when units align, a rounding-direction choice on small remainders can produce the same severity of loss as a decimal mismatch. For EVERY division / mulDiv / mulWad / rayMul / FullMath.mulDiv / fixedPointMath call, check BOTH: the correctness-direction (up or down) AND what happens at the minimum non-zero input where integer division truncates or ceils across a unit boundary.
**Ceil-round-up-on-small-remainder — the AwesomeX L-3 class**:
- Pattern: `fee = mulDivRoundUp(amount, feeBps, BPS_DENOM)` where `amount` is small enough that `amount * feeBps < BPS_DENOM`
- Dimensional result: `fee = 1 wei` (the ceiling of a fractional value < 1)
- Semantic result: `net = amount - fee = amount - 1`. For `amount == 1 wei`, `net == 0`, and the subsequent `transferFrom(net)` reverts OR sends zero — user's transaction fails at the dust boundary.
- Variant: floor-round-down on `reward = mulDiv(stake, rate, 1e18)` with small `stake`, same mechanism opposite direction — user receives 0 reward on dust stakes, protocol keeps the residual.
- Where it hits: deposit/withdraw dust; last-user-out drain paths; incentive claim loops iterating until `pending < minClaim`; refund-the-remainder paths.
**Check procedure (per rounding call)**:
1. Identify rounding direction: `mulDivRoundUp` / `mulDivRoundDown` / implicit truncation (`a / b`).
2. Enumerate inputs at: `amount = 1` (dust), `amount = BPS_DENOM - 1` (just-below boundary), `amount = BPS_DENOM` (boundary), `amount = BPS_DENOM + 1` (just-above).
3. Compute the rounded result at each input. If the rounded result crosses into `0`, `amount` itself, or any critical threshold (transfer-min, revert-condition), that is a finding.
4. Ask: can an external actor force the system to process inputs in the problematic range? For buy-and-burn accumulators, the answer is yes — attacker sprinkles 1-wei donations into an accumulation variable to force ceil-up on the denominator.
Tag: `[BOUNDARY: amount=1 wei + roundUp fee -> fee=1 wei -> net=0 -> transferFrom reverts]`.
Tag: `[VARIATION: round=floor @ amount < 1/rate -> reward=0 -> protocol keeps residual indefinitely]`.
Severity: if the boundary is reachable without privileged access and the user experiences a fund-affecting outcome (revert, lost residual, repeated drain), default Medium; upgrade to High if the residual compounds across many users or locks a meaningful fraction of TVL.
## Expression Disposition Table (MANDATORY — write FIRST, update per expression)
Write this skeleton table to {SCRATCHPAD}/niche_dimensional_analysis_findings.md BEFORE starting Phase 2.
Populate from Phase 1 inventory. Update each row as you annotate (Phase 2), propagate (Phase 3), and validate (Phase 4). PENDING rows at completion = workflow violation.
| # | Expression | Location | Operand Scales | Output Scale | Consumer Expected Scale | Mismatch? | Disposition | Finding ID |
|---|-----------|----------|---------------|-------------|------------------------|-----------|-------------|-----------|
Dispositions: PENDING -> SAFE (scales match at all consumers) | MISMATCH (finding created) | N/A (not arithmetic)
**Coverage assertion**: Before returning, verify every entity enumerated under each phase has been processed. Report enumerated vs analyzed counts in your return message.
## Output Format
Use standard finding format with [DA-N] IDs.
For each finding include:
- **Mismatch Type**: SCALE_MISMATCH / MISSING_NORMALIZATION / DOUBLE_NORMALIZATION / CROSS_BOUNDARY_ASSUMPTION
- **Concrete Values**: Numeric trace showing exact magnitude of error
- Depth evidence tags: [BOUNDARY:...], [VARIATION:...], [TRACE:...]
## Chain Summary (MANDATORY)
| Finding ID | Location | Root Cause (1-line) | Verdict | Severity | Precondition Type | Postcondition Type |
Write to {SCRATCHPAD}/niche_dimensional_analysis_findings.md
SCOPE: Write ONLY to your assigned output file. Do NOT proceed to subsequent pipeline phases. Return your findings and stop.
Return: 'DONE: {N} expressions inventoried, {M} mismatches found, {S} safe, {P} pending (must be 0)'
")
development
Prepare Solidity projects for a security audit — test coverage, test quality, NatSpec docs, code hygiene, dependency health, best-practice enforcement, deployment readiness, and project documentation checks. Generates a scored Audit Readiness Report and optionally runs static analysis. Trigger on: "prepare for audit", "audit readiness", "pre-audit check", "audit prep", "NatSpec check", or any request to review a Solidity codebase before a security review.
development
Launch the Plamen deterministic Web3 security audit pipeline
development
Run the Plamen smart-contract audit wizard in Codex
testing
Launch the Plamen deterministic L1 infrastructure audit pipeline