diff --git a/.gitignore b/.gitignore index 637efee1..351f9c0a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ coverage *.log .idea create_issues.sh -.claude \ No newline at end of file +.claude +.kiro/ diff --git a/contracts/lending_pool/src/lib.rs b/contracts/lending_pool/src/lib.rs index 9bda8ac6..3c83de14 100644 --- a/contracts/lending_pool/src/lib.rs +++ b/contracts/lending_pool/src/lib.rs @@ -479,6 +479,28 @@ impl LendingPool { Ok(()) } + /// Returns `(shares, current_asset_value)` for `provider` in the `token` pool. + /// + /// Net yield = `current_asset_value - original_deposit`. Since original + /// deposit amounts are not stored per-depositor, callers derive yield by + /// comparing `current_asset_value` against their own recorded cost basis. + pub fn get_depositor_yield(env: Env, provider: Address, token: Address) -> (i128, i128) { + let shares = Self::read_shares(&env, &provider, &token); + if shares == 0 { + return (0, 0); + } + let cur_total_shares = Self::total_shares(&env, &token); + if cur_total_shares == 0 { + return (shares, 0); + } + let asset_value = Self::calc_assets_to_redeem( + shares, + Self::read_pool_balance(&env, &token), + cur_total_shares, + ); + (shares, asset_value) + } + /// Underlying asset value of `provider`'s LP shares (principal + yield). pub fn get_deposit(env: Env, provider: Address, token: Address) -> i128 { let shares = Self::read_shares(&env, &provider, &token); diff --git a/contracts/lending_pool/src/test.rs b/contracts/lending_pool/src/test.rs index a62583e4..1986a31e 100644 --- a/contracts/lending_pool/src/test.rs +++ b/contracts/lending_pool/src/test.rs @@ -831,3 +831,51 @@ fn test_get_admin_returns_initialized_admin() { assert_eq!(pool_client.get_admin(), admin); } + +#[test] +fn test_get_depositor_yield_no_deposit() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, _, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + + let provider = Address::generate(&env); + assert_eq!( + pool_client.get_depositor_yield(&provider, &token_id), + (0, 0) + ); +} + +#[test] +fn test_get_depositor_yield_reflects_accrued_interest() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar_asset_client, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); + + let provider = Address::generate(&env); + stellar_asset_client.mint(&provider, &1000); + pool_client.deposit(&provider, &token_id, &1000); + + // Before any yield: asset_value == deposit amount. + let (shares, asset_value) = pool_client.get_depositor_yield(&provider, &token_id); + assert_eq!(shares, 1000); + assert_eq!(asset_value, 1000); + + // Simulate interest repaid into the pool (increases pool balance without + // minting new shares, so each share is now worth more). + stellar_asset_client.mint(&pool_id, &200); + + let (shares2, asset_value2) = pool_client.get_depositor_yield(&provider, &token_id); + assert_eq!(shares2, 1000); + assert_eq!(asset_value2, 1200); // 1000 shares * 1200 assets / 1000 total_shares +}