@@ -728,9 +728,21 @@ fn build_container_command(
728728 meta : & ExtractedMetadata ,
729729 supports_locked : bool ,
730730) -> ( Vec < String > , Vec < String > ) {
731+ // Replay the recorded options in a canonical (sorted) order so the rebuild
732+ // command — and the `bldopt`/`--meta` metadata it re-records — doesn't
733+ // depend on the order they happened to be recorded in. SEP-58 requires the
734+ // metadata entries be sorted, so sorting here rebuilds to that canonical
735+ // form even when the original recorded them out of order (a non-conformant
736+ // origin). Only `--meta` order affects the WASM's metadata section, but
737+ // sorting every option is harmless and keeps the replayed command
738+ // deterministic. Mirrors the sort `contract build` applies when writing the
739+ // section.
740+ let mut bldopts = meta. bldopts . clone ( ) ;
741+ bldopts. sort ( ) ;
742+
731743 let mut forwarded: Vec < String > = Vec :: new ( ) ;
732744 let mut env: Vec < String > = Vec :: new ( ) ;
733- for o in & meta . bldopts {
745+ for o in & bldopts {
734746 // Every recorded bldopt is shell-escaped at the source (see
735747 // `build_forwarded_args` in verifiable.rs) so it's valid shell on its
736748 // own — e.g. `--meta=source_repo='github:foo'` or `--env=B='a b'`. The
@@ -756,7 +768,7 @@ fn build_container_command(
756768 source_uri : meta. source_uri . clone ( ) ,
757769 source_sha256 : meta. source_sha256 . clone ( ) ,
758770 } ;
759- let metadata = verifiable:: build_metadata_args ( & meta. bldimg , & ids, & meta . bldopts ) ;
771+ let metadata = verifiable:: build_metadata_args ( & meta. bldimg , & ids, & bldopts) ;
760772
761773 // When the image supports it, `--locked` is forced — even if the original
762774 // somehow lacked it (a non-conformant build) — so the verifier insists on a
@@ -1131,6 +1143,42 @@ mod tests {
11311143 . any( |w| w[ 0 ] == "--meta" && w[ 1 ] == "bldopt=--env=A=1" ) ) ;
11321144 }
11331145
1146+ #[ test]
1147+ fn build_container_command_sorts_replayed_bldopts ( ) {
1148+ // Recorded in a deliberately unsorted order; the replayed command (and
1149+ // the `bldopt=` meta it re-records) must come out sorted so the rebuilt
1150+ // metadata is canonical regardless of the original's recording order.
1151+ let meta = ExtractedMetadata {
1152+ bldimg : good_bldimg ( ) ,
1153+ source_uri : Some ( "https://github.com/foo/bar" . to_string ( ) ) ,
1154+ source_sha256 : Some ( "b" . repeat ( 64 ) ) ,
1155+ bldopts : vec ! [
1156+ "--meta=zebra=1" . to_string( ) ,
1157+ "--meta=apple=2" . to_string( ) ,
1158+ "--meta=mango=3" . to_string( ) ,
1159+ ] ,
1160+ } ;
1161+ let ( cmd, _env) = build_container_command ( & meta, false ) ;
1162+
1163+ // The re-recorded `bldopt=` meta values appear in sorted order.
1164+ let bldopt_metas: Vec < & String > = cmd
1165+ . windows ( 2 )
1166+ . filter ( |w| w[ 0 ] == "--meta" && w[ 1 ] . starts_with ( "bldopt=" ) )
1167+ . map ( |w| & w[ 1 ] )
1168+ . collect ( ) ;
1169+ let mut sorted = bldopt_metas. clone ( ) ;
1170+ sorted. sort ( ) ;
1171+ assert_eq ! ( bldopt_metas, sorted, "bldopt metas must be sorted: {cmd:?}" ) ;
1172+ assert_eq ! (
1173+ bldopt_metas,
1174+ vec![
1175+ "bldopt=--meta=apple=2" ,
1176+ "bldopt=--meta=mango=3" ,
1177+ "bldopt=--meta=zebra=1" ,
1178+ ]
1179+ ) ;
1180+ }
1181+
11341182 #[ test]
11351183 fn build_container_command_unescapes_quoted_meta_bldopt ( ) {
11361184 // Recorded bldopts are shell-escaped at the source, so a `--meta` value
0 commit comments