Skip to content

bugc: string and bytes calldata parameters - #383

Merged
gnidan merged 3 commits into
mainfrom
bugc-calldata-params
Oct 15, 2026
Merged

gnidan merged 3 commits into
mainfrom
bugc-calldata-params

Conversation

@gnidan

@gnidan gnidan commented Oct 15, 2026

Copy link
Copy Markdown
Member

A function parameter may now be string calldata or bytes calldata, so a contract can hand a reference into its call data to a function without copying it:

define {
  function join(name: string calldata) {
    players[msg.sender].name = name;
  };
}
code {
  let offset = msg.data[4:36] as bytes32 as uint256;
  let n = msg.data[4 + offset:36 + offset] as bytes32 as uint256;
  let name: string calldata =
    msg.data[36 + offset:36 + offset + n] as string calldata;
  join(name);
}

#367 and #379 limited these types to a let or a cast (TYPE016) to keep their scope small; nothing in the calling convention needs it. A calldata reference is one word (offset in the high 128 bits, length in the low 128), so it passes as any word argument does. The call site now skips the copy to memory when the parameter is in calldata, and a parameter of the plain string or bytes type still gets a copy. The argument must itself be in calldata, with the same rules as a let of that type: a string in memory, or bytes calldata without a cast, is a type error for a string calldata parameter. A return type, struct field, storage variable or nested type of calldata is still TYPE016.

The parameter's IR type is the calldata reference, so its debug info is a calldata let's: the parameter's word, then a calldata region whose offset and length are computed from it.

Tests:

  • Type checking accepts both parameter types, passing them on to another function, and rejects a calldata return type, a nested calldata parameter type, and a memory or mis-typed argument.
  • At O0–O3, a string calldata and a bytes calldata parameter of 0, 31, 32, 33 and 65 bytes are stored and indexed, through a leaf function (inlined at O2 and O3) and through a function with two returns that passes its parameter on (a real call at every level). The IR has calldata parameter types and no extra copy.
  • Soundness: the locals harness runs both shapes and, at every step that lists a parameter, reads its pointer against the trace. The pointer must decode to the string's bytes in calldata, and each parameter must be located at every level.

The BUG case study and the changelog are updated.

A function parameter may be `string calldata` or `bytes calldata`. Its
argument must be in calldata too, and passes as its word of offset and
length, with no copy to memory, in a real call and when the function
is inlined. The parameter's pointer is that of a calldata `let`.
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-15 00:40 UTC

A call whose callee has no function type is now an error, not a call
that copies every argument to memory. New tests run a calldata
parameter's copy to a string, keccak256, a string return value, an
argument to a string parameter, and a tail-recursive function at O0 to
O3, and check a bytes calldata parameter's pointer.
@gnidan
gnidan merged commit daf04e7 into main Oct 15, 2026
8 checks passed
@gnidan
gnidan deleted the bugc-calldata-params branch October 15, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant