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
39 changes: 39 additions & 0 deletions contracts/data_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ enum DataKey {
B32(BytesN<32>),
AddrSet(BytesN<32>),
B32Set(BytesN<32>),
// Instance-tier cache variants for market config (#299)
InstanceU128(BytesN<32>),
InstanceU128(BytesN<32>),
InstanceI128(BytesN<32>),
}
Expand Down Expand Up @@ -147,6 +149,43 @@ impl DataStore {
value
}

/// Write-through cache variant for rarely-changing market config (#299).
///
/// Writes to both persistent storage (durable) and the instance-level cache
/// (cheap reads). Use for fee factors, OI caps, leverage limits, and other
/// admin-set parameters that change infrequently but are read on every order
/// execution. Subsequent `get_u128_cached` calls are served from the
/// cheaper instance entry without a persistent read.
pub fn set_u128_config(env: Env, caller: Address, key: BytesN<32>, value: u128) -> u128 {
caller.require_auth();
require_controller(&env, &caller);
env.storage().persistent().set(&DataKey::U128(key.clone()), &value);
env.storage().instance().set(&DataKey::InstanceU128(key), &value);
value
}

/// Cache-first read for market config u128 values (#299).
///
/// Checks the instance cache first. On a miss, reads from persistent storage
/// and populates the cache so subsequent reads are served without a persistent
/// round-trip. Use for the same keys managed by `set_u128_config`.
pub fn get_u128_cached(env: Env, key: BytesN<32>) -> u128 {
if let Some(v) = env
.storage()
.instance()
.get::<_, u128>(&DataKey::InstanceU128(key.clone()))
{
return v;
}
let v: u128 = env
.storage()
.persistent()
.get(&DataKey::U128(key.clone()))
.unwrap_or(0);
env.storage().instance().set(&DataKey::InstanceU128(key), &v);
v
}

pub fn remove_u128(env: Env, caller: Address, key: BytesN<32>) {
caller.require_auth();
require_controller(&env, &caller);
Expand Down
2 changes: 2 additions & 0 deletions contracts/liquidation_handler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ trait IRoleStore {
#[soroban_sdk::contractclient(name = "DataStoreClient")]
trait IDataStore {
fn get_u128(env: Env, key: BytesN<32>) -> u128;
/// Cache-first read for rarely-changing market config (issue #299).
fn get_u128_cached(env: Env, key: BytesN<32>) -> u128;
fn get_address(env: Env, key: BytesN<32>) -> Option<Address>;
}

Expand Down
32 changes: 32 additions & 0 deletions contracts/order_handler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ use soroban_sdk::{
symbol_short, Address, BytesN, Env,
};

// ─── TTL constants (#297) ─────────────────────────────────────────────────────
//
// Lazy bump: extend_ttl only fires when the remaining TTL falls below
// MIN_BUMP_THRESHOLD. Both values are in ledger sequences; at 5 s/ledger:
// PERSISTENT_BUMP_TARGET ≈ 30 days (518 400 ledgers)
// MIN_BUMP_THRESHOLD ≈ 15 days (259 200 ledgers)
//
// Only extend when current TTL < MIN_BUMP_THRESHOLD; target PERSISTENT_BUMP_TARGET.
// This halves unnecessary rent payments on busy markets (issue #297).
const PERSISTENT_BUMP_TARGET: u32 = 518_400;
const MIN_BUMP_THRESHOLD: u32 = 259_200;

// ─── Storage keys ─────────────────────────────────────────────────────────────

#[contracttype]
Expand Down Expand Up @@ -172,6 +184,8 @@ trait IRoleStore {
#[soroban_sdk::contractclient(name = "DataStoreClient")]
trait IDataStore {
fn get_u128(env: Env, key: BytesN<32>) -> u128;
/// Cache-first read for rarely-changing market config (issue #299).
fn get_u128_cached(env: Env, key: BytesN<32>) -> u128;
fn set_u128(env: Env, caller: Address, key: BytesN<32>, value: u128) -> u128;
fn apply_delta_to_u128(env: Env, caller: Address, key: BytesN<32>, delta: i128) -> u128;
fn get_i128(env: Env, key: BytesN<32>) -> i128;
Expand Down Expand Up @@ -695,6 +709,12 @@ impl OrderHandler {
updated_at_time: env.ledger().timestamp(),
};

env.storage().persistent().set(&OrderStorageKey::Order(key.clone()), &order);
env.storage().persistent().extend_ttl(
&OrderStorageKey::Order(key.clone()),
MIN_BUMP_THRESHOLD,
PERSISTENT_BUMP_TARGET,
);
env.storage()
.persistent()
.set(&OrderStorageKey::Order(key.clone()), &order);
Expand Down Expand Up @@ -1121,6 +1141,12 @@ impl OrderHandler {
order.min_output_amount = min_output_amount;
order.updated_at_time = env.ledger().timestamp();

env.storage().persistent().set(&OrderStorageKey::Order(key.clone()), &order);
env.storage().persistent().extend_ttl(
&OrderStorageKey::Order(key.clone()),
MIN_BUMP_THRESHOLD,
PERSISTENT_BUMP_TARGET,
);
env.storage()
.persistent()
.set(&OrderStorageKey::Order(key.clone()), &order);
Expand All @@ -1145,6 +1171,12 @@ impl OrderHandler {
.get(&OrderStorageKey::Order(key.clone()))
.unwrap_or_else(|| panic_with_error!(&env, Error::OrderNotFound));

env.storage().persistent().set(&OrderStorageKey::OrderFrozen(key.clone()), &true);
env.storage().persistent().extend_ttl(
&OrderStorageKey::OrderFrozen(key.clone()),
MIN_BUMP_THRESHOLD,
PERSISTENT_BUMP_TARGET,
);
env.storage()
.persistent()
.set(&OrderStorageKey::OrderFrozen(key.clone()), &true);
Expand Down
49 changes: 49 additions & 0 deletions contracts/referral_storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ use soroban_sdk::{
Bytes, BytesN, Env,
};

// ─── TTL constants (#297) ─────────────────────────────────────────────────────
// Referral codes and trader links are long-lived; bump only when TTL < 15 days.
// At 5 s/ledger: PERSISTENT_BUMP_TARGET ≈ 30 days, MIN_BUMP_THRESHOLD ≈ 15 days.
const PERSISTENT_BUMP_TARGET: u32 = 518_400;
const MIN_BUMP_THRESHOLD: u32 = 259_200;
// ─── Constants ────────────────────────────────────────────────────────────────

/// Maximum number of bytes in a referral code.
Expand Down Expand Up @@ -160,6 +165,7 @@ impl ReferralStorage {
panic_with_error!(&env, Error::CodeAlreadyTaken);
}
env.storage().persistent().set(&key, &caller);
env.storage().persistent().extend_ttl(&key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
env.events().publish_event(&CodeRegistered { caller, code });
}

Expand All @@ -174,6 +180,12 @@ impl ReferralStorage {
{
panic_with_error!(&env, Error::CodeNotFound);
}
let trader_key = ReferralKey::TraderCode(trader.clone());
env.storage().persistent().set(&trader_key, &code);
env.storage().persistent().extend_ttl(&trader_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
// Also keep the code-owner entry alive while a trader references it.
let owner_key = ReferralKey::CodeOwner(code.clone());
env.storage().persistent().extend_ttl(&owner_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
env.storage()
.persistent()
.set(&ReferralKey::TraderCode(trader.clone()), &code);
Expand All @@ -182,6 +194,13 @@ impl ReferralStorage {

/// Look up the referral code for a trader, and return the referrer's address.
pub fn get_trader_referrer(env: Env, trader: Address) -> Option<Address> {
let trader_key = ReferralKey::TraderCode(trader);
let code: BytesN<32> = env.storage().persistent().get(&trader_key)?;
env.storage().persistent().extend_ttl(&trader_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
let owner_key = ReferralKey::CodeOwner(code);
let referrer: Address = env.storage().persistent().get(&owner_key)?;
env.storage().persistent().extend_ttl(&owner_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
Some(referrer)
let code: Bytes = env
.storage()
.persistent()
Expand Down Expand Up @@ -212,6 +231,9 @@ impl ReferralStorage {
if tier > 2 {
panic_with_error!(&env, Error::InvalidTier);
}
let tier_key = ReferralKey::ReferrerTier(referrer);
env.storage().persistent().set(&tier_key, &tier);
env.storage().persistent().extend_ttl(&tier_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
env.storage()
.persistent()
.set(&ReferralKey::ReferrerTier(referrer), &tier);
Expand All @@ -231,6 +253,9 @@ impl ReferralStorage {
if tier > 2 {
panic_with_error!(&env, Error::InvalidTier);
}
let tier_key = ReferralKey::TierConfig(tier);
env.storage().persistent().set(&tier_key, &config);
env.storage().persistent().extend_ttl(&tier_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
// Validate config parameters
let discount_bps = ((config.total_rebate_bps as u64) * (config.discount_share_bps as u64) / 10000) as u32;
let rebate_bps = if config.total_rebate_bps >= discount_bps {
Expand Down Expand Up @@ -275,6 +300,28 @@ impl ReferralStorage {

/// Return the fee discount bps for a trader given their referral code, or 0 if none.
pub fn get_trader_discount_bps(env: Env, trader: Address) -> u32 {
let trader_key = ReferralKey::TraderCode(trader);
let code: BytesN<32> = match env.storage().persistent().get(&trader_key) {
Some(c) => c,
None => return 0,
};
env.storage().persistent().extend_ttl(&trader_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);

let owner_key = ReferralKey::CodeOwner(code);
let referrer: Address = match env.storage().persistent().get(&owner_key) {
Some(r) => r,
None => return 0,
};
env.storage().persistent().extend_ttl(&owner_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);

let tier_key = ReferralKey::ReferrerTier(referrer);
let tier: u32 = env.storage().persistent().get(&tier_key).unwrap_or(0);
if tier > 0 {
env.storage().persistent().extend_ttl(&tier_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);
}

let config_key = ReferralKey::TierConfig(tier);
let config: TierConfig = match env.storage().persistent().get(&config_key) {
let code: Bytes = match env
.storage()
.persistent()
Expand Down Expand Up @@ -304,6 +351,8 @@ impl ReferralStorage {
Some(c) => c,
None => return 0,
};
env.storage().persistent().extend_ttl(&config_key, MIN_BUMP_THRESHOLD, PERSISTENT_BUMP_TARGET);

// discount = total_rebate * discount_share / 10_000
config.total_rebate_bps * config.discount_share_bps / 10_000
}
Expand Down
Loading
Loading