Skip to content

Commit e80c349

Browse files
sagpatilfnando
andauthored
Support single change format in restore metadata parsing (#2168)
Handle cases where restore operation returns only one change instead of expected two changes, preventing TxSorobanInvalid errors. --------- Co-authored-by: Nando Vieira <me@fnando.com>
1 parent 8505c95 commit e80c349

File tree

1 file changed

+287
-5
lines changed

1 file changed

+287
-5
lines changed

cmd/soroban-cli/src/commands/contract/restore.rs

Lines changed: 287 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,40 @@ impl NetworkRunnable for Cmd {
239239
}
240240

241241
fn parse_changes(changes: &[LedgerEntryChange]) -> Option<u32> {
242-
match (&changes[0], &changes[1]) {
243-
(
244-
LedgerEntryChange::State(_),
242+
match changes.len() {
243+
// Handle case with 2 changes (original expected format)
244+
2 => match (&changes[0], &changes[1]) {
245+
(
246+
LedgerEntryChange::State(_),
247+
LedgerEntryChange::Restored(LedgerEntry {
248+
data:
249+
LedgerEntryData::Ttl(TtlEntry {
250+
live_until_ledger_seq,
251+
..
252+
}),
253+
..
254+
})
255+
| LedgerEntryChange::Updated(LedgerEntry {
256+
data:
257+
LedgerEntryData::Ttl(TtlEntry {
258+
live_until_ledger_seq,
259+
..
260+
}),
261+
..
262+
})
263+
| LedgerEntryChange::Created(LedgerEntry {
264+
data:
265+
LedgerEntryData::Ttl(TtlEntry {
266+
live_until_ledger_seq,
267+
..
268+
}),
269+
..
270+
}),
271+
) => Some(*live_until_ledger_seq),
272+
_ => None,
273+
},
274+
// Handle case with 1 change (single "Restored" type change)
275+
1 => match &changes[0] {
245276
LedgerEntryChange::Restored(LedgerEntry {
246277
data:
247278
LedgerEntryData::Ttl(TtlEntry {
@@ -265,8 +296,259 @@ fn parse_changes(changes: &[LedgerEntryChange]) -> Option<u32> {
265296
..
266297
}),
267298
..
268-
}),
269-
) => Some(*live_until_ledger_seq),
299+
}) => Some(*live_until_ledger_seq),
300+
_ => None,
301+
},
270302
_ => None,
271303
}
272304
}
305+
306+
#[cfg(test)]
307+
mod tests {
308+
use super::*;
309+
use crate::xdr::{Hash, LedgerEntry, LedgerEntryChange, LedgerEntryData, TtlEntry};
310+
311+
#[test]
312+
fn test_parse_changes_two_changes_restored() {
313+
// Test the original expected format with 2 changes
314+
let ttl_entry = TtlEntry {
315+
live_until_ledger_seq: 12345,
316+
key_hash: Hash([0; 32]),
317+
};
318+
319+
let changes = vec![
320+
LedgerEntryChange::State(LedgerEntry {
321+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
322+
last_modified_ledger_seq: 0,
323+
ext: crate::xdr::LedgerEntryExt::V0,
324+
}),
325+
LedgerEntryChange::Restored(LedgerEntry {
326+
data: LedgerEntryData::Ttl(ttl_entry),
327+
last_modified_ledger_seq: 0,
328+
ext: crate::xdr::LedgerEntryExt::V0,
329+
}),
330+
];
331+
332+
let result = parse_changes(&changes);
333+
assert_eq!(result, Some(12345));
334+
}
335+
336+
#[test]
337+
fn test_parse_changes_two_changes_updated() {
338+
// Test the original expected format with 2 changes, but second change is Updated
339+
let ttl_entry = TtlEntry {
340+
live_until_ledger_seq: 67890,
341+
key_hash: Hash([0; 32]),
342+
};
343+
344+
let changes = vec![
345+
LedgerEntryChange::State(LedgerEntry {
346+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
347+
last_modified_ledger_seq: 0,
348+
ext: crate::xdr::LedgerEntryExt::V0,
349+
}),
350+
LedgerEntryChange::Updated(LedgerEntry {
351+
data: LedgerEntryData::Ttl(ttl_entry),
352+
last_modified_ledger_seq: 0,
353+
ext: crate::xdr::LedgerEntryExt::V0,
354+
}),
355+
];
356+
357+
let result = parse_changes(&changes);
358+
assert_eq!(result, Some(67890));
359+
}
360+
361+
#[test]
362+
fn test_parse_changes_two_changes_created() {
363+
// Test the original expected format with 2 changes, but second change is Created
364+
let ttl_entry = TtlEntry {
365+
live_until_ledger_seq: 11111,
366+
key_hash: Hash([0; 32]),
367+
};
368+
369+
let changes = vec![
370+
LedgerEntryChange::State(LedgerEntry {
371+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
372+
last_modified_ledger_seq: 0,
373+
ext: crate::xdr::LedgerEntryExt::V0,
374+
}),
375+
LedgerEntryChange::Created(LedgerEntry {
376+
data: LedgerEntryData::Ttl(ttl_entry),
377+
last_modified_ledger_seq: 0,
378+
ext: crate::xdr::LedgerEntryExt::V0,
379+
}),
380+
];
381+
382+
let result = parse_changes(&changes);
383+
assert_eq!(result, Some(11111));
384+
}
385+
386+
#[test]
387+
fn test_parse_changes_single_change_restored() {
388+
// Test the new single change format with Restored type
389+
let ttl_entry = TtlEntry {
390+
live_until_ledger_seq: 22222,
391+
key_hash: Hash([0; 32]),
392+
};
393+
394+
let changes = vec![LedgerEntryChange::Restored(LedgerEntry {
395+
data: LedgerEntryData::Ttl(ttl_entry),
396+
last_modified_ledger_seq: 0,
397+
ext: crate::xdr::LedgerEntryExt::V0,
398+
})];
399+
400+
let result = parse_changes(&changes);
401+
assert_eq!(result, Some(22222));
402+
}
403+
404+
#[test]
405+
fn test_parse_changes_single_change_updated() {
406+
// Test the new single change format with Updated type
407+
let ttl_entry = TtlEntry {
408+
live_until_ledger_seq: 33333,
409+
key_hash: Hash([0; 32]),
410+
};
411+
412+
let changes = vec![LedgerEntryChange::Updated(LedgerEntry {
413+
data: LedgerEntryData::Ttl(ttl_entry),
414+
last_modified_ledger_seq: 0,
415+
ext: crate::xdr::LedgerEntryExt::V0,
416+
})];
417+
418+
let result = parse_changes(&changes);
419+
assert_eq!(result, Some(33333));
420+
}
421+
422+
#[test]
423+
fn test_parse_changes_single_change_created() {
424+
// Test the new single change format with Created type
425+
let ttl_entry = TtlEntry {
426+
live_until_ledger_seq: 44444,
427+
key_hash: Hash([0; 32]),
428+
};
429+
430+
let changes = vec![LedgerEntryChange::Created(LedgerEntry {
431+
data: LedgerEntryData::Ttl(ttl_entry),
432+
last_modified_ledger_seq: 0,
433+
ext: crate::xdr::LedgerEntryExt::V0,
434+
})];
435+
436+
let result = parse_changes(&changes);
437+
assert_eq!(result, Some(44444));
438+
}
439+
440+
#[test]
441+
fn test_parse_changes_invalid_two_changes() {
442+
// Test invalid 2-change format (first change is not State)
443+
let ttl_entry = TtlEntry {
444+
live_until_ledger_seq: 55555,
445+
key_hash: Hash([0; 32]),
446+
};
447+
448+
let changes = vec![
449+
LedgerEntryChange::Restored(LedgerEntry {
450+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
451+
last_modified_ledger_seq: 0,
452+
ext: crate::xdr::LedgerEntryExt::V0,
453+
}),
454+
LedgerEntryChange::Restored(LedgerEntry {
455+
data: LedgerEntryData::Ttl(ttl_entry),
456+
last_modified_ledger_seq: 0,
457+
ext: crate::xdr::LedgerEntryExt::V0,
458+
}),
459+
];
460+
461+
let result = parse_changes(&changes);
462+
assert_eq!(result, None);
463+
}
464+
465+
#[test]
466+
fn test_parse_changes_invalid_single_change() {
467+
// Test invalid single change format (not TTL data)
468+
let changes = vec![LedgerEntryChange::Restored(LedgerEntry {
469+
data: LedgerEntryData::Account(crate::xdr::AccountEntry {
470+
account_id: crate::xdr::AccountId(crate::xdr::PublicKey::PublicKeyTypeEd25519(
471+
crate::xdr::Uint256([0; 32]),
472+
)),
473+
balance: 0,
474+
seq_num: SequenceNumber(0),
475+
num_sub_entries: 0,
476+
inflation_dest: None,
477+
flags: 0,
478+
home_domain: crate::xdr::String32::default(),
479+
thresholds: crate::xdr::Thresholds::default(),
480+
signers: crate::xdr::VecM::default(),
481+
ext: crate::xdr::AccountEntryExt::V0,
482+
}),
483+
last_modified_ledger_seq: 0,
484+
ext: crate::xdr::LedgerEntryExt::V0,
485+
})];
486+
487+
let result = parse_changes(&changes);
488+
assert_eq!(result, None);
489+
}
490+
491+
#[test]
492+
fn test_parse_changes_empty_changes() {
493+
// Test empty changes array
494+
let changes = vec![];
495+
496+
let result = parse_changes(&changes);
497+
assert_eq!(result, None);
498+
}
499+
500+
#[test]
501+
fn test_parse_changes_three_changes() {
502+
// Test with 3 changes (should return None)
503+
let ttl_entry = TtlEntry {
504+
live_until_ledger_seq: 66666,
505+
key_hash: Hash([0; 32]),
506+
};
507+
508+
let changes = vec![
509+
LedgerEntryChange::State(LedgerEntry {
510+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
511+
last_modified_ledger_seq: 0,
512+
ext: crate::xdr::LedgerEntryExt::V0,
513+
}),
514+
LedgerEntryChange::Restored(LedgerEntry {
515+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
516+
last_modified_ledger_seq: 0,
517+
ext: crate::xdr::LedgerEntryExt::V0,
518+
}),
519+
LedgerEntryChange::Updated(LedgerEntry {
520+
data: LedgerEntryData::Ttl(ttl_entry),
521+
last_modified_ledger_seq: 0,
522+
ext: crate::xdr::LedgerEntryExt::V0,
523+
}),
524+
];
525+
526+
let result = parse_changes(&changes);
527+
assert_eq!(result, None);
528+
}
529+
530+
#[test]
531+
fn test_parse_changes_mixed_invalid_types() {
532+
// Test with mixed valid and invalid change types
533+
let ttl_entry = TtlEntry {
534+
live_until_ledger_seq: 77777,
535+
key_hash: Hash([0; 32]),
536+
};
537+
538+
let changes = vec![
539+
LedgerEntryChange::State(LedgerEntry {
540+
data: LedgerEntryData::Ttl(ttl_entry.clone()),
541+
last_modified_ledger_seq: 0,
542+
ext: crate::xdr::LedgerEntryExt::V0,
543+
}),
544+
LedgerEntryChange::State(LedgerEntry {
545+
data: LedgerEntryData::Ttl(ttl_entry),
546+
last_modified_ledger_seq: 0,
547+
ext: crate::xdr::LedgerEntryExt::V0,
548+
}),
549+
];
550+
551+
let result = parse_changes(&changes);
552+
assert_eq!(result, None);
553+
}
554+
}

0 commit comments

Comments
 (0)