Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,21 @@ async fn clawback_claimable_balance() {
.assert()
.success();

// Fetch the balance ID from Horizon
let horizon_url = format!(
"http://localhost:8000/claimable_balances/?claimant={}",
claimant
);
let response = reqwest::get(&horizon_url)
.await
.expect("Failed to fetch claimable balances from Horizon");

let json: serde_json::Value = response
.json()
.await
.expect("Failed to parse Horizon response");

// Extract the balance ID from the response
let balance_id = json["_embedded"]["records"][0]["id"]
.as_str()
.expect("Failed to get balance ID from Horizon response");
// Fetch the balance ID from Horizon. Poll instead of reading once: Horizon
// ingestion lags the RPC ack, so an immediate read can find no record yet.
let horizon_url = format!("http://localhost:8000/claimable_balances/?claimant={claimant}");
let balance_id = crate::integration::util::poll_horizon_until(
&horizon_url,
|json| {
json["_embedded"]["records"][0]["id"]
.as_str()
.map(ToString::to_string)
},
|_| true,
)
.await
.expect("claimable balance never appeared on Horizon within the polling window");
let balance_id = balance_id.as_str();

// Test clawback-claimable-balance command
// this should succeed for the issuer
Expand Down
47 changes: 22 additions & 25 deletions cmd/crates/soroban-test/tests/it/integration/tx/clawback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,33 +105,30 @@ async fn clawback() {
.assert()
.success();

// Verify holder's balance after clawback (should be 500 USDC: 1000 sent - 500 clawed back)
let horizon_url = format!("http://localhost:8000/accounts/{}", holder);
let response = reqwest::get(&horizon_url)
.await
.expect("Failed to fetch account from Horizon");
let json: serde_json::Value = response
.json()
.await
.expect("Failed to parse Horizon response");

let final_balance = json["balances"]
.as_array()
.unwrap()
.iter()
.find(|balance| {
balance["asset_code"].as_str() == Some("USDC")
&& balance["asset_issuer"].as_str() == Some(&issuer)
})
.expect("USDC balance not found after clawback")["balance"]
.as_str()
.unwrap()
.parse::<f64>()
.unwrap();
// Verify holder's balance after clawback (should be 500 USDC: 1000 sent -
// 500 clawed back). Poll instead of reading once: Horizon ingestion lags
// the RPC ack, so an immediate read can still see the pre-clawback balance.
let horizon_url = format!("http://localhost:8000/accounts/{holder}");
let final_balance = crate::integration::util::poll_horizon_until(
&horizon_url,
|json| {
json["balances"].as_array()?.iter().find(|balance| {
balance["asset_code"].as_str() == Some("USDC")
&& balance["asset_issuer"].as_str() == Some(&issuer)
})?["balance"]
.as_str()?
.parse::<f64>()
.ok()
},
|balance| *balance == 500.0,
)
.await;

assert_eq!(
final_balance, 500.0,
"Holder should have 500 USDC remaining after clawback (1000 sent - 500 clawed back)"
final_balance,
Some(500.0),
"Holder should have 500 USDC remaining after clawback (1000 sent - 500 clawed back); \
last balance observed on Horizon within the polling window shown on the left"
);

// Verify that a non-issuer cannot perform clawback
Expand Down
30 changes: 30 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,36 @@ pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types")
pub const CUSTOM_ACCOUNT: &Wasm = &Wasm::Custom("test-wasms", "test_custom_account");
pub const SWAP: &Wasm = &Wasm::Custom("test-wasms", "test_swap");

/// Poll a Horizon endpoint until `extract` yields a value that `accept`s, or
/// ~30s elapse (150 tries, 200ms apart).
///
/// Horizon ingestion lags the RPC acknowledgment the CLI returns on, so a
/// read issued immediately after a submitted transaction can observe stale
/// state. On timeout the last successfully extracted value is returned (even
/// if never accepted) so the caller's assertion message can show what Horizon
/// actually reported instead of a bare `None`.
pub async fn poll_horizon_until<T>(
url: &str,
extract: impl Fn(&serde_json::Value) -> Option<T>,
accept: impl Fn(&T) -> bool,
) -> Option<T> {
let mut last = None;
for _ in 0..150 {
if let Ok(response) = reqwest::get(url).await {
if let Ok(json) = response.json::<serde_json::Value>().await {
if let Some(value) = extract(&json) {
if accept(&value) {
return Some(value);
}
last = Some(value);
}
}
}
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
last
Comment on lines +28 to +42
}

pub async fn invoke(sandbox: &TestEnv, id: &str, func: &str, data: &str) -> String {
sandbox
.invoke_with_test(&["--id", id, "--", func, &format!("--{func}"), data])
Expand Down