Skip to content

3.13: msg.value/msg.data shielded-assignment warning misses tuple member/index targets and helper-call launders #370

Open

Description

@HenryMBaldwin

Zellic finding 3.13 (Low). Warnings 10306/10307 are best-effort developer safety nets flagging msg.value/msg.data stored into a shielded type. Two gaps weaken them:

  1. Tuple member/index targets. The tuple branch of visit(Assignment) called checkMsgValueToShielded only when the LHS component was an Identifier, so (s.priv, x) = (suint256(msg.value), 0) (MemberAccess) and (arr[0], x) = ... (IndexAccess) were silent.
  2. Helper-call launder. checkMsgValueToShielded recursed only through TypeConversion nodes, so secret = wrap(msg.value) (a real call whose param is public uint256) was silent — and the call-site check does not fire either because it only triggers on a shielded parameter.

Fix

  • Drop the Identifier-only gate in the tuple branch; fire for every component whose resolved type is shielded (checkMsgValueToShielded self-guards on target type).
  • In checkMsgValueToShielded, additionally recurse into a real call's positional arguments whose parameter is public. Shielded-parameter arguments are still covered by the call-site check, so no double warning.
  • Guard against TupleType targets (nested-tuple LHS components): TupleType::containsShieldedType() is unimplemented, and those components are decomposed/checked individually anyway.

Tests

msg_value_tuple_shielded_target.sol (10306 x3: member, index, identifier) and msg_value_helper_launder.sol (10306 x3: launder, direct, shielded-param). Full syntax suite passes; no regressions. Warning-only, no codegen change.

Activity

  1. Osraka commented on Aug 25, 2026

    @Osraka

    Follow-up after #371 landed via #398: current seismic (e0eb91ec8) still misses the named-argument form of the helper-launder case.

    Minimal reproducer:

    contract C {
        suint256 private positionalSecret;
        suint256 private namedSecret;
    
        function wrap(uint256 value) internal pure returns (suint256) {
            return suint256(value);
        }
    
        function positional() external payable {
            positionalSecret = wrap(msg.value);
        }
    
        function named() external payable {
            namedSecret = wrap({value: msg.value});
        }
    }

    With ssolc --evm-version mercury --bin, the positional call emits 10306, while the named call compiles without that warning.

    The helper recursion in checkMsgValueToShielded is currently gated by funcCall->names().empty(). The general function-call checker already maps named arguments back to parameter positions, so a narrow follow-up could preserve the same public-parameter rule while applying it to both call forms.

    Would a focused PR covering the named form, the existing positional behavior, and the no-double-warning shielded-parameter case be useful here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions