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
67 changes: 66 additions & 1 deletion packages/contracts/blend-adapter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ use soroban_sdk::{
const POOL_KEY: Symbol = symbol_short!("POOL");
const TOTAL_KEY: Symbol = symbol_short!("TOTAL");

// TTL bump targets. A "day" is ~17,280 ledgers at ~5 s/ledger.
const DAY_IN_LEDGERS: u32 = 17_280;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DAY_IN_LEDGERS/INSTANCE_BUMP/INSTANCE_THRESHOLD and extend_instance() are duplicated verbatim here and in defindex-adapter/src/lib.rs, even though both crates already depend on adapter-common, built for exactly this kind of shared scaffolding (see VAULT_KEY, require_vault_auth, get_or_not_initialized there). A future change to the TTL bump policy, e.g. adjusting the 30-day window, has to be made in both files and can silently drift out of sync. Please move these into adapter-common.

const INSTANCE_BUMP: u32 = 30 * DAY_IN_LEDGERS;
const INSTANCE_THRESHOLD: u32 = INSTANCE_BUMP - DAY_IN_LEDGERS;

// Blend RequestType constants, per
// blend-contracts-v2/pool/src/request_type.rs (submitted against the pool's
// `submit`). The "collateral" suffix is deliberate and load-bearing: this
Expand Down Expand Up @@ -221,6 +226,7 @@ impl MeridianBlendAdapter {
/// tracks genuine, appreciating shares instead of raw principal (#486).
pub fn deposit(env: Env, amount: i128) -> i128 {
require_vault_auth(&env);
Self::extend_instance(&env);

let pool: Address = adapter_common::get_or_not_initialized::<_, ContractError>(
&env,
Expand Down Expand Up @@ -300,6 +306,7 @@ impl MeridianBlendAdapter {
/// measured directly rather than assumed to equal the request (#489).
pub fn withdraw(env: Env, shares: i128, recipient: Address) -> i128 {
require_vault_auth(&env);
Self::extend_instance(&env);

let pool: Address = adapter_common::get_or_not_initialized::<_, ContractError>(
&env,
Expand Down Expand Up @@ -361,6 +368,8 @@ impl MeridianBlendAdapter {
/// (`get_positions`) rather than self-tracking it, so there is no risk of
/// drift between the stored total and Blend's actual accounting.
pub fn accrue(env: Env) -> Result<(), ContractError> {
Self::extend_instance(&env);

let pool: Address = env
.storage()
.instance()
Expand Down Expand Up @@ -446,6 +455,15 @@ impl MeridianBlendAdapter {
pub fn get_protocol(env: Env) -> Symbol {
Symbol::new(&env, "blend")
}

/// Extends the TTL of the contract instance. Called at the start of
/// every state-changing entry point so the adapter's configuration
/// never expires while it is actively used.
fn extend_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_THRESHOLD, INSTANCE_BUMP);
}
}

// ---------------------------------------------------------------------------
Expand All @@ -457,7 +475,7 @@ mod tests {
use super::*;
use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, Events, MockAuth, MockAuthInvoke},
testutils::{Address as _, Events, Ledger as _, MockAuth, MockAuthInvoke},
token::{StellarAssetClient, TokenClient},
Address, Env,
};
Expand Down Expand Up @@ -1085,4 +1103,51 @@ mod tests {
soroban_sdk::TryIntoVal::try_into_val(&accrue_event.2, &env).unwrap();
assert_eq!(data, (expected_prev, expected_new));
}

#[test]
fn deposit_extends_instance_ttl() {
let (env, vault, usdc_id, adapter, _pool) = setup();
let adapter_id = adapter.address.clone();
let amount = 100_0000000_i128;
TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter_id, &amount);
adapter.deposit(&amount);
env.ledger()
.with_mut(|li| li.sequence_number += INSTANCE_THRESHOLD - 1);
env.as_contract(&adapter_id, || {
assert!(env.storage().instance().has(&TOTAL_KEY));
});
}

#[test]
fn withdraw_extends_instance_ttl() {
let (env, vault, usdc_id, adapter, _pool) = setup();
let adapter_id = adapter.address.clone();
let amount = 100_0000000_i128;
TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter_id, &amount);
adapter.deposit(&amount);
let recipient = Address::generate(&env);
adapter.withdraw(&amount, &recipient);
env.ledger()
.with_mut(|li| li.sequence_number += INSTANCE_THRESHOLD - 1);
env.as_contract(&adapter_id, || {
assert!(env.storage().instance().has(&TOTAL_KEY));
});
}

#[test]
fn accrue_extends_instance_ttl() {
let (env, vault, usdc_id, adapter, pool) = setup();
let adapter_id = adapter.address.clone();
let amount = 100_0000000_i128;
TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter_id, &amount);
adapter.deposit(&amount);
let new_rate = SCALAR + SCALAR / 10;
pool.set_rate(&new_rate);
adapter.accrue();
env.ledger()
.with_mut(|li| li.sequence_number += INSTANCE_THRESHOLD - 1);
env.as_contract(&adapter_id, || {
assert!(env.storage().instance().has(&TOTAL_KEY));
});
}
}
50 changes: 49 additions & 1 deletion packages/contracts/defindex-adapter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ use soroban_sdk::{

const DFX_VAULT: Symbol = symbol_short!("DFXVAULT");

// TTL bump targets. A "day" is ~17,280 ledgers at ~5 s/ledger.
const DAY_IN_LEDGERS: u32 = 17_280;
const INSTANCE_BUMP: u32 = 30 * DAY_IN_LEDGERS;
const INSTANCE_THRESHOLD: u32 = INSTANCE_BUMP - DAY_IN_LEDGERS;

// ---------------------------------------------------------------------------
// DeFindex vault interface
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -137,6 +142,7 @@ impl MeridianDefindexAdapter {
/// returns the dfToken shares received.
pub fn deposit(env: Env, amount: i128) -> i128 {
require_vault_auth(&env);
Self::extend_instance(&env);

let dfx: Address = adapter_common::get_or_not_initialized::<_, ContractError>(
&env,
Expand Down Expand Up @@ -169,6 +175,7 @@ impl MeridianDefindexAdapter {
/// `recipient`. Returns the USDC amount received.
pub fn withdraw(env: Env, shares: i128, recipient: Address) -> i128 {
require_vault_auth(&env);
Self::extend_instance(&env);

let dfx: Address = adapter_common::get_or_not_initialized::<_, ContractError>(
&env,
Expand All @@ -194,6 +201,8 @@ impl MeridianDefindexAdapter {
/// Live USDC value of the adapter's dfToken position, computed by the
/// DeFindex vault's exchange rate. Updates automatically as yield accrues.
pub fn total_assets(env: Env) -> i128 {
Self::extend_instance(&env);

let dfx: Address = adapter_common::get_or_not_initialized::<_, ContractError>(
&env,
env.storage().instance().get(&DFX_VAULT),
Expand Down Expand Up @@ -241,6 +250,15 @@ impl MeridianDefindexAdapter {
pub fn get_protocol(env: Env) -> Symbol {
Symbol::new(&env, "defindex")
}

/// Extends the TTL of the contract instance. Called at the start of
/// every state-changing entry point so the adapter's configuration
/// never expires while it is actively used.
fn extend_instance(env: &Env) {
env.storage()
.instance()
.extend_ttl(INSTANCE_THRESHOLD, INSTANCE_BUMP);
}
}

// ---------------------------------------------------------------------------
Expand All @@ -252,7 +270,7 @@ mod tests {
use super::*;
use soroban_sdk::{
contract, contractimpl, symbol_short,
testutils::Address as _,
testutils::{Address as _, Ledger as _},
token::{StellarAssetClient, TokenClient},
Address, Env,
};
Expand Down Expand Up @@ -677,4 +695,34 @@ mod tests {
let _ = ContractError::Overflow;
let _ = ContractError::NotInitialized;
}

#[test]
fn deposit_extends_instance_ttl() {
let (env, vault, usdc_id, adapter, _dfx) = setup();
let adapter_id = adapter.address.clone();
let amount = 100_0000000_i128;
TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter_id, &amount);
adapter.deposit(&amount);
env.ledger()
.with_mut(|li| li.sequence_number += INSTANCE_THRESHOLD - 1);
env.as_contract(&adapter_id, || {
assert!(env.storage().instance().has(&DFX_VAULT));
});
}

#[test]
fn withdraw_extends_instance_ttl() {
let (env, vault, usdc_id, adapter, _dfx) = setup();
let adapter_id = adapter.address.clone();
let amount = 100_0000000_i128;
TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter_id, &amount);
adapter.deposit(&amount);
let recipient = Address::generate(&env);
adapter.withdraw(&amount, &recipient);
env.ledger()
.with_mut(|li| li.sequence_number += INSTANCE_THRESHOLD - 1);
env.as_contract(&adapter_id, || {
assert!(env.storage().instance().has(&DFX_VAULT));
});
}
}
Loading
Loading