From 4bcc15e493b869c0b7cb7ecd8d515fcf836de18b Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Tue, 25 Aug 2026 16:18:42 +0000 Subject: [PATCH] fix(cli): gate authoring-meta-v2 normalize on an abi decode AuthoringMetaV2 fell through the pure-bytes default arm, so `validate --meta authoring-meta-v2` reported every byte string valid, including bytes that can never abi decode as AuthoringMetaV2Sol[]. Closes #174 Co-Authored-By: Claude Opus 5 (1M context) --- crates/cli/src/cli/validate.rs | 12 +++++++ crates/cli/src/meta/normalize.rs | 60 ++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/cli/validate.rs b/crates/cli/src/cli/validate.rs index 7578d0db..214aa711 100644 --- a/crates/cli/src/cli/validate.rs +++ b/crates/cli/src/cli/validate.rs @@ -50,4 +50,16 @@ mod tests { }; assert!(validate(v).is_err()); } + + /// Arbitrary bytes are not a valid authoring-meta-v2. + #[test] + fn test_validate_err_for_arbitrary_authoring_meta_v2() { + let mut file = tempfile::NamedTempFile::new().unwrap(); + file.write_all(&[0xde, 0xad]).unwrap(); + let v = Validate { + meta: KnownMeta::AuthoringMetaV2, + input_path: file.path().to_path_buf(), + }; + assert!(validate(v).is_err()); + } } diff --git a/crates/cli/src/meta/normalize.rs b/crates/cli/src/meta/normalize.rs index 9e8c3c0f..391307e6 100644 --- a/crates/cli/src/meta/normalize.rs +++ b/crates/cli/src/meta/normalize.rs @@ -2,8 +2,8 @@ use super::{ KnownMeta, super::error::Error, types::{ - op::v1::OpMeta, authoring::v1::AuthoringMeta, solidity_abi::v2::SolidityAbiMeta, - interpreter_caller::v1::InterpreterCallerMeta, + op::v1::OpMeta, authoring::v1::AuthoringMeta, authoring::v2::AuthoringMetaV2, + solidity_abi::v2::SolidityAbiMeta, interpreter_caller::v1::InterpreterCallerMeta, }, }; @@ -34,6 +34,13 @@ impl KnownMeta { )?, } } + KnownMeta::AuthoringMetaV2 => { + // v2 is abi encoded onchain and this crate has no encoder for + // it, so validation is a decode gate over the input as is + AuthoringMetaV2::abi_decode(data) + .map_err(|e| Error::InvalidInput(e.to_string()))?; + data.to_vec() + } // rest of meta types are only pure bytes (ut8 strings or binary) // so no normalization/validation can happen for them at this level _ => data.to_vec(), @@ -43,10 +50,20 @@ impl KnownMeta { #[cfg(all(test, not(target_family = "wasm")))] mod tests { + use alloy::sol_types::SolValue; use crate::error::Error; use crate::meta::types::authoring::v1::{AuthoringMeta, AuthoringMetaItem}; + use crate::meta::types::authoring::v2::AuthoringMetaV2Sol; use crate::meta::KnownMeta; + fn authoring_meta_v2_abi(word: [u8; 32], description: &str) -> Vec { + vec![AuthoringMetaV2Sol { + word: word.into(), + description: description.to_string(), + }] + .abi_encode() + } + /// OpV1 normalizes valid metadata to its canonical compact json form. #[test] fn test_normalize_op_v1_canonicalizes() { @@ -159,6 +176,45 @@ mod tests { assert_ne!(normalized, json.as_bytes().to_vec()); } + /// AuthoringMetaV2 has a concrete abi encoding, so a decodable payload is + /// valid and passes through byte identically. + #[test] + fn test_normalize_authoring_meta_v2_abi_passthrough() { + let mut word = [0u8; 32]; + word[..5].copy_from_slice(b"stack"); + let abi = authoring_meta_v2_abi(word, "Copies an existing value from the stack."); + assert_eq!(KnownMeta::AuthoringMetaV2.normalize(&abi).unwrap(), abi); + } + + /// AuthoringMetaV2 is not pure bytes: bytes that cannot abi decode as + /// AuthoringMetaV2Sol[] are rejected rather than passed through. + #[test] + fn test_normalize_authoring_meta_v2_rejects_arbitrary_bytes() { + assert!(matches!( + KnownMeta::AuthoringMetaV2.normalize(&[0xde, 0xad]), + Err(Error::InvalidInput(_)) + )); + assert!(matches!( + KnownMeta::AuthoringMetaV2.normalize(b"[]"), + Err(Error::InvalidInput(_)) + )); + } + + /// The decode gate carries the word utf8 requirement: abi shaped bytes + /// whose word is not utf8 before its first NUL are rejected. + #[test] + fn test_normalize_authoring_meta_v2_rejects_non_utf8_word() { + let mut word = [0u8; 32]; + // 0xc3 followed by 0x28 is an invalid utf8 sequence, before any NUL + word[0] = 0xc3; + word[1] = 0x28; + let abi = authoring_meta_v2_abi(word, "bad word bytes"); + assert!(matches!( + KnownMeta::AuthoringMetaV2.normalize(&abi), + Err(Error::InvalidInput(_)) + )); + } + /// Meta types without a structured normal form pass raw bytes through /// unchanged. #[test]