provide investment information#20
Conversation
WalkthroughIntroduces lender investment tracking in the pool contract: new Investment model and storage keys; APIs for recording investments, distributing rewards, withdrawing, and querying investments/balances/rewards/history; corresponding events; and dependency bumps to soroban-sdk 22.0.0 plus a new dev-dependency blend-contract-sdk 1.22.0. Changes
Sequence Diagram(s)sequenceDiagram
actor Lender
participant PoolContract
participant Storage
participant Events
rect rgba(200,230,255,0.3)
note right of Lender: Record investment
Lender->>PoolContract: record_investment(lender, amount)
PoolContract->>Storage: add_lender_investment(lender, Investment{amount,timestamp,...})
PoolContract->>Storage: inc_lender_total_balance(lender, +amount)
PoolContract->>Events: investment_made(lender, amount, timestamp)
PoolContract-->>Lender: ok
end
rect rgba(220,255,220,0.3)
note right of Lender: Distribute rewards
Lender->>PoolContract: add_rewards(lender, index, rewards)
PoolContract->>Storage: inc_lender_total_rewards(lender, +rewards)
PoolContract->>Storage: update Investment[index].returns += rewards
PoolContract->>Events: rewards_distributed(lender, rewards)
PoolContract-->>Lender: ok
end
rect rgba(255,235,200,0.3)
note right of Lender: Withdraw investment
Lender->>PoolContract: withdraw_investment(lender, index)
PoolContract->>Storage: read Investment[index], totals
PoolContract->>Storage: inc_lender_total_balance(lender, -amount)
PoolContract->>Storage: mark Investment[index].status = withdrawn
PoolContract->>Events: withdrawal_made(lender, amount, timestamp)
PoolContract-->>Lender: ok
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
contracts/pool/Cargo.toml(1 hunks)contracts/pool/src/contract.rs(3 hunks)contracts/pool/src/events.rs(2 hunks)contracts/pool/src/storage.rs(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
contracts/pool/src/storage.rs (1)
contracts/pool/src/contract.rs (4)
get_lender_investments(334-334)get_lender_investments(701-703)get_lender_total_balance(337-337)get_lender_total_balance(705-707)
contracts/pool/src/contract.rs (2)
contracts/pool/src/storage.rs (24)
e(169-169)e(203-205)e(229-231)e(249-251)e(266-268)e(289-291)e(309-311)e(332-334)e(351-353)e(364-366)e(390-392)e(412-414)e(446-448)e(459-461)e(496-498)e(531-533)get_lender_investments(679-685)get_lender_total_balance(701-708)extend_instance(155-159)add_lender_investment(694-698)inc_lender_total_balance(717-720)set_lender_investments(688-691)inc_lender_total_rewards(739-742)get_lender_total_rewards(723-730)contracts/pool/src/events.rs (3)
investment_made(378-384)rewards_distributed(386-391)withdrawal_made(393-398)
| fn record_investment(e: Env, lender: Address, amount: i128) { | ||
| storage::extend_instance(&e); | ||
|
|
||
| // basic checks - adjust as needed | ||
| if amount <= 0 { | ||
| panic_with_error!(&e, PoolError::BadRequest); | ||
| } | ||
|
|
||
| let ts: u64 = e.ledger().timestamp() as u64; | ||
|
|
||
| let inv = Investment { | ||
| amount, | ||
| timestamp: ts, | ||
| returns: 0, | ||
| status: 0u32, // active | ||
| }; | ||
|
|
||
| // append investment | ||
| storage::add_lender_investment(&e, &lender, &inv); | ||
|
|
||
| // increment aggregate balance | ||
| storage::inc_lender_total_balance(&e, &lender, amount); | ||
|
|
||
| // emit event | ||
| PoolEvents::investment_made(&e, lender.clone(), amount, ts); | ||
| } | ||
|
|
||
| fn add_rewards(e: Env, lender: Address, index: u32, rewards: i128) { | ||
| storage::extend_instance(&e); | ||
|
|
||
| if rewards <= 0 { | ||
| panic_with_error!(&e, PoolError::BadRequest); | ||
| } | ||
|
|
||
| // load investments vec, update index | ||
| let mut invs = storage::get_lender_investments(&e, &lender); | ||
| let len = invs.len(); | ||
| if (index as usize) >= len { | ||
| panic_with_error!(&e, PoolError::BadRequest); | ||
| } | ||
|
|
||
| let mut inv = invs.get(index as usize).unwrap(); | ||
| inv.returns = inv.returns + rewards; | ||
| // update vec slot | ||
| invs.set(index as usize, inv.clone()); | ||
| storage::set_lender_investments(&e, &lender, &invs); | ||
|
|
||
| // update aggregate rewards | ||
| storage::inc_lender_total_rewards(&e, &lender, rewards); | ||
|
|
||
| // emit event | ||
| PoolEvents::rewards_distributed(&e, lender.clone(), rewards); | ||
| } | ||
|
|
||
| fn withdraw_investment(e: Env, lender: Address, index: u32) { | ||
| storage::extend_instance(&e); | ||
|
|
||
| let mut invs = storage::get_lender_investments(&e, &lender); | ||
| let len = invs.len(); | ||
| if (index as usize) >= len { | ||
| panic_with_error!(&e, PoolError::BadRequest); | ||
| } | ||
|
|
||
| let mut inv = invs.get(index as usize).unwrap(); | ||
| if inv.status != 0u32 { | ||
| // already withdrawn | ||
| panic_with_error!(&e, PoolError::BadRequest); | ||
| } | ||
|
|
||
| inv.status = 1u32; // withdrawn | ||
| invs.set(index as usize, inv.clone()); | ||
| storage::set_lender_investments(&e, &lender, &invs); | ||
|
|
||
| // decrement aggregate balance | ||
| storage::inc_lender_total_balance(&e, &lender, -inv.amount); | ||
|
|
||
| // emit event | ||
| let ts: u64 = e.ledger().timestamp() as u64; | ||
| PoolEvents::withdrawal_made(&e, lender.clone(), inv.amount, ts); | ||
| } |
There was a problem hiding this comment.
Investment mutators need authentication or non-public exposure
record_investment, add_rewards, and withdraw_investment are now in the public Pool trait (Lines 620-699), so anyone can invoke them directly. None of them call require_auth, which lets an attacker (1) credit themselves arbitrary balances via record_investment, (2) mint rewards with add_rewards, or (3) zero out another lender’s position using withdraw_investment. This is a critical authorization hole. Either keep these helpers internal (remove from the trait) or gate them with the appropriate require_auth checks aligned with the supply/reward flows.
| pub fn investment_made(e: &Env, lender: &Address, amount: i128, timestamp: u64) { | ||
| // Topic: ("InvestmentMade", lender) | ||
| e.events().publish( | ||
| (Symbol::short("InvestmentMade"), lender.clone()), | ||
| (amount, timestamp), | ||
| ); | ||
| } | ||
|
|
||
| pub fn rewards_distributed(e: &Env, lender: &Address, amount: i128) { | ||
| e.events().publish( | ||
| (Symbol::short("RewardsDistributed"), lender.clone()), | ||
| amount, | ||
| ); | ||
| } | ||
|
|
||
| pub fn withdrawal_made(e: &Env, lender: &Address, amount: i128, timestamp: u64) { | ||
| e.events().publish( | ||
| (Symbol::short("WithdrawalMade"), lender.clone()), | ||
| (amount, timestamp), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Replace Symbol::short with length-compliant symbols for new events
Symbol::short only accepts ASCII strings up to 9 characters. The literals "InvestmentMade", "RewardsDistributed", and "WithdrawalMade" (Lines 381, 388, 395) all exceed that limit, so these calls will panic every time the events fire. Please switch to Symbol::new(&e, "...") (or shorten the identifiers to ≤9 chars) before shipping.
🤖 Prompt for AI Agents
In contracts/pool/src/events.rs around lines 378 to 398, the calls to
Symbol::short use string literals longer than 9 chars which will panic; replace
each Symbol::short("InvestmentMade"), Symbol::short("RewardsDistributed"), and
Symbol::short("WithdrawalMade") with Symbol::new(&e, "<same-name>") (or
alternatively shorten each identifier to ≤9 ASCII chars) so the symbols are
length-compliant; update the three event publish calls accordingly to use
Symbol::new(&e, "...") with the same readable names.
| const KEY_LENDER_INVESTMENTS: Symbol = Symbol::short("lender_invs"); // (lender -> Vec<Investment>) | ||
| const KEY_LENDER_TOTAL_BAL: Symbol = Symbol::short("lender_bal"); // (lender -> i128) | ||
| const KEY_LENDER_TOTAL_REW: Symbol = Symbol::short("lender_rew"); // (lender -> i128) | ||
|
|
||
| /// Return the investments vec for a lender (or an empty Vec if not set). | ||
| pub fn get_lender_investments(e: &Env, lender: &Address) -> Vec<Investment> { | ||
| let key = (KEY_LENDER_INVESTMENTS.clone(), lender.clone()); | ||
| e.storage() | ||
| .instance() | ||
| .get(&key) | ||
| .unwrap_or_else(|_| Vec::new(e)) | ||
| } | ||
|
|
||
| /// Persist the investments vec for a lender. | ||
| pub fn set_lender_investments(e: &Env, lender: &Address, invs: &Vec<Investment>) { | ||
| let key = (KEY_LENDER_INVESTMENTS.clone(), lender.clone()); | ||
| e.storage().instance().set(&key, invs); | ||
| } | ||
|
|
||
| /// Append a new investment to a lender's investment vector and persist. | ||
| pub fn add_lender_investment(e: &Env, lender: &Address, inv: &Investment) { | ||
| let mut vec = get_lender_investments(e, lender); | ||
| vec.push_back(inv.clone()); | ||
| set_lender_investments(e, lender, &vec); | ||
| } | ||
|
|
||
| /// Get lender total balance (aggregated active amounts). Returns 0 if none. | ||
| pub fn get_lender_total_balance(e: &Env, lender: &Address) -> i128 { | ||
| let key = (KEY_LENDER_TOTAL_BAL.clone(), lender.clone()); | ||
| e.storage() | ||
| .instance() | ||
| .get(&key) | ||
| .unwrap_or(Ok(0i128)) | ||
| .unwrap() | ||
| } | ||
|
|
||
| /// Set lender total balance. | ||
| pub fn set_lender_total_balance(e: &Env, lender: &Address, bal: &i128) { | ||
| let key = (KEY_LENDER_TOTAL_BAL.clone(), lender.clone()); | ||
| e.storage().instance().set(&key, bal); | ||
| } | ||
|
|
||
| /// Increase lender total balance by delta (can be negative to decrease). | ||
| pub fn inc_lender_total_balance(e: &Env, lender: &Address, delta: i128) { | ||
| let cur = get_lender_total_balance(e, lender); | ||
| set_lender_total_balance(e, lender, &(cur + delta)); | ||
| } | ||
|
|
||
| /// Get lender total rewards (aggregated). Returns 0 if none. | ||
| pub fn get_lender_total_rewards(e: &Env, lender: &Address) -> i128 { | ||
| let key = (KEY_LENDER_TOTAL_REW.clone(), lender.clone()); | ||
| e.storage() | ||
| .instance() | ||
| .get(&key) | ||
| .unwrap_or(Ok(0i128)) | ||
| .unwrap() | ||
| } | ||
|
|
||
| /// Set lender total rewards. | ||
| pub fn set_lender_total_rewards(e: &Env, lender: &Address, rew: &i128) { | ||
| let key = (KEY_LENDER_TOTAL_REW.clone(), lender.clone()); | ||
| e.storage().instance().set(&key, rew); | ||
| } | ||
|
|
||
| /// Increase lender total rewards by delta. | ||
| pub fn inc_lender_total_rewards(e: &Env, lender: &Address, delta: i128) { | ||
| let cur = get_lender_total_rewards(e, lender); | ||
| set_lender_total_rewards(e, lender, &(cur + delta)); | ||
| } |
There was a problem hiding this comment.
Symbol::short constants exceed the permitted 9-character length
Symbol::short("lender_invs"), "lender_bal", and "lender_rew" (Lines 675-677) are all ≥10 characters. Symbol::short will panic when the contract loads these keys, breaking every storage call beneath. Please shorten the identifiers to ≤9 characters (e.g., "lend_invs") or create the symbols at runtime with Symbol::new(&e, ...).
🤖 Prompt for AI Agents
In contracts/pool/src/storage.rs around lines 674 to 742, the three
Symbol::short calls use identifiers >=10 chars which will panic at contract
load; replace them with either shorter identifiers (<=9 chars) like "lend_invs",
"lend_bal", "lend_rew" or change the constants to be created at runtime using
Symbol::new(&e, "<full_name>") and update all uses accordingly so storage keys
no longer trigger Symbol::short length panics.
Pull Request for TrustBridge - Close Issue
❗ Pull Request Information
Implement lender investment tracking in the Soroban Rust Pool contract to support real-time lender dashboards.
Add here some information
🌀 Summary of Changes
Added Investment struct in storage.rs to represent individual lender investments (amount, timestamp, returns, status).
Storage helpers to create, update, and query lender investments, total balances, and rewards.
Event emitters (InvestmentMade, RewardsDistributed, WithdrawalMade) in events.rs for front-end/indexer tracking.
Contract view functions in contract.rs:
get_lender_investments
get_lender_total_balance
get_lender_rewards
get_lender_investment_history
Record new investments when lenders supply funds.
Update rewards distribution.
Mark investments as withdrawn and adjust aggregates.
Add here the changes:
More changes:
🛠 Testing
Evidence Before Solution
Evidence After Solution
✅ ESLint Compliance (Mandatory)
To ensure that the code follows project standards, please run the following command and attach a screenshot of the output:
You should see:
📸 Attach a screenshot showing the result of the lint check:
📂 Related Issue
This pull request will **close #13 ** upon merging.
🎉 Thank you for reviewing this PR! 🎉
Summary by CodeRabbit
New Features
Chores