Skip to content

Commit ac1379f

Browse files
authored
Fix contract extend panic when extending a missing entry (#2657)
1 parent 0f907ea commit ac1379f

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

cmd/crates/soroban-test/tests/it/integration/hello_world.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use predicates::prelude::PredicateBooleanExt;
12
use soroban_cli::{
23
commands::{
34
contract::{self, fetch},
@@ -342,6 +343,28 @@ async fn contract_data_read() {
342343
.stdout(predicates::str::starts_with("COUNTER,2"));
343344
}
344345

346+
#[tokio::test]
347+
async fn extend_nonexistent_entry_errors_without_panic() {
348+
// A well-formed but non-existent contract id makes the extend a no-op. The
349+
// CLI must surface a clean error rather than panic with "index out of
350+
// bounds" while inspecting the (empty) ledger entries. See issue #2599.
351+
const NONEXISTENT_ID: &str = "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4";
352+
let sandbox = &TestEnv::new();
353+
354+
sandbox
355+
.new_assert_cmd("contract")
356+
.arg("extend")
357+
.arg("--id")
358+
.arg(NONEXISTENT_ID)
359+
.arg("--ledgers-to-extend")
360+
.arg("1")
361+
.assert()
362+
.failure()
363+
.stderr(predicates::str::contains("Ledger entry not found"))
364+
.stderr(predicates::str::contains("index out of bounds").not())
365+
.stderr(predicates::str::contains("panicked").not());
366+
}
367+
345368
async fn invoke_with_seed(sandbox: &TestEnv, id: &str, seed_phrase: &str) {
346369
invoke_with_source(sandbox, seed_phrase, id).await;
347370
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,14 @@ impl Cmd {
288288
if changes.is_empty() {
289289
print.infoln("No changes detected, transaction was a no-op.");
290290
let entry = client.get_full_ledger_entries(&keys).await?;
291-
let extension = entry.entries[0].live_until_ledger_seq.unwrap_or_default();
291+
// A no-op extend against a non-existent entry returns no entries, so
292+
// avoid indexing into an empty vec (which would panic).
293+
let extension = entry
294+
.entries
295+
.first()
296+
.ok_or(Error::LedgerEntryNotFound)?
297+
.live_until_ledger_seq
298+
.unwrap_or_default();
292299

293300
return Ok(TxnResult::Res(extension));
294301
}

0 commit comments

Comments
 (0)