Skip to content

feat: decode contract method signature and args in /construction/parse - #165

Merged
potterbm-cb merged 2 commits into
coinbase:masterfrom
andrew-lin-cb:feat/parse-contract-call-decoding
Aug 6, 2026
Merged

feat: decode contract method signature and args in /construction/parse#165
potterbm-cb merged 2 commits into
coinbase:masterfrom
andrew-lin-cb:feat/parse-contract-call-decoding

Conversation

@andrew-lin-cb

@andrew-lin-cb andrew-lin-cb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Motivation

/construction/parse omitted method_signature and method_args for contract-call transactions (there was even a //TODO: add logic for contract call parsing). Consumers that need to verify a transaction's intent — e.g. confirming a delegate(address) call targets the address the customer intended — had nothing decoded from the calldata to check against, so they fell back to the caller-supplied (self-declared) values.

That's a verification gap: a payload whose calldata targets address X could declare intended address Y and still pass validation, because nothing independently re-derived the destination from the signed bytes.

Solution

Decode the method signature and arguments directly from the transaction calldata and surface them in the parse response metadata, so they become a verified source of truth rather than an echo of the declaration.

  • Allowlist of signatures, not an ABI download. A 4-byte selector is a one-way keccak hash of the signature and cannot be reversed, so decoding is only possible against known signatures. A new RosettaConfig.SupportedContractMethods []string lets each chain declare the method signatures (e.g. "delegate(address)") it wants decoded — mirroring the existing TokenWhiteList pattern.
  • Exact inverse of the encoder. The decoder (ParseContractCallData) inverts ConstructContractCallDataGeneric, emitting args in the same string form the encoder accepts, so a decoded (method_signature, method_args) pair round-trips back to identical calldata (verified across address / uint / int / bool / bytes / bytesN / array types).

Behavior: fail-open, never blocks a parse

/construction/parse never fails or blocks a request because of contract-call decoding:

  • No matching method in the allowlist → request still succeeds (200); method_signature/method_args are simply omitted (omitempty). For any unmatched call the endpoint behaves byte-for-byte as it did before this PR.
  • Matched but undecodable calldata → logs and omits the fields, request still succeeds (no 500).
  • The only "closed" aspect is on the data, not the request: when nothing matches, the SDK omits the fields rather than emitting caller-supplied/unverified values, so downstream verifiers get verified data or nothing — never a spoofable echo. With an empty allowlist the output is unchanged, so this is fully backward compatible.

Safety around misconfiguration

  • Startup validation. ValidateSupportedContractMethods lets a chain validate its configured allowlist at boot (well-formedness + valid, decodable ABI arg types) so misconfiguration fails loudly rather than at request time.
  • Tuple/struct args are rejected explicitly (rather than silently decoding to the wrong or empty args), keeping decoded output a faithful, round-trippable representation of the calldata.

Scope

This PR only exposes the capability in the SDK. It does not turn decoding on for any chain: the SupportedContractMethods allowlist is populated per chain in the chain-specific implementation repos (e.g. rosetta-zksync), which will also opt in and call ValidateSupportedContractMethods at startup. With no list configured, behavior is unchanged.

Tests

Unit tests for MatchMethodSignature, ParseContractCallData (round-trip), tuple rejection, and ValidateSupportedContractMethods, plus /construction/parse tests covering a supported method decoding from calldata, an unsupported method emitting no method fields, and the request-time fail-open (matched-but-undecodable) path. go build ./..., go vet, and the construction suite pass.

/construction/parse previously omitted method_signature and method_args
for contract-call transactions, so consumers had to fall back to
caller-supplied (self-declared) values from the unsigned transaction when
verifying transaction intent. This let a payload whose calldata targets
one address declare a different intended address and still pass validation.

This change decodes the method signature and arguments directly from the
transaction calldata for methods on a configurable allowlist
(RosettaCfg.SupportedContractMethods), populating them in the parse
response metadata. Because a 4-byte selector is a one-way hash of the
signature, decoding is only possible against known signatures; selectors
that do not match the allowlist are left undecoded (no fields emitted) so
consumers fail closed instead of trusting unverified data.

The decoder is the exact inverse of ConstructContractCallDataGeneric, so a
decoded (method_signature, method_args) pair round-trips back to identical
calldata.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@cb-heimdall

cb-heimdall commented Aug 3, 2026

Copy link
Copy Markdown

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

Address two verification-safety gaps in contract-call parsing:

1. parseSigArgTypes now rejects tuple/struct signatures (nested parens)
   with an explicit error instead of silently returning empty arg types,
   which previously produced misleading empty method_args. Adds
   ValidateSupportedContractMethods for loud startup validation of the
   configured allowlist (well-formedness + valid ABI arg types).

2. /construction/parse now fails closed on a matched-but-undecodable
   signature: it logs and omits method_signature/method_args instead of
   returning a 500 for otherwise-valid transactions.

Adds tests for tuple rejection, the startup validator, and the
request-time fail-closed path.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@potterbm-cb

Copy link
Copy Markdown
Contributor

For posterity: looks like the github actions are broken at the moment. Andrew ran the checks locally and they passed so I'm merging this without CI running in GHA

@potterbm-cb
potterbm-cb merged commit a3817a4 into coinbase:master Aug 6, 2026
2 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants