@@ -152,14 +152,33 @@ impl Cost {
152152 return None ;
153153 }
154154
155- // Two bytes are automatically added to the encoded witness stack by adding the annex :
155+ // Adding the annex to the witness stack increases the serialized size by :
156156 //
157- // 1. The encoded annex starts with the annex byte length
158- // 2. The first annex byte is always 0x50
157+ // 1. CompactSize(annex_len): the length prefix of the annex item
158+ // 2. annex_len: the annex bytes themselves ( 0x50 tag + zero padding)
159159 //
160- // The remaining padding is done by adding (zero) bytes to the annex.
161- let required_padding = weight - budget - U32Weight ( 2 ) ;
162- let padding_len = required_padding. 0 as usize ; // cast safety: 32-bit machine or higher
160+ // CompactSize uses 1 byte for values <= 252, 3 bytes for <= 65535,
161+ // and 5 bytes for larger values. The overhead subtracted must account
162+ // for the actual CompactSize encoding length of the resulting annex.
163+ let deficit = ( weight - budget) . 0 as usize ; // cast safety: 32-bit machine or higher
164+
165+ // overhead = compact_size_len + 1 (for 0x50 tag)
166+ let padding_len = match deficit {
167+ // annex_len <= 252, compact_size uses 1 byte, overhead = 2
168+ 0 ..=253 => deficit. saturating_sub ( 2 ) ,
169+ // Boundary region: annex must be >= 253 bytes (3-byte compact_size),
170+ // but deficit - 4 < 252. Use minimum padding for 3-byte encoding.
171+ 254 ..=255 => 252 ,
172+ // annex_len in 253..=65535, compact_size uses 3 bytes, overhead = 4
173+ 256 ..=65538 => deficit - 4 ,
174+ // Boundary region for 5-byte compact_size encoding.
175+ 65539 ..=65540 => 65535 ,
176+ // annex_len >= 65536, compact_size uses 5 bytes, overhead = 6
177+ _ => deficit - 6 ,
178+ // Note: the 9-byte compact_size boundary (deficit > 4_294_967_300)
179+ // is unreachable because Cost uses u32 milliweight, limiting the
180+ // maximum deficit to ~4_294_968 weight units.
181+ } ;
163182 let annex_bytes: Vec < u8 > = std:: iter:: once ( 0x50 )
164183 . chain ( std:: iter:: repeat ( 0x00 ) . take ( padding_len) )
165184 . collect ( ) ;
@@ -435,6 +454,31 @@ mod tests {
435454 ( Cost :: from_milliweight( empty + 4_000 ) , vec![ ] , Some ( 3 ) ) ,
436455 ( Cost :: from_milliweight( empty + 4_001 ) , vec![ ] , Some ( 4 ) ) ,
437456 ( Cost :: from_milliweight( empty + 50_000 ) , vec![ ] , Some ( 49 ) ) ,
457+ // Test around CompactSize boundary (annex_len crossing 252 -> 253)
458+ // deficit = 253: annex_len = 252, compact_size = 1 byte, overhead = 2
459+ ( Cost :: from_milliweight( empty + 253_000 ) , vec![ ] , Some ( 252 ) ) ,
460+ // deficit = 254: annex_len must be 253 (3-byte compact_size), overhead = 4
461+ ( Cost :: from_milliweight( empty + 254_000 ) , vec![ ] , Some ( 253 ) ) ,
462+ // deficit = 255: same boundary case
463+ ( Cost :: from_milliweight( empty + 255_000 ) , vec![ ] , Some ( 253 ) ) ,
464+ // deficit = 256: annex_len = 253, compact_size = 3, exact fit
465+ ( Cost :: from_milliweight( empty + 256_000 ) , vec![ ] , Some ( 253 ) ) ,
466+ // deficit = 257: annex_len = 254
467+ ( Cost :: from_milliweight( empty + 257_000 ) , vec![ ] , Some ( 254 ) ) ,
468+ // Large annex (exercises the 3-byte compact_size path)
469+ (
470+ Cost :: from_milliweight( empty + 7_424_000 ) ,
471+ vec![ ] ,
472+ Some ( 7_421 ) ,
473+ ) ,
474+ // Hash loop example
475+ (
476+ Cost :: from_milliweight( 8_045_103 ) ,
477+ vec![ vec![ ] , vec![ 0 ; 497 ] , vec![ 0 ; 32 ] , vec![ 0 ; 33 ] ] ,
478+ Some ( 7_424 ) ,
479+ ) ,
480+ // Max
481+ ( Cost :: CONSENSUS_MAX , vec![ ] , Some ( 3_999_994 ) ) ,
438482 ] ;
439483
440484 for ( cost, mut witness, maybe_padding) in test_vectors {
0 commit comments