Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ state.
| `withdraw_liquidity_multi(provider, requests)` | provider | Withdraw from several assets in one call and authorization; validates the whole batch (no duplicate assets) before applying any of it [...]
| `deregister_anchor(anchor)` | admin | Remove an anchor from the approved set |
| `pool(asset)` | – | Read aggregate pool state |
| `pool_exists(asset)` | – | Check whether a pool entry exists for an asset (i.e. liquidity has ever been provided for it) |
| `total_liquidity(asset)` | – | Read total liquidity for an asset |
| `total_liquidity_all()` | – | Read the sum of total liquidity across every asset ever funded |
| `balance(provider, asset)` | – | Read a provider's balance |
Expand Down
16 changes: 14 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,18 @@ impl AnchornetContract {
Ok(storage::get_pool(&env, &asset))
}

/// Returns `true` if a pool entry exists for `asset`, i.e. liquidity has
/// ever been provided for it via [`provide_liquidity`](Self::provide_liquidity)
/// or [`provide_liquidity_multi`](Self::provide_liquidity_multi).
///
/// This is a pure read view with no authorization requirement. It lets
/// off-chain integrators check for pool existence without catching an
/// error from [`pool`](Self::pool), mirroring the [`is_anchor`](Self::is_anchor)
/// pattern used for anchor membership.
pub fn pool_exists(env: Env, asset: Symbol) -> bool {
storage::has_pool(&env, &asset)
}

/// Returns up to `limit` assets that have ever had liquidity provided, in
/// first-use order, starting at list index `start` (0-based). Useful for
/// discovering which assets to query via [`pool`](Self::pool) or
Expand Down Expand Up @@ -1488,15 +1500,15 @@ impl<'a> AnchornetContractClient<'a> {
}

/// Backward-compatible Rust client alias for the shorter
/// `list_settlements_by_anchor_asset` on-chain export.
/// `list_settlements_by_anch_asset` on-chain export.
pub fn list_settlements_by_anchor_and_asset(
&self,
anchor: &Address,
asset: &Symbol,
start: &u64,
limit: &u32,
) -> Vec<Settlement> {
self.list_settlements_by_anchor_asset(anchor, asset, start, limit)
self.list_settlements_by_anch_asset(anchor, asset, start, limit)
}
}

Expand Down
125 changes: 125 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6353,6 +6353,131 @@ fn test_reserved_liquidity_returns_zero_for_asset_with_only_non_pending() {
assert_eq!(client.reserved_liquidity(&asset), 0);
}

// ---------------------------------------------------------------------------
// pool_exists – boolean existence view for asset pools
// ---------------------------------------------------------------------------

/// `pool_exists` returns `false` before any liquidity has been provided for
/// the asset — the pool entry does not exist yet.
#[test]
fn test_pool_exists_false_before_any_liquidity() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let asset = symbol_short!("USDC");

client.initialize(&admin);

assert!(!client.pool_exists(&asset));
}

/// `pool_exists` returns `true` once `provide_liquidity` has been called for
/// the asset, and stays `true` even after a full withdrawal empties the pool.
#[test]
fn test_pool_exists_true_after_provide_liquidity() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let anchor = Address::generate(&env);
let asset = symbol_short!("USDC");

client.initialize(&admin);
client.register_anchor(&anchor);

assert!(!client.pool_exists(&asset), "must be false before any liquidity");

client.provide_liquidity(&anchor, &asset, &1_000);

assert!(client.pool_exists(&asset), "must be true after provide_liquidity");
}

/// `pool_exists` returns `true` once `provide_liquidity_multi` has touched the
/// asset, matching the same post-condition as the single-asset entrypoint.
#[test]
fn test_pool_exists_true_after_provide_liquidity_multi() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let anchor = Address::generate(&env);
let usdc = symbol_short!("USDC");
let eurc = symbol_short!("EURC");

client.initialize(&admin);
client.register_anchor(&anchor);

assert!(!client.pool_exists(&usdc));
assert!(!client.pool_exists(&eurc));

client.provide_liquidity_multi(&anchor, &vec![&env, (usdc.clone(), 100), (eurc.clone(), 200)]);

assert!(client.pool_exists(&usdc));
assert!(client.pool_exists(&eurc));
}

/// `pool_exists` remains `true` after a full withdrawal: the pool entry persists
/// even when `total == 0`, because assets are never removed from enumeration.
#[test]
fn test_pool_exists_true_after_full_withdrawal() {
let env = Env::default();
let (client, _admin, anchor, asset) = funded(&env, 1_000);

assert!(client.pool_exists(&asset), "precondition: pool exists after funding");

client.withdraw_all_liquidity(&anchor, &asset);

assert_eq!(client.total_liquidity(&asset), 0, "pool drained");
assert!(
client.pool_exists(&asset),
"pool_exists must stay true after a full withdrawal — the entry persists",
);
}

/// `pool_exists` is independent per asset: funding one asset must not make
/// another appear to exist.
#[test]
fn test_pool_exists_is_per_asset() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let anchor = Address::generate(&env);
let usdc = symbol_short!("USDC");
let eurc = symbol_short!("EURC");

client.initialize(&admin);
client.register_anchor(&anchor);
client.provide_liquidity(&anchor, &usdc, &500);

assert!(client.pool_exists(&usdc), "USDC was funded");
assert!(!client.pool_exists(&eurc), "EURC was never funded");
}

/// `pool_exists` is consistent with `pool()`: the latter errors with
/// `PoolNotFound` exactly when `pool_exists` returns `false`, and succeeds
/// exactly when `pool_exists` returns `true`.
#[test]
fn test_pool_exists_consistent_with_pool_getter() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let anchor = Address::generate(&env);
let asset = symbol_short!("USDC");

client.initialize(&admin);
client.register_anchor(&anchor);

// Before funding: pool_exists == false ↔ pool() == PoolNotFound
assert!(!client.pool_exists(&asset));
assert_eq!(
client.try_pool(&asset).err().unwrap().unwrap(),
Error::PoolNotFound,
);

// After funding: pool_exists == true ↔ pool() succeeds
client.provide_liquidity(&anchor, &asset, &1_000);
assert!(client.pool_exists(&asset));
assert_eq!(client.pool(&asset).total, 1_000);
}

// --- hello (smoke test that setup still works after all new tests) ---

#[test]
Expand Down