fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes#2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

CopilotAI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 15, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.
The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.
Closesstellar#2244
…rror
Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
@Galmanus
Galmanusforce-pushed the fix/2244-bytesn-no-zero-pad branch from c98824f to d57bb33CompareSeptember 1, 2026 23:05
CopilotAI review requested due to automatic review settings September 1, 2026 23:05

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 808 to 820
/// # Errors
///
/// Might return an error
/// Decode a hex string that must be complete: an even number of digits, no
/// zero-padding (see #2244). Returns the raw bytes; callers enforce any length.
fn decode_complete_hex(s: &str, t: &ScType) -> Result<Vec<u8>, Error> {
if !s.len().is_multiple_of(2) {
return Err(Error::OddHexLength(s.len()));
}
hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))
}

pub fn from_string_primitive(s: &str, t: &ScType) -> Result<ScVal, Error> {
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants

@Galmanus