From c056ca18b9f80eb6763f242b8780bfa6c9ed0291 Mon Sep 17 00:00:00 2001 From: shukudaidayo <108971147+shukudaidayo@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:00:43 -0500 Subject: [PATCH 1/2] Fix visibility guards in bundled groups --- crates/clear-signing/src/eip712.rs | 47 ++-- crates/clear-signing/src/engine.rs | 128 +++++++-- crates/clear-signing/tests/spec_compliance.rs | 249 ++++++++++++++++++ 3 files changed, 385 insertions(+), 39 deletions(-) diff --git a/crates/clear-signing/src/eip712.rs b/crates/clear-signing/src/eip712.rs index 7ba3955..ba8b6e5 100644 --- a/crates/clear-signing/src/eip712.rs +++ b/crates/clear-signing/src/eip712.rs @@ -453,7 +453,10 @@ fn render_typed_fields<'a>( enum TypedGroupRenderKind { Scalar(Vec), - Bundles(Vec>), + Bundles { + len: usize, + bundles: Vec>, + }, } #[allow(clippy::too_many_arguments)] @@ -508,8 +511,8 @@ fn render_typed_group_field_kind<'a>( if let Some(serde_json::Value::Array(items)) = resolve_typed_path_in_context(message, base, container)? { - let mut bundles = Vec::new(); - for item in &items { + let mut bundles = vec![Vec::new(); items.len()]; + for (index, item) in items.iter().enumerate() { let val = if rest.is_empty() { Some(item.clone()) } else { @@ -554,9 +557,12 @@ fn render_typed_group_field_kind<'a>( .await?, }] }; - bundles.push(rendered); + bundles[index].extend(rendered); } - return Ok(TypedGroupRenderKind::Bundles(bundles)); + return Ok(TypedGroupRenderKind::Bundles { + len: items.len(), + bundles, + }); } } @@ -675,7 +681,7 @@ fn render_typed_group_kind<'a>( let all_bundles = !child_kinds.is_empty() && child_kinds .iter() - .all(|k| matches!(k, TypedGroupRenderKind::Bundles(_))); + .all(|k| matches!(k, TypedGroupRenderKind::Bundles { .. })); let items = if all_bundles { // Element-major: keep each array element's fields contiguous // (e.g. [amount0, addr0, amount1, addr1]) rather than grouping @@ -683,7 +689,7 @@ fn render_typed_group_kind<'a>( let mut sets: Vec<_> = child_kinds .into_iter() .map(|k| match k { - TypedGroupRenderKind::Bundles(b) => b, + TypedGroupRenderKind::Bundles { bundles, .. } => bundles, TypedGroupRenderKind::Scalar(_) => Vec::new(), }) .collect(); @@ -702,7 +708,7 @@ fn render_typed_group_kind<'a>( .into_iter() .flat_map(|kind| match kind { TypedGroupRenderKind::Scalar(items) => items, - TypedGroupRenderKind::Bundles(bundles) => { + TypedGroupRenderKind::Bundles { bundles, .. } => { bundles.into_iter().flatten().collect() } }) @@ -714,7 +720,9 @@ fn render_typed_group_kind<'a>( let mut bundle_sets = Vec::new(); for kind in child_kinds { match kind { - TypedGroupRenderKind::Bundles(bundles) => bundle_sets.push(bundles), + TypedGroupRenderKind::Bundles { len, bundles } => { + bundle_sets.push((len, bundles)); + } TypedGroupRenderKind::Scalar(_) => { return Err(Error::Render( "bundled groups cannot mix array-expanded and scalar fields" @@ -725,14 +733,14 @@ fn render_typed_group_kind<'a>( } if bundle_sets.is_empty() { - return Ok(TypedGroupRenderKind::Bundles(Vec::new())); + return Ok(TypedGroupRenderKind::Bundles { + len: 0, + bundles: Vec::new(), + }); } - let expected_len = bundle_sets[0].len(); - if bundle_sets - .iter() - .any(|bundles| bundles.len() != expected_len) - { + let expected_len = bundle_sets[0].0; + if bundle_sets.iter().any(|(len, _)| *len != expected_len) { return Err(Error::Render( "bundled groups require all array-expanded fields to have the same length" .to_string(), @@ -740,12 +748,15 @@ fn render_typed_group_kind<'a>( } let mut bundled = vec![Vec::new(); expected_len]; - for bundles in bundle_sets { + for (_, bundles) in bundle_sets { for (index, items) in bundles.into_iter().enumerate() { bundled[index].extend(items); } } - Ok(TypedGroupRenderKind::Bundles(bundled)) + Ok(TypedGroupRenderKind::Bundles { + len: expected_len, + bundles: bundled, + }) } } }) @@ -790,7 +801,7 @@ async fn render_typed_field_group_entries<'a>( Ok(items.into_iter().map(DisplayEntry::Item).collect()) } } - TypedGroupRenderKind::Bundles(bundles) => { + TypedGroupRenderKind::Bundles { bundles, .. } => { let items: Vec = bundles.into_iter().flatten().collect(); if items.is_empty() { return Ok(Vec::new()); diff --git a/crates/clear-signing/src/engine.rs b/crates/clear-signing/src/engine.rs index a2e1c7f..d10c40d 100644 --- a/crates/clear-signing/src/engine.rs +++ b/crates/clear-signing/src/engine.rs @@ -340,7 +340,10 @@ fn render_fields<'a>( enum GroupRenderKind { Scalar(Vec), - Bundles(Vec>), + Bundles { + len: usize, + bundles: Vec>, + }, } pub(crate) fn flatten_display_entry(entry: DisplayEntry) -> Vec { @@ -389,7 +392,7 @@ fn render_group_field_kind<'a>( let path_str = path.as_deref().unwrap_or(""); if let Some((base, rest)) = split_array_iter_path(path_str) { if let Some(ArgumentValue::Array(items)) = resolve_path(ctx.decoded, base) { - let mut bundles = Vec::new(); + let mut bundles = vec![Vec::new(); items.len()]; for (i, item) in items.iter().enumerate() { let val = if rest.is_empty() { Some(item.clone()) @@ -430,9 +433,12 @@ fn render_group_field_kind<'a>( .await?, }] }; - bundles.push(rendered); + bundles[i].extend(rendered); } - return Ok(GroupRenderKind::Bundles(bundles)); + return Ok(GroupRenderKind::Bundles { + len: items.len(), + bundles, + }); } } @@ -494,7 +500,7 @@ fn render_group_kind<'a>( let all_bundles = !child_kinds.is_empty() && child_kinds .iter() - .all(|k| matches!(k, GroupRenderKind::Bundles(_))); + .all(|k| matches!(k, GroupRenderKind::Bundles { .. })); let items = if all_bundles { // Element-major: keep each array element's fields contiguous // (e.g. [amount0, addr0, amount1, addr1]) rather than grouping @@ -502,7 +508,7 @@ fn render_group_kind<'a>( let mut sets: Vec<_> = child_kinds .into_iter() .map(|k| match k { - GroupRenderKind::Bundles(b) => b, + GroupRenderKind::Bundles { bundles, .. } => bundles, GroupRenderKind::Scalar(_) => Vec::new(), }) .collect(); @@ -521,7 +527,7 @@ fn render_group_kind<'a>( .into_iter() .flat_map(|kind| match kind { GroupRenderKind::Scalar(items) => items, - GroupRenderKind::Bundles(bundles) => { + GroupRenderKind::Bundles { bundles, .. } => { bundles.into_iter().flatten().collect() } }) @@ -533,7 +539,9 @@ fn render_group_kind<'a>( let mut bundle_sets = Vec::new(); for kind in child_kinds { match kind { - GroupRenderKind::Bundles(bundles) => bundle_sets.push(bundles), + GroupRenderKind::Bundles { len, bundles } => { + bundle_sets.push((len, bundles)); + } GroupRenderKind::Scalar(_) => { return Err(Error::Render( "bundled groups cannot mix array-expanded and scalar fields" @@ -544,14 +552,14 @@ fn render_group_kind<'a>( } if bundle_sets.is_empty() { - return Ok(GroupRenderKind::Bundles(Vec::new())); + return Ok(GroupRenderKind::Bundles { + len: 0, + bundles: Vec::new(), + }); } - let expected_len = bundle_sets[0].len(); - if bundle_sets - .iter() - .any(|bundles| bundles.len() != expected_len) - { + let expected_len = bundle_sets[0].0; + if bundle_sets.iter().any(|(len, _)| *len != expected_len) { return Err(Error::Render( "bundled groups require all array-expanded fields to have the same length" .to_string(), @@ -559,12 +567,15 @@ fn render_group_kind<'a>( } let mut bundled = vec![Vec::new(); expected_len]; - for bundles in bundle_sets { + for (_, bundles) in bundle_sets { for (index, items) in bundles.into_iter().enumerate() { bundled[index].extend(items); } } - Ok(GroupRenderKind::Bundles(bundled)) + Ok(GroupRenderKind::Bundles { + len: expected_len, + bundles: bundled, + }) } } }) @@ -593,7 +604,7 @@ async fn render_field_group_entries<'a>( Ok(items.into_iter().map(DisplayEntry::Item).collect()) } } - GroupRenderKind::Bundles(bundles) => { + GroupRenderKind::Bundles { bundles, .. } => { let items: Vec = bundles.into_iter().flatten().collect(); if items.is_empty() { return Ok(Vec::new()); @@ -1059,6 +1070,78 @@ fn visibility_context(label: &str, path: &str) -> String { } } +fn visibility_string_literals_match(actual: &str, expected: &str) -> bool { + actual == expected + || (actual.starts_with("0x") + && expected.starts_with("0x") + && actual.eq_ignore_ascii_case(expected)) +} + +fn parse_visibility_unsigned_literal(literal: &str) -> Option { + let trimmed = literal.trim(); + if trimmed.is_empty() || trimmed.starts_with('-') { + return None; + } + + if let Some(hex_literal) = trimmed + .strip_prefix("0x") + .or_else(|| trimmed.strip_prefix("0X")) + { + if hex_literal.is_empty() { + return None; + } + return BigUint::parse_bytes(hex_literal.as_bytes(), 16); + } + + BigUint::parse_bytes(trimmed.as_bytes(), 10) +} + +fn visibility_unsigned_integer_value(value: &ArgumentValue) -> Option { + match value { + ArgumentValue::Uint(bytes) => Some(BigUint::from_bytes_be(bytes)), + _ => None, + } +} + +fn visibility_value_matches_argument(value: &ArgumentValue, expected: &serde_json::Value) -> bool { + let actual_json = value.to_json_value(); + if &actual_json == expected { + return true; + } + + if let (serde_json::Value::String(actual), serde_json::Value::String(expected)) = + (&actual_json, expected) + { + if visibility_string_literals_match(actual, expected) { + return true; + } + } + + let Some(actual_number) = visibility_unsigned_integer_value(value) else { + return false; + }; + + match expected { + serde_json::Value::Number(expected_number) => expected_number + .as_u64() + .is_some_and(|expected| actual_number == BigUint::from(expected)), + serde_json::Value::String(expected_string) => { + parse_visibility_unsigned_literal(expected_string) + .is_some_and(|expected| actual_number == expected) + } + _ => false, + } +} + +fn visibility_list_matches_argument( + expected_values: &[serde_json::Value], + value: &ArgumentValue, +) -> bool { + expected_values + .iter() + .any(|expected| visibility_value_matches_argument(value, expected)) +} + /// Check if a field should be visible based on the visibility rule and decoded value. fn check_visibility( rule: &VisibleRule, @@ -1084,12 +1167,15 @@ fn check_visibility( return Ok(true); }; - let json_val = val.to_json_value(); - if cond.hides_for_if_not_in(&json_val) { + if cond + .if_not_in + .as_ref() + .is_some_and(|excluded| visibility_list_matches_argument(excluded, val)) + { return Ok(false); } - if cond.must_match.is_some() { - if cond.matches_must_match(&json_val) { + if let Some(required) = cond.must_match.as_ref() { + if visibility_list_matches_argument(required, val) { return Ok(false); } return Err(Error::Render(format!( diff --git a/crates/clear-signing/tests/spec_compliance.rs b/crates/clear-signing/tests/spec_compliance.rs index 2de039c..60b86cd 100644 --- a/crates/clear-signing/tests/spec_compliance.rs +++ b/crates/clear-signing/tests/spec_compliance.rs @@ -109,6 +109,31 @@ fn build_two_array_calldata(sig_str: &str, addrs: &[&str], values: &[u64]) -> Ve calldata } +fn build_three_array_calldata( + sig_str: &str, + first_values: &[u64], + second_values: &[u64], + addrs: &[&str], +) -> Vec { + let sig = decoder::parse_signature(sig_str).unwrap(); + let first_encoded = encode_uint_array(first_values); + let second_encoded = encode_uint_array(second_values); + let addresses_encoded = encode_address_array(addrs); + let first_offset = 96usize; + let second_offset = first_offset + first_encoded.len(); + let addresses_offset = second_offset + second_encoded.len(); + + let mut calldata = Vec::new(); + calldata.extend_from_slice(&sig.selector); + calldata.extend_from_slice(&dynamic_offset_word(first_offset)); + calldata.extend_from_slice(&dynamic_offset_word(second_offset)); + calldata.extend_from_slice(&dynamic_offset_word(addresses_offset)); + calldata.extend_from_slice(&first_encoded); + calldata.extend_from_slice(&second_encoded); + calldata.extend_from_slice(&addresses_encoded); + calldata +} + fn keccak256_test(bytes: &[u8]) -> [u8; 32] { let mut hasher = Keccak::v256(); hasher.update(bytes); @@ -1330,6 +1355,71 @@ async fn test_calldata_bundled_group_zips_array_items() { } } +#[tokio::test] +async fn test_calldata_bundled_group_allows_hidden_array_guard_fields() { + let json = r#"{ + "context": { + "contract": { + "deployments": [{"chainId": 1, "address": "0xabc"}] + } + }, + "metadata": {"owner": "test", "enums": {}, "constants": {}, "maps": {}}, + "display": { + "definitions": {}, + "formats": { + "batch(uint256[] kinds,uint256[] amounts,address[] recipients)": { + "intent": "Batch", + "fields": [{ + "label": "Transfers", + "iteration": "bundled", + "fields": [ + {"path": "kinds.[]", "label": "Kind", "format": "number", "visible": {"mustBe": ["1"]}}, + {"path": "amounts.[]", "label": "Amount", "format": "number"}, + {"path": "recipients.[]", "label": "Recipient", "format": "address"} + ] + }] + } + } + } + }"#; + + let descriptor = Descriptor::from_json(json).unwrap(); + let calldata = build_three_array_calldata( + "batch(uint256[],uint256[],address[])", + &[1, 1], + &[100, 200], + &[ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + ], + ); + let tx = TransactionContext { + chain_id: 1, + to: "0xabc", + calldata: &calldata, + value: None, + from: None, + implementation_address: None, + }; + + let result = format_calldata(&wrap_rd(descriptor, 1, "0xabc"), &tx, &EmptyDataProvider) + .await + .unwrap(); + match &result.entries[0] { + DisplayEntry::Group { + iteration, items, .. + } => { + assert!(matches!(iteration, GroupIteration::Bundled)); + assert_eq!(items.len(), 4); + assert_eq!(items[0].label, "Amount"); + assert_eq!(items[1].label, "Recipient"); + assert_eq!(items[2].label, "Amount"); + assert_eq!(items[3].label, "Recipient"); + } + _ => panic!("expected bundled group"), + } +} + #[tokio::test] async fn test_eip712_bundled_group_zips_array_items() { let descriptor = Descriptor::from_json( @@ -1394,6 +1484,86 @@ async fn test_eip712_bundled_group_zips_array_items() { } } +#[tokio::test] +async fn test_eip712_bundled_group_allows_hidden_array_guard_fields() { + let descriptor = Descriptor::from_json( + r##"{ + "context": { "eip712": { "deployments": [{"chainId": 1, "address": "0xabc"}] } }, + "metadata": { "owner": "test", "enums": {}, "constants": {}, "maps": {} }, + "display": { + "definitions": {}, + "formats": { + "Batch(Item[] items)Item(uint256 kind,uint256 amount,address recipient)": { + "intent": "Batch", + "fields": [{ + "label": "Transfers", + "iteration": "bundled", + "fields": [ + { "path": "items.[].kind", "label": "Kind", "format": "number", "visible": { "mustBe": [1] } }, + { "path": "items.[].amount", "label": "Amount", "format": "number" }, + { "path": "items.[].recipient", "label": "Recipient", "format": "address" } + ] + }] + } + } + } + }"##, + ) + .unwrap(); + + let typed_data: TypedData = serde_json::from_value(serde_json::json!({ + "types": { + "EIP712Domain": [], + "Batch": [ + { "name": "items", "type": "Item[]" } + ], + "Item": [ + { "name": "kind", "type": "uint256" }, + { "name": "amount", "type": "uint256" }, + { "name": "recipient", "type": "address" } + ] + }, + "primaryType": "Batch", + "domain": { "chainId": 1, "verifyingContract": "0xabc" }, + "message": { + "items": [ + { + "kind": 1, + "amount": 100, + "recipient": "0x0000000000000000000000000000000000000001" + }, + { + "kind": 1, + "amount": 200, + "recipient": "0x0000000000000000000000000000000000000002" + } + ] + } + })) + .unwrap(); + + let result = format_typed_data( + &wrap_rd(descriptor, 1, "0xabc"), + &typed_data, + &EmptyDataProvider, + ) + .await + .unwrap(); + match &result.entries[0] { + DisplayEntry::Group { + iteration, items, .. + } => { + assert!(matches!(iteration, GroupIteration::Bundled)); + assert_eq!(items.len(), 4); + assert_eq!(items[0].label, "Amount"); + assert_eq!(items[1].label, "Recipient"); + assert_eq!(items[2].label, "Amount"); + assert_eq!(items[3].label, "Recipient"); + } + _ => panic!("expected bundled group"), + } +} + #[tokio::test] async fn test_eip712_bundled_group_mixed_scalar_child_errors() { let descriptor = Descriptor::from_json( @@ -5603,6 +5773,85 @@ async fn test_visible_must_match_hides_matching_value_and_errors_on_mismatch() { assert!(err.contains("visible.mustMatch")); } +#[tokio::test] +async fn test_calldata_visibility_must_be_matches_decimal_strings_and_checksum_addresses() { + let descriptor = Descriptor::from_json( + r#"{ + "context": { "contract": { "deployments": [{"chainId": 1, "address": "0xabc"}] } }, + "metadata": { "owner": "test", "enums": {}, "constants": {}, "maps": {} }, + "display": { + "definitions": {}, + "formats": { + "show(uint256 guard,address registry,uint256 value)": { + "intent": "Show", + "fields": [ + { "path": "guard", "label": "Guard", "format": "number", "visible": { "mustBe": ["1"] } }, + { "path": "registry", "label": "Registry", "format": "address", "visible": { "mustBe": ["0x000000000000000000000000000000000000aBcD"] } }, + { "path": "value", "label": "Value", "format": "number" } + ] + } + } + } + }"#, + ) + .unwrap(); + + let matching = build_calldata( + "show(uint256,address,uint256)", + &[ + uint_word(1), + addr_word("0x000000000000000000000000000000000000abcd"), + uint_word(5), + ], + ); + let matching_tx = TransactionContext { + chain_id: 1, + to: "0xabc", + calldata: &matching, + value: None, + from: None, + implementation_address: None, + }; + let matching_result = format_calldata( + &wrap_rd(descriptor.clone(), 1, "0xabc"), + &matching_tx, + &EmptyDataProvider, + ) + .await + .unwrap(); + assert_eq!(matching_result.entries.len(), 1); + match &matching_result.entries[0] { + DisplayEntry::Item(item) => assert_eq!(item.label, "Value"), + _ => panic!("expected Item"), + } + + let mismatching = build_calldata( + "show(uint256,address,uint256)", + &[ + uint_word(2), + addr_word("0x000000000000000000000000000000000000abcd"), + uint_word(5), + ], + ); + let mismatching_tx = TransactionContext { + chain_id: 1, + to: "0xabc", + calldata: &mismatching, + value: None, + from: None, + implementation_address: None, + }; + let err = format_calldata( + &wrap_rd(descriptor, 1, "0xabc"), + &mismatching_tx, + &EmptyDataProvider, + ) + .await + .unwrap_err() + .to_string(); + assert!(err.contains("visible.mustMatch")); +} + #[tokio::test] async fn test_typed_visibility_alias_must_be_behaves_like_must_match() { let descriptor = Descriptor::from_json( From 4914bd586b7ea2cc27c45513ac2f328d8ee13a8f Mon Sep 17 00:00:00 2001 From: shukudaidayo <108971147+shukudaidayo@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:09:51 -0500 Subject: [PATCH 2/2] Simplify bundle render state --- crates/clear-signing/src/eip712.rs | 41 +++++++++++------------------- crates/clear-signing/src/engine.rs | 41 +++++++++++------------------- 2 files changed, 30 insertions(+), 52 deletions(-) diff --git a/crates/clear-signing/src/eip712.rs b/crates/clear-signing/src/eip712.rs index ba8b6e5..8ce8df9 100644 --- a/crates/clear-signing/src/eip712.rs +++ b/crates/clear-signing/src/eip712.rs @@ -453,10 +453,7 @@ fn render_typed_fields<'a>( enum TypedGroupRenderKind { Scalar(Vec), - Bundles { - len: usize, - bundles: Vec>, - }, + Bundles(Vec>), } #[allow(clippy::too_many_arguments)] @@ -559,10 +556,7 @@ fn render_typed_group_field_kind<'a>( }; bundles[index].extend(rendered); } - return Ok(TypedGroupRenderKind::Bundles { - len: items.len(), - bundles, - }); + return Ok(TypedGroupRenderKind::Bundles(bundles)); } } @@ -681,7 +675,7 @@ fn render_typed_group_kind<'a>( let all_bundles = !child_kinds.is_empty() && child_kinds .iter() - .all(|k| matches!(k, TypedGroupRenderKind::Bundles { .. })); + .all(|k| matches!(k, TypedGroupRenderKind::Bundles(_))); let items = if all_bundles { // Element-major: keep each array element's fields contiguous // (e.g. [amount0, addr0, amount1, addr1]) rather than grouping @@ -689,7 +683,7 @@ fn render_typed_group_kind<'a>( let mut sets: Vec<_> = child_kinds .into_iter() .map(|k| match k { - TypedGroupRenderKind::Bundles { bundles, .. } => bundles, + TypedGroupRenderKind::Bundles(bundles) => bundles, TypedGroupRenderKind::Scalar(_) => Vec::new(), }) .collect(); @@ -708,7 +702,7 @@ fn render_typed_group_kind<'a>( .into_iter() .flat_map(|kind| match kind { TypedGroupRenderKind::Scalar(items) => items, - TypedGroupRenderKind::Bundles { bundles, .. } => { + TypedGroupRenderKind::Bundles(bundles) => { bundles.into_iter().flatten().collect() } }) @@ -720,9 +714,7 @@ fn render_typed_group_kind<'a>( let mut bundle_sets = Vec::new(); for kind in child_kinds { match kind { - TypedGroupRenderKind::Bundles { len, bundles } => { - bundle_sets.push((len, bundles)); - } + TypedGroupRenderKind::Bundles(bundles) => bundle_sets.push(bundles), TypedGroupRenderKind::Scalar(_) => { return Err(Error::Render( "bundled groups cannot mix array-expanded and scalar fields" @@ -733,14 +725,14 @@ fn render_typed_group_kind<'a>( } if bundle_sets.is_empty() { - return Ok(TypedGroupRenderKind::Bundles { - len: 0, - bundles: Vec::new(), - }); + return Ok(TypedGroupRenderKind::Bundles(Vec::new())); } - let expected_len = bundle_sets[0].0; - if bundle_sets.iter().any(|(len, _)| *len != expected_len) { + let expected_len = bundle_sets[0].len(); + if bundle_sets + .iter() + .any(|bundles| bundles.len() != expected_len) + { return Err(Error::Render( "bundled groups require all array-expanded fields to have the same length" .to_string(), @@ -748,15 +740,12 @@ fn render_typed_group_kind<'a>( } let mut bundled = vec![Vec::new(); expected_len]; - for (_, bundles) in bundle_sets { + for bundles in bundle_sets { for (index, items) in bundles.into_iter().enumerate() { bundled[index].extend(items); } } - Ok(TypedGroupRenderKind::Bundles { - len: expected_len, - bundles: bundled, - }) + Ok(TypedGroupRenderKind::Bundles(bundled)) } } }) @@ -801,7 +790,7 @@ async fn render_typed_field_group_entries<'a>( Ok(items.into_iter().map(DisplayEntry::Item).collect()) } } - TypedGroupRenderKind::Bundles { bundles, .. } => { + TypedGroupRenderKind::Bundles(bundles) => { let items: Vec = bundles.into_iter().flatten().collect(); if items.is_empty() { return Ok(Vec::new()); diff --git a/crates/clear-signing/src/engine.rs b/crates/clear-signing/src/engine.rs index d10c40d..98f0f59 100644 --- a/crates/clear-signing/src/engine.rs +++ b/crates/clear-signing/src/engine.rs @@ -340,10 +340,7 @@ fn render_fields<'a>( enum GroupRenderKind { Scalar(Vec), - Bundles { - len: usize, - bundles: Vec>, - }, + Bundles(Vec>), } pub(crate) fn flatten_display_entry(entry: DisplayEntry) -> Vec { @@ -435,10 +432,7 @@ fn render_group_field_kind<'a>( }; bundles[i].extend(rendered); } - return Ok(GroupRenderKind::Bundles { - len: items.len(), - bundles, - }); + return Ok(GroupRenderKind::Bundles(bundles)); } } @@ -500,7 +494,7 @@ fn render_group_kind<'a>( let all_bundles = !child_kinds.is_empty() && child_kinds .iter() - .all(|k| matches!(k, GroupRenderKind::Bundles { .. })); + .all(|k| matches!(k, GroupRenderKind::Bundles(_))); let items = if all_bundles { // Element-major: keep each array element's fields contiguous // (e.g. [amount0, addr0, amount1, addr1]) rather than grouping @@ -508,7 +502,7 @@ fn render_group_kind<'a>( let mut sets: Vec<_> = child_kinds .into_iter() .map(|k| match k { - GroupRenderKind::Bundles { bundles, .. } => bundles, + GroupRenderKind::Bundles(bundles) => bundles, GroupRenderKind::Scalar(_) => Vec::new(), }) .collect(); @@ -527,7 +521,7 @@ fn render_group_kind<'a>( .into_iter() .flat_map(|kind| match kind { GroupRenderKind::Scalar(items) => items, - GroupRenderKind::Bundles { bundles, .. } => { + GroupRenderKind::Bundles(bundles) => { bundles.into_iter().flatten().collect() } }) @@ -539,9 +533,7 @@ fn render_group_kind<'a>( let mut bundle_sets = Vec::new(); for kind in child_kinds { match kind { - GroupRenderKind::Bundles { len, bundles } => { - bundle_sets.push((len, bundles)); - } + GroupRenderKind::Bundles(bundles) => bundle_sets.push(bundles), GroupRenderKind::Scalar(_) => { return Err(Error::Render( "bundled groups cannot mix array-expanded and scalar fields" @@ -552,14 +544,14 @@ fn render_group_kind<'a>( } if bundle_sets.is_empty() { - return Ok(GroupRenderKind::Bundles { - len: 0, - bundles: Vec::new(), - }); + return Ok(GroupRenderKind::Bundles(Vec::new())); } - let expected_len = bundle_sets[0].0; - if bundle_sets.iter().any(|(len, _)| *len != expected_len) { + let expected_len = bundle_sets[0].len(); + if bundle_sets + .iter() + .any(|bundles| bundles.len() != expected_len) + { return Err(Error::Render( "bundled groups require all array-expanded fields to have the same length" .to_string(), @@ -567,15 +559,12 @@ fn render_group_kind<'a>( } let mut bundled = vec![Vec::new(); expected_len]; - for (_, bundles) in bundle_sets { + for bundles in bundle_sets { for (index, items) in bundles.into_iter().enumerate() { bundled[index].extend(items); } } - Ok(GroupRenderKind::Bundles { - len: expected_len, - bundles: bundled, - }) + Ok(GroupRenderKind::Bundles(bundled)) } } }) @@ -604,7 +593,7 @@ async fn render_field_group_entries<'a>( Ok(items.into_iter().map(DisplayEntry::Item).collect()) } } - GroupRenderKind::Bundles { bundles, .. } => { + GroupRenderKind::Bundles(bundles) => { let items: Vec = bundles.into_iter().flatten().collect(); if items.is_empty() { return Ok(Vec::new());