Skip to content

feat(soroban): split idle and async withdrawals#423

Merged
carrion256 merged 6 commits into
devfrom
feat/soroban-idle-withdraw-async-requests
Jun 5, 2026
Merged

feat(soroban): split idle and async withdrawals#423
carrion256 merged 6 commits into
devfrom
feat/soroban-idle-withdraw-async-requests

Conversation

@carrion256

@carrion256 carrion256 commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR makes Soroban vault withdrawal semantics explicit:

  • withdraw / redeem command flow is atomic and idle-liquidity-only.
  • request_withdraw remains the async user path for queued exits.
  • execute_withdraw is keeper/allocator progression and now only advances a cooled-down queue head when it is fully backed by idle assets.
  • queued withdrawal execution no longer partially settles from idle assets; insufficient idle liquidity fails atomically and leaves the queue unchanged.
  • docs and justfile wording now distinguish atomic idle exits from async withdrawal requests.

Validation

  • cargo test -p templar-soroban-shared-types -- --nocapture
  • cargo test -p templar-vault-kernel --features all-actions -- --nocapture
  • cargo test -p templar-soroban-runtime -- --nocapture
  • just -f contract/vault/soroban/justfile build
  • just -f contract/vault/soroban/justfile size-budget-check

Size gate: runtime deploy WASM is 98522 bytes, under the 131072 byte budget.


This change is Reviewable

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9d91dac-7179-4877-a922-33bce6056535

📥 Commits

Reviewing files that changed from the base of the PR and between f13fd68 and 2f08907.

📒 Files selected for processing (12)
  • contract/vault/kernel/src/actions/mod.rs
  • contract/vault/kernel/src/actions/tests.rs
  • contract/vault/soroban/README.md
  • contract/vault/soroban/STRIDE.md
  • contract/vault/soroban/justfile
  • contract/vault/soroban/scripts/vault_payload.py
  • contract/vault/soroban/shared-types/src/lib.rs
  • contract/vault/soroban/src/contract/entrypoints.rs
  • contract/vault/soroban/src/contract/mod.rs
  • contract/vault/soroban/src/fungible_vault.rs
  • contract/vault/soroban/src/tests.rs
  • contract/vault/soroban/tests/integration_tests.rs

📝 Walkthrough

Summary

This PR separates Soroban vault withdrawal semantics into two explicit paths: atomic idle-only exits (withdraw/redeem) that must succeed or fail instantly, and async queued exits (request_withdraw + execute_withdraw) that escrow shares and use fixed-price settlement. Changes include new kernel action variants (AtomicWithdraw, AtomicRedeem), new error code ExecuteWithdrawInsufficientIdleAssets, updated liquidity checks, and comprehensive documentation/test updates.

Critical Issues Requiring Review

1. Authorization Bypass: Atomic Operations Missing RBAC Check ⚠️

atomic_withdraw_impl and atomic_redeem_impl do not call vault.authorize(ActionKind::AtomicWithdraw/Redeem, caller) before executing kernel actions. All other privileged operations (Deposit, RequestWithdraw, ExecuteWithdraw, BeginAllocating, etc.) explicitly authorize callers, but atomic operations skip this. The STRIDE.md documents that execute_withdraw requires "RBAC Allocator," but the implementation only calls require_signed(), making it permissionless among any signed caller. Similarly, atomic operations appear to have no role gating. This violates the principle that state-mutating operations must enforce access control.

2. Liquidity Check Regression: Removed Minimum Threshold Gating

The simplified has_actionable_withdrawal_liquidity(expected_assets, available_assets) now requires only available_assets >= expected_assets and no longer references min_withdrawal_assets. The plan_idle_payout() function explicitly ignores the min_withdrawal_assets parameter (let _ = min_withdrawal_assets). If min_withdrawal_assets is intended as a minimum-settlement-size floor, this removal could allow inefficient micro-settlements. Confirm whether this is intentional or a regression.

3. Slippage Locking Without Re-Validation

Queued withdrawals capture expected_assets at request time using the share-price snapshot. At execution, this fixed value is paid regardless of current share price. If the vault's share price declines (e.g., due to losses), queued withdrawers receive more in absolute terms than the current value of their escrowed shares. This is flagged as intentional by the bot but should be prominently documented as a trade-off: it protects queued withdrawers from post-request losses but subsidizes them via remaining shareholders. Integrators must understand this risk.

4. No User Cancellation Path

Once shares are escrowed via request_withdraw, no user-callable cancellation exists. Recovery requires keeper intervention or governance action. This is acceptable for a stable design but creates operational risk if a queued withdrawal is delayed indefinitely due to liquidity bottlenecks.

5. Implicit Withdrawal Settlement via Allocation Completion

The bot reported that handle_finish_allocating was changed to stop auto-chaining into withdrawal progression. Verify that removal is intentional and that the PR description's claim that "FinishAllocating still auto-chains into withdrawal progression when conditions are met" is accurate or if this is a breaking change to integrator automation assumptions.

Code Quality Observations

  • Kernel Action Dispatch: AtomicWithdraw and AtomicRedeem are correctly integrated into the kernel action enum and dispatcher, with proper handler stubs in place.
  • State Consistency: Tests verify that failed execute_withdraw with insufficient idle assets leaves the queue head's expected_assets unchanged, maintaining state integrity.
  • Documentation: README and STRIDE.md clearly separate atomic vs. async paths; justfile examples are updated.
  • Test Coverage: New tests added for atomic operations when liquidity is in idle vs. external assets, and for refusal of partial settlement.

Recommendations for Maintainers

  1. Immediate: Add vault.authorize() calls to atomic_withdraw and atomic_redeem callsites, and confirm the intended action kind (or create new ActionKind::AtomicWithdraw/Redeem variants if distinct authorization logic is needed).
  2. Clarify: Document whether min_withdrawal_assets removal is intentional and update plan_idle_payout() signature to remove the now-unused parameter.
  3. Audit: Review execution paths for execute_withdraw to confirm it respects allocator RBAC, since the entrypoint currently only checks require_signed().
  4. Integration: Confirm with integrators that dual settlement paths (explicit execute_withdraw and implicit via FinishAllocating) do not break downstream automation tracking.

Walkthrough

Adds atomic withdraw/redeem commands that exit only from idle assets, tightens kernel gating so queued execute_withdraw/idle payouts require full idle funding, stops allocation completion from chaining into withdrawals, and updates entrypoints, codec/CLI, tests, fixtures, docs, and recipes to match the new semantics.

Changes

Atomic Withdrawal and Redemption Implementation

Layer / File(s) Summary
Kernel queued-withdraw liquidity & finish-allocation
contract/vault/kernel/src/actions/mod.rs, contract/vault/kernel/src/actions/tests.rs
Actionable queued withdrawals now require available_assets >= expected_assets; plan_idle_payout uses state.idle_assets; handle_finish_allocating no longer chains into withdrawals; tests updated to assert refusal/no-partial-settlement and restricted-head retention.
VaultCommand schema, codec, and CLI encoder
contract/vault/soroban/shared-types/src/lib.rs, contract/vault/soroban/scripts/vault_payload.py, tests
Adds AtomicWithdraw/AtomicRedeem variants with tags 6/7, extends encode/decode and roundtrip/trailing-byte tests, and adds CLI subcommands/encoders for the new commands.
Entrypoints and proxy_view wiring
contract/vault/soroban/src/contract/entrypoints.rs, contract/vault/soroban/src/contract/mod.rs, contract/vault/soroban/src/fungible_vault.rs
Implements atomic_withdraw_impl/atomic_redeem_impl, dispatches new commands and returns i128 results, updates proxy_view to compute per-owner atomic limits when idle/unpaused, and adds share_balance helper plus related docs.
Soroban tests and fixture wiring
contract/vault/soroban/src/tests.rs, contract/vault/soroban/tests/integration_tests.rs
Adjusts low-liquidity test setups and expectations to refuse partial settlements when idle is insufficient, adds atomic command success/failure tests (idle vs external liquidity), and updates integration fixture to pass/store share token and assert non-zero per-owner proxy previews.
Docs, diagrams, and recipes
contract/vault/soroban/STRIDE.md, contract/vault/soroban/README.md, contract/vault/soroban/justfile
Clarifies atomic vs queued withdrawal flows, introduces Keeper/Allocator role, updates sequence diagrams and STRIDE interaction mapping, and adds an atomic withdraw recipe plus demo/help updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • peer2f00l
  • royalf00l
  • SmolinPavel
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/soroban-idle-withdraw-async-requests

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands and usage tips.

@carrion256

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@carrion256

Copy link
Copy Markdown
Collaborator Author

@coderabbitai for the benefit of the human reviewers - please output a description of this PR and any critique on the usability of the design when you are reviewing.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me examine the actual code changes in this PR to give the human reviewers a thorough description and design critique.


🏁 Script executed:

#!/bin/bash
# Get the list of changed files in this PR
git diff --name-only origin/main...HEAD

echo "=== FULL DIFF STAT ==="
git diff --stat origin/main...HEAD

Length of output: 421


🏁 Script executed:

#!/bin/bash
echo "=== BRANCHES ==="
git branch -a

echo "=== RECENT COMMITS ==="
git log --oneline -10

echo "=== CHANGED FILES (last commit range) ==="
git diff HEAD~5...HEAD --name-only 2>/dev/null || git show --name-only --pretty=format: HEAD

Length of output: 1996


🏁 Script executed:

#!/bin/bash
echo "=== entrypoints.rs ==="
cat contract/vault/soroban/src/contract/entrypoints.rs

echo ""
echo "=== actions/mod.rs ==="
cat contract/vault/kernel/src/actions/mod.rs

echo ""
echo "=== kernel/error.rs ==="
cat contract/vault/kernel/src/error.rs

echo ""
echo "=== fungible_vault.rs ==="
cat contract/vault/soroban/src/fungible_vault.rs

Length of output: 124994


🏁 Script executed:

#!/bin/bash
echo "=== README.md ==="
cat contract/vault/soroban/README.md

echo ""
echo "=== STRIDE.md ==="
cat contract/vault/soroban/STRIDE.md

echo ""
echo "=== kernel actions tests (brief) ==="
head -200 contract/vault/kernel/src/actions/tests.rs

Length of output: 57489


@carrion256 Here is a structured description and design critique for the human reviewers.


PR Description

This PR makes the withdrawal semantics of the Soroban vault explicit by splitting previously ambiguous code paths into distinct, clearly-bounded operations.

What changed

Three withdrawal verbs, three clear contracts:

Verb Path Semantics
withdraw / redeem AtomicWithdraw / AtomicRedeem Idle-only, atomic, instant. Fails if vault is not Idle or idle_assets < requested. No queue involvement.
request_withdraw RequestWithdraw Async enqueue. Escrows shares, records expected_assets at current share price, starts cooldown.
execute_withdraw ExecuteWithdraw Keeper/allocator progression. Advances the cooled-down queue head only if it is fully covered by idle assets. Fails atomically otherwise — the queue is left untouched.

Before this PR: execute_withdraw could partially settle from idle assets. After this PR: it is all-or-nothing.

New error code added:

ExecuteWithdrawInsufficientIdleAssets = 41
→ "execute_withdraw insufficient idle_assets"

New kernel actions: AtomicWithdraw and AtomicRedeem are now first-class KernelAction enum variants with the standard state machine/effects pipeline.

handle_finish_allocating retains auto-chaining: when an allocation completes (FinishAllocating), the kernel still looks ahead and begins a withdrawal transition if the queue head is cooled down and fully backed — so a keeper completing an allocation can implicitly settle a queued withdrawal without a separate execute_withdraw call.

max_withdraw / max_redeem in proxy_view: now bounded to idle_assets rather than total share value, reflecting the new atomic-only semantics for these ERC-4626 entrypoints.


Design Critique

1. Slippage locked at request time, not execution time

min_assets_out is validated against the current share price at request_withdraw time and stored as expected_assets. At execute_withdraw time, the kernel simply pays expected_assets without re-checking whether that amount still reflects the shares' fair value. If the share price has declined between request and execution (e.g., due to market losses or fee accrual diluting shares), the queued withdrawer still receives expected_assets, which may now exceed their shares' current worth — extracting value from remaining depositors.

This is a known ERC-4626 async withdrawal design trade-off. It should be explicitly acknowledged in the STRIDE threat model or a comment explaining the intentional trade-off (protect the withdrawer's slippage expectation vs. protect remaining depositors).

2. No user self-escape from the queue

Once a user calls request_withdraw, their shares are escrowed and the request is queued. If execute_withdraw repeatedly fails due to insufficient idle liquidity, the user's capital is locked with no user-callable cancellation path. Recovery requires either:

  • The keeper freeing enough liquidity (allocator path), or
  • EmergencyReset, which is governance-gated and has broader operational impact.

A cancel_withdraw(request_id) entrypoint returning escrowed shares to the owner would significantly improve UX, especially for the failure mode where idle_assets never recovers enough to cover the queue head. Even if intentionally deferred, this should be documented as a known gap.

3. execute_withdraw is fully permissionless (any signer)

execute_withdraw_impl calls require_signed(&caller) but has no RBAC role check — any signed Soroban account can call it. This is consistent with keeper design (open competition among keepers is desirable), but it differs from the allocator-gated allocate_* flows. This should be confirmed as intentional. The STRIDE table (I3) currently lists require_auth(caller) + RBAC Allocator for execute_withdraw, which appears stale relative to the implementation.

4. finish_allocating dual-path may surprise integrators

There are two code paths that can silently advance the withdrawal queue:

  1. Explicit: keeper calls execute_withdraw.
  2. Implicit: keeper calls allocate_withdraw / FinishAllocating, which auto-chains into the queue head if it is now fully covered.

An off-chain keeper that monitors only execute_withdraw transactions may miss silent settlements triggered via path (2). Integrators building dashboards or reconciliation logic need to index FinishAllocating events as a potential withdrawal settlement trigger. This should be explicitly documented in the README/justfile comment.

5. No quantitative retry hint on execute_withdraw failure

When execute_withdraw fails with ExecuteWithdrawInsufficientIdleAssets, the error carries no information about the shortfall magnitude. The keeper must independently query the queue head (expected_assets) and idle_assets to compute how much additional liquidity to free. A richer error variant — e.g., { needed: u128, available: u128 } — would make keeper automation simpler and reduce round trips.

6. ERC-4626 maxWithdraw deviation needs prominent documentation

ERC-4626 consumers typically expect maxWithdraw(owner) to reflect the owner's full redeemable value. In this vault, maxWithdraw is bounded by idle_assets, meaning a user holding 10,000 shares worth 10,000 assets gets maxWithdraw = 0 whenever idle_assets = 0. The proxy_view computes this correctly, but DeFi integrators (aggregators, yield optimisers) that use standard ERC-4626 ABI to query withdrawal capacity without reading the docs will be surprised. The fungible_vault.rs module-level comment explains this well, but the public proxy_view response should perhaps carry a flag or the docs should explicitly call this out as a non-standard deviation.

7. expected_assets could over-compensate queued withdrawers after share price decline

Related to point 1, but from the vault's perspective: when execute_withdraw pays expected_assets from idle_assets, it deducts payout.amount from idle_assets and burns burn_shares. If the share price at execution is lower than at request time, expected_assets exceeds the current redemption value of the escrowed shares. The vault is paying more than the shares are currently worth, creating a mild wealth transfer from remaining depositors to queued withdrawers.

Whether this is intentional (honoring the withdrawer's slippage guarantee) or a bug warrants explicit documentation. If intentional, the STRIDE threat model should note this under the information-disclosure or elevation-of-privilege categories.


Overall the design is clean and the atomicity/clarity improvement is substantial. The main usability gaps are the missing cancellation path, the permissionless-but-undocumented execute_withdraw auth model, and the ERC-4626 maxWithdraw deviation that needs surfacing to integrators.


🧠 Learnings used
Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Applies to contract/vault/soroban/src/contract/entrypoints.rs : withdraw() and redeem() include an idle-only atomic path that bypasses the queued withdrawal lifecycle. Do not change queue semantics without checking both paths

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Watch for `OpState` transitions, escrow accounting, keeper-routed withdrawals, callback ordering, idle-balance resync, fee accrual, and reconciliation after partial failures in the vault contract

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Applies to contract/vault/soroban/** : If behavior changes around governance bridging, withdrawal lifecycle, or share-token auth, update the docs in contract/vault/soroban/ in the same change

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/market/AGENTS.md:0-0
Timestamp: 2026-03-19T05:08:46.663Z
Learning: Review the full initial-call plus finalize path for borrow, collateral withdrawal, liquidation, and supply-withdrawal execution; these are asynchronous operations and must not be analyzed from the initial function alone

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Serialized VaultState remains a practical resource boundary because Soroban persists a single StateBlob. Pending withdrawals are the main long-lived growth vector

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Applies to contract/vault/soroban/** : Read README.md, STRIDE.md, SIZE_BUDGET.md, src/contract/entrypoints.rs, src/effects/mod.rs, and src/storage/mod.rs before making non-trivial changes to the Soroban vault runtime

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/curator-primitives/src/policy/cooldown/mod.rs:17-23
Timestamp: 2026-04-27T09:33:55.992Z
Learning: In Templar-Protocol/contracts, the Soroban vault (`contract/vault/soroban/`) and NEAR vault (`contract/vault/near/`) are greenfield deployments with no live persisted state. Do not flag internal serialization format changes (e.g., field type changes in structs like `Cooldown`, `RefreshThrottle`, or policy state) as breaking migration risks — there is no existing stored data to break.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Applies to contract/vault/soroban/src/** : RemoveMarket, skim recipient changes, and share-token authority changes can all become authority drift or asset-loss bugs if altered casually

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:42.034Z
Learning: Applies to contract/vault/soroban/src/contract/entrypoints.rs : Review auth on every privileged Soroban entrypoint. Do not rely on outer routing alone

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/soroban/src/contract/entrypoints.rs:806-836
Timestamp: 2026-04-28T08:29:30.164Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), the `SorobanVaultContract::initialize()` entrypoint intentionally has no caller-authorization check. Deployment is expected to go through a controlled/factory flow where `initialize` is called atomically with contract deployment, eliminating any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
contract/vault/soroban/justfile (1)

737-745: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

demo-withdraw text says conditional execute, but the recipe still hard-fails unconditionally.

Line 737 says execution happens only if idle-funded, but Line 744 always runs just execute-withdraw. When cooldown/idle conditions are unmet, the demo aborts instead of demonstrating the async path cleanly.

Suggested fix
 demo-withdraw:
 	`@echo` "=== WITHDRAWAL DEMO ==="
 	`@echo` "Step 1: Request withdrawal"
 	`@just` request-withdraw-self 500000
 	`@echo` ""
 	`@echo` "Step 2: Execute withdrawal"
-	`@just` execute-withdraw
+	`@if` just execute-withdraw; then \
+		echo "execute_withdraw completed"; \
+	else \
+		echo "execute_withdraw pending (cooldown or insufficient idle); retry later."; \
+	fi
 	`@echo` ""
 	`@just` vault-state
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contract/vault/soroban/justfile` around lines 737 - 745, The demo-withdraw
recipe claims to only execute when idle-funded but unconditionally runs just
execute-withdraw, causing hard failures; update the demo-withdraw recipe so
after request-withdraw-self 500000 it checks the vault idle/funded condition
(using the existing idle-check helper or CLI/status command used elsewhere) and
only invokes just execute-withdraw when that check succeeds, otherwise print a
message like "idle not funded, withdrawal pending" and exit successfully to
demonstrate the async path; reference the demo-withdraw recipe and the
execute-withdraw and request-withdraw-self targets when making the change.
contract/vault/soroban/src/tests.rs (1)

702-706: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep these queue tests solvent while starving idle liquidity.

Both setups lower idle_assets but leave external_assets at zero, so total_assets also drops below the queued payout. That means the test can still pass because the vault is insolvent, not because the request is fully funded overall but not idle-funded. Move the shortfall into external_assets so this specifically covers the new "no partial settlement from idle" invariant.

🧪 Suggested test fix
         {
             let state = vault.state_mut().unwrap();
             state.idle_assets = MIN_WITHDRAWAL_ASSETS.saturating_sub(1);
+            state.external_assets = deposit_amount.saturating_sub(state.idle_assets);
             state.total_assets = state.idle_assets.saturating_add(state.external_assets);
         }
@@
         {
             let state = vault.state_mut().unwrap();
             state.idle_assets = MIN_WITHDRAWAL_ASSETS.saturating_add(1);
+            state.external_assets = deposit_amount.saturating_sub(state.idle_assets);
             state.total_assets = state.idle_assets.saturating_add(state.external_assets);
         }
Based on learnings: "Watch for `OpState` transitions, escrow accounting, keeper-routed withdrawals, callback ordering, idle-balance resync, fee accrual, and reconciliation after partial failures in the vault contract".

Also applies to: 763-767

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contract/vault/soroban/src/tests.rs` around lines 702 - 706, The test
currently reduces state.idle_assets and recomputes state.total_assets so
total_assets falls below the queued payout, making the vault insolvent rather
than specifically starving idle liquidity; instead leave total_assets intact and
move the shortfall into state.external_assets so idle_assets < required but
total_assets >= required; update the two test blocks that set state.idle_assets
and state.total_assets (references: state.idle_assets, state.external_assets,
state.total_assets, MIN_WITHDRAWAL_ASSETS) to set state.idle_assets =
MIN_WITHDRAWAL_ASSETS.saturating_sub(1) and then set state.external_assets =
state.external_assets.saturating_add(1 + (original_total_assets -
state.total_assets)) (i.e. adjust external_assets so total_assets remains
sufficient) and apply the same change to the other occurrence mentioned (lines
around the second block at 763-767).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contract/vault/soroban/README.md`:
- Around line 58-59: The diagram incorrectly implies entrypoint-level
require_auth() for atomic withdraws; update the README so it either
removes/narrows the Entry->>Entry: require_auth() arrow for the atomic withdraw
path or add a brief note stating that atomic_withdraw_impl and
atomic_redeem_impl do not call require_signed and that authorization is enforced
deeper in the vault/share-token path (e.g., via the share-token or vault-level
checks), referencing the atomic_withdraw_impl and atomic_redeem_impl symbols so
readers know where auth actually occurs.

---

Outside diff comments:
In `@contract/vault/soroban/justfile`:
- Around line 737-745: The demo-withdraw recipe claims to only execute when
idle-funded but unconditionally runs just execute-withdraw, causing hard
failures; update the demo-withdraw recipe so after request-withdraw-self 500000
it checks the vault idle/funded condition (using the existing idle-check helper
or CLI/status command used elsewhere) and only invokes just execute-withdraw
when that check succeeds, otherwise print a message like "idle not funded,
withdrawal pending" and exit successfully to demonstrate the async path;
reference the demo-withdraw recipe and the execute-withdraw and
request-withdraw-self targets when making the change.

In `@contract/vault/soroban/src/tests.rs`:
- Around line 702-706: The test currently reduces state.idle_assets and
recomputes state.total_assets so total_assets falls below the queued payout,
making the vault insolvent rather than specifically starving idle liquidity;
instead leave total_assets intact and move the shortfall into
state.external_assets so idle_assets < required but total_assets >= required;
update the two test blocks that set state.idle_assets and state.total_assets
(references: state.idle_assets, state.external_assets, state.total_assets,
MIN_WITHDRAWAL_ASSETS) to set state.idle_assets =
MIN_WITHDRAWAL_ASSETS.saturating_sub(1) and then set state.external_assets =
state.external_assets.saturating_add(1 + (original_total_assets -
state.total_assets)) (i.e. adjust external_assets so total_assets remains
sufficient) and apply the same change to the other occurrence mentioned (lines
around the second block at 763-767).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 81393740-b140-4dca-b6f1-71bc15a58721

📥 Commits

Reviewing files that changed from the base of the PR and between 559c13f and 71c36bd.

📒 Files selected for processing (10)
  • contract/vault/kernel/src/actions/mod.rs
  • contract/vault/kernel/src/actions/tests.rs
  • contract/vault/kernel/src/error.rs
  • contract/vault/soroban/README.md
  • contract/vault/soroban/STRIDE.md
  • contract/vault/soroban/justfile
  • contract/vault/soroban/shared-types/src/lib.rs
  • contract/vault/soroban/src/contract/entrypoints.rs
  • contract/vault/soroban/src/fungible_vault.rs
  • contract/vault/soroban/src/tests.rs

Comment thread contract/vault/soroban/README.md Outdated
carrion256 added a commit that referenced this pull request May 18, 2026
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 force-pushed the feat/soroban-idle-withdraw-async-requests branch from 71c36bd to e3d167f Compare May 18, 2026 16:07
carrion256 added a commit that referenced this pull request May 19, 2026
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 force-pushed the feat/soroban-idle-withdraw-async-requests branch from e3d167f to a0b40f3 Compare May 19, 2026 08:28
carrion256 added a commit that referenced this pull request Jun 2, 2026
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 force-pushed the feat/soroban-idle-withdraw-async-requests branch from a0b40f3 to 1a1dc1b Compare June 2, 2026 11:40
@carrion256
carrion256 changed the base branch from spr/refactor/vault-ergonomics/4f330057 to halborn-audit June 2, 2026 11:41
@carrion256
carrion256 marked this pull request as ready for review June 3, 2026 16:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a1dc1b115

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 1900 to +1901
let Some(settlement) =
compute_idle_settlement(request_escrow, request_expected, available_assets)
compute_idle_settlement(request_escrow, request_expected, state.idle_assets)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject partial idle liquidity before starting queued payouts

When the cooled-down queue head expects more than the current idle balance but the idle balance is still above min_withdrawal_assets (for example expected 600, idle 500), classify_withdrawal_head still marks it Ready, and this call to compute_idle_settlement returns a partial settlement rather than None. execute_withdraw then burns/refunds shares and transfers the partial idle amount, so queued withdrawals still partially settle and mutate the queue instead of failing atomically and leaving it unchanged under the new full-idle-liquidity semantics.

Useful? React with 👍 / 👎.

Comment thread contract/vault/soroban/justfile Outdated
Base automatically changed from halborn-audit to dev June 4, 2026 16:06
An error occurred while trying to automatically change base from halborn-audit to dev June 4, 2026 16:06
carrion256 added a commit that referenced this pull request Jun 5, 2026
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 force-pushed the feat/soroban-idle-withdraw-async-requests branch from 1a1dc1b to 455e582 Compare June 5, 2026 08:34
@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
contract/vault/kernel/src/actions/mod.rs 72.72% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contract/vault/soroban/README.md`:
- Around line 114-115: Update the phrasing around "primary user exit path" in
the README where it describes "atomic withdraw/redeem" — replace the ambiguous
"primary user exit path whenever sufficient idle liquidity is already available"
with clearer wording such as "This is the immediate user exit path when
sufficient idle liquidity is available" or "This provides instant withdrawal
from idle assets, while `request_withdraw` handles positions requiring allocator
work" to clarify that atomic withdraw/redeem only applies when idle liquidity
exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: de92ca76-0e87-467d-8155-c80124690d56

📥 Commits

Reviewing files that changed from the base of the PR and between f13fd68 and 76e1490.

📒 Files selected for processing (2)
  • contract/vault/soroban/README.md
  • contract/vault/soroban/STRIDE.md

Comment thread contract/vault/soroban/README.md Outdated
@carrion256
carrion256 enabled auto-merge (rebase) June 5, 2026 10:42
@carrion256
carrion256 disabled auto-merge June 5, 2026 10:51
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 force-pushed the feat/soroban-idle-withdraw-async-requests branch from 2df853c to 2f08907 Compare June 5, 2026 10:52
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contract/vault/soroban/justfile`:
- Around line 775-787: The withdraw recipe should fail fast when the current
identity (addr) does not match the user-supplied owner because the contract
requires operator == owner; inside the withdraw recipe (function name: withdraw)
after computing `@addr` and before calling just vault-execute atomic-withdraw,
compare addr to the "{{owner}}" value and if they differ print a clear error
(including both operator and owner) and exit non‑zero so the shell recipe aborts
early rather than invoking vault-execute with mismatched operator/owner.

In `@contract/vault/soroban/scripts/vault_payload.py`:
- Around line 117-130: The atomic CLI branches for "atomic-withdraw" and
"atomic-redeem" allow an arbitrary --operator which will be rejected on-chain;
update the payload construction in the atomic-withdraw and atomic-redeem
branches so that args.operator is validated or replaced with args.owner (i.e.,
if args.operator is present and != args.owner, either raise/exit with an error
or set operator = owner) before calling push_string(out, args.operator) (and do
the same for the other identical block around lines 215-227) to ensure operator
== owner in the encoded payload.

In `@contract/vault/soroban/shared-types/src/lib.rs`:
- Around line 1080-1095: The test for ProxyViewFields currently uses the same
sdk_address() for all four tuple slots, so swaps between curator, governance,
asset_token, and share_token won't be caught; update the tuple in the
ProxyViewFields::from(...) construction to supply four distinct addresses (e.g.,
curator_addr, governance_addr, asset_token_addr, share_token_addr created via
sdk_address or Env::from_seed variants) and then assert each named field on the
resulting fields variable explicitly (check ProxyViewFields::curator,
::governance, ::asset_token, ::share_token) to ensure the test fails on
wire-position regressions; apply the same change pattern to the other similar
block around lines 1104-1125.
- Around line 30-33: Replace the public tuple alias ProxyCapGroupView with a
named struct (e.g., ProxyCapGroupFields) containing named fields for the three
components, add a From<ProxyCapGroupView> (or implement
From<(String,i128,i128)>) so existing tuple-based conversions still work, and
update ProxyPolicyView and any other type aliases (e.g., ProxyPolicyView,
ProxyViewResponse, places referencing ProxyCapGroupView) to use
Vec<ProxyCapGroupFields> instead of Vec<ProxyCapGroupView>; ensure the new
struct is exported from the crate and that any conversions or tests that
previously used the tuple are adapted to use the named fields or the From
implementation.

In `@contract/vault/soroban/src/contract/entrypoints.rs`:
- Around line 1623-1632: proxy_view currently computes
max_withdraw_value/max_redeem_value from state.idle_assets and owner_shares but
omits the same atomic-path dust floor check used by
handle_atomic_withdraw/handle_atomic_redeem; update the proxy_view logic (the
block that calculates max_withdraw_u128 and max_redeem_u128 in entrypoints.rs)
to apply the min_withdrawal guard (compare against config.min_withdrawal_assets
or MIN_WITHDRAWAL_ASSETS) so that if the computed max_withdraw_u128 is below
that dust floor it is treated as zero (and similarly ensure redeem follows the
same floor rule), using the existing helpers convert_to_assets/convert_to_shares
and state.idle_assets to decide whether to return zero or the to_i128(...)
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9d91dac-7179-4877-a922-33bce6056535

📥 Commits

Reviewing files that changed from the base of the PR and between f13fd68 and 2f08907.

📒 Files selected for processing (12)
  • contract/vault/kernel/src/actions/mod.rs
  • contract/vault/kernel/src/actions/tests.rs
  • contract/vault/soroban/README.md
  • contract/vault/soroban/STRIDE.md
  • contract/vault/soroban/justfile
  • contract/vault/soroban/scripts/vault_payload.py
  • contract/vault/soroban/shared-types/src/lib.rs
  • contract/vault/soroban/src/contract/entrypoints.rs
  • contract/vault/soroban/src/contract/mod.rs
  • contract/vault/soroban/src/fungible_vault.rs
  • contract/vault/soroban/src/tests.rs
  • contract/vault/soroban/tests/integration_tests.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contract/vault/soroban/justfile`:
- Around line 775-787: The withdraw recipe should fail fast when the current
identity (addr) does not match the user-supplied owner because the contract
requires operator == owner; inside the withdraw recipe (function name: withdraw)
after computing `@addr` and before calling just vault-execute atomic-withdraw,
compare addr to the "{{owner}}" value and if they differ print a clear error
(including both operator and owner) and exit non‑zero so the shell recipe aborts
early rather than invoking vault-execute with mismatched operator/owner.

In `@contract/vault/soroban/scripts/vault_payload.py`:
- Around line 117-130: The atomic CLI branches for "atomic-withdraw" and
"atomic-redeem" allow an arbitrary --operator which will be rejected on-chain;
update the payload construction in the atomic-withdraw and atomic-redeem
branches so that args.operator is validated or replaced with args.owner (i.e.,
if args.operator is present and != args.owner, either raise/exit with an error
or set operator = owner) before calling push_string(out, args.operator) (and do
the same for the other identical block around lines 215-227) to ensure operator
== owner in the encoded payload.

In `@contract/vault/soroban/shared-types/src/lib.rs`:
- Around line 1080-1095: The test for ProxyViewFields currently uses the same
sdk_address() for all four tuple slots, so swaps between curator, governance,
asset_token, and share_token won't be caught; update the tuple in the
ProxyViewFields::from(...) construction to supply four distinct addresses (e.g.,
curator_addr, governance_addr, asset_token_addr, share_token_addr created via
sdk_address or Env::from_seed variants) and then assert each named field on the
resulting fields variable explicitly (check ProxyViewFields::curator,
::governance, ::asset_token, ::share_token) to ensure the test fails on
wire-position regressions; apply the same change pattern to the other similar
block around lines 1104-1125.
- Around line 30-33: Replace the public tuple alias ProxyCapGroupView with a
named struct (e.g., ProxyCapGroupFields) containing named fields for the three
components, add a From<ProxyCapGroupView> (or implement
From<(String,i128,i128)>) so existing tuple-based conversions still work, and
update ProxyPolicyView and any other type aliases (e.g., ProxyPolicyView,
ProxyViewResponse, places referencing ProxyCapGroupView) to use
Vec<ProxyCapGroupFields> instead of Vec<ProxyCapGroupView>; ensure the new
struct is exported from the crate and that any conversions or tests that
previously used the tuple are adapted to use the named fields or the From
implementation.

In `@contract/vault/soroban/src/contract/entrypoints.rs`:
- Around line 1623-1632: proxy_view currently computes
max_withdraw_value/max_redeem_value from state.idle_assets and owner_shares but
omits the same atomic-path dust floor check used by
handle_atomic_withdraw/handle_atomic_redeem; update the proxy_view logic (the
block that calculates max_withdraw_u128 and max_redeem_u128 in entrypoints.rs)
to apply the min_withdrawal guard (compare against config.min_withdrawal_assets
or MIN_WITHDRAWAL_ASSETS) so that if the computed max_withdraw_u128 is below
that dust floor it is treated as zero (and similarly ensure redeem follows the
same floor rule), using the existing helpers convert_to_assets/convert_to_shares
and state.idle_assets to decide whether to return zero or the to_i128(...)
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9d91dac-7179-4877-a922-33bce6056535

📥 Commits

Reviewing files that changed from the base of the PR and between f13fd68 and 2f08907.

📒 Files selected for processing (12)
  • contract/vault/kernel/src/actions/mod.rs
  • contract/vault/kernel/src/actions/tests.rs
  • contract/vault/soroban/README.md
  • contract/vault/soroban/STRIDE.md
  • contract/vault/soroban/justfile
  • contract/vault/soroban/scripts/vault_payload.py
  • contract/vault/soroban/shared-types/src/lib.rs
  • contract/vault/soroban/src/contract/entrypoints.rs
  • contract/vault/soroban/src/contract/mod.rs
  • contract/vault/soroban/src/fungible_vault.rs
  • contract/vault/soroban/src/tests.rs
  • contract/vault/soroban/tests/integration_tests.rs
🛑 Comments failed to post (5)
contract/vault/soroban/justfile (1)

775-787: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit owner/operator guard in withdraw recipe.

At Line 779 and Line 784, operator is always the current identity while owner is user-supplied. This recipe will fail when they differ, but only after invoking the contract. Fail fast with a clear check.

Suggested patch
 withdraw assets receiver owner max_shares_burned="":
 	`@echo` "Atomic withdrawal of {{assets}} assets..."
 	`@addr`="$(stellar keys address ${SOROBAN_IDENTITY:-{{default_identity}}})"; \
+	if [ "$addr" != "{{owner}}" ]; then \
+		echo "Error: delegated withdraw is not supported; owner must equal current identity ($addr)."; \
+		exit 1; \
+	fi; \
 	max_shares="{{max_shares_burned}}"; \
 	if [ -z "$max_shares" ]; then max_shares="{{assets}}"; fi; \
 	just vault-execute atomic-withdraw \
 		--owner "{{owner}}" \
 		--operator "$addr" \
 		--assets "{{assets}}" \
 		--receiver "{{receiver}}" \
 		--max-shares-burned "$max_shares"

Based on learnings: delegated withdrawal operators are intentionally rejected in contract/proxy-4626-soroban/src/contract.rs (operator == owner).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

# Atomic withdraw (ERC-4626-style immediate exit; idle assets only, never queues).
# Usage: just withdraw <assets> <receiver> <owner> [max_shares_burned]
withdraw assets receiver owner max_shares_burned="":
	`@echo` "Atomic withdrawal of {{assets}} assets..."
	`@addr`="$(stellar keys address ${SOROBAN_IDENTITY:-{{default_identity}}})"; \
	if [ "$addr" != "{{owner}}" ]; then \
		echo "Error: delegated withdraw is not supported; owner must equal current identity ($addr)."; \
		exit 1; \
	fi; \
	max_shares="{{max_shares_burned}}"; \
	if [ -z "$max_shares" ]; then max_shares="{{assets}}"; fi; \
	just vault-execute atomic-withdraw \
		--owner "{{owner}}" \
		--operator "$addr" \
		--assets "{{assets}}" \
		--receiver "{{receiver}}" \
		--max-shares-burned "$max_shares"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/justfile` around lines 775 - 787, The withdraw recipe
should fail fast when the current identity (addr) does not match the
user-supplied owner because the contract requires operator == owner; inside the
withdraw recipe (function name: withdraw) after computing `@addr` and before
calling just vault-execute atomic-withdraw, compare addr to the "{{owner}}"
value and if they differ print a clear error (including both operator and owner)
and exit non‑zero so the shell recipe aborts early rather than invoking
vault-execute with mismatched operator/owner.
contract/vault/soroban/scripts/vault_payload.py (1)

117-130: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject operator != owner in the atomic CLI paths.

These subcommands currently let users encode arbitrary --operator values, but the proxy intentionally rejects delegated withdrawal operators. That means this tool can generate payloads that are guaranteed to fail on-chain. Either validate operator == owner before encoding or default operator to owner.

Based on learnings, delegated withdrawal operators are explicitly rejected and the Soroban proxy withdraw / redeem paths enforce operator == owner.

Also applies to: 215-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/scripts/vault_payload.py` around lines 117 - 130, The
atomic CLI branches for "atomic-withdraw" and "atomic-redeem" allow an arbitrary
--operator which will be rejected on-chain; update the payload construction in
the atomic-withdraw and atomic-redeem branches so that args.operator is
validated or replaced with args.owner (i.e., if args.operator is present and !=
args.owner, either raise/exit with an error or set operator = owner) before
calling push_string(out, args.operator) (and do the same for the other identical
block around lines 215-227) to ensure operator == owner in the encoded payload.
contract/vault/soroban/shared-types/src/lib.rs (2)

30-33: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Replace the remaining cap-group triple with a named type.

ProxyViewFields is clearly trying to eliminate tuple-position footguns, but cap_groups still exposes raw (String, i128, i128) entries in the public API. That keeps the most error-prone part of the payload positional for downstream consumers. A dedicated ProxyCapGroupFields wrapper plus From<ProxyCapGroupView> would finish the job.

As per coding guidelines, "Prefer stronger types over loosely constrained values in Rust" and "make invalid states unrepresentable."

Also applies to: 76-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/shared-types/src/lib.rs` around lines 30 - 33, Replace
the public tuple alias ProxyCapGroupView with a named struct (e.g.,
ProxyCapGroupFields) containing named fields for the three components, add a
From<ProxyCapGroupView> (or implement From<(String,i128,i128)>) so existing
tuple-based conversions still work, and update ProxyPolicyView and any other
type aliases (e.g., ProxyPolicyView, ProxyViewResponse, places referencing
ProxyCapGroupView) to use Vec<ProxyCapGroupFields> instead of
Vec<ProxyCapGroupView>; ensure the new struct is exported from the crate and
that any conversions or tests that previously used the tuple are adapted to use
the named fields or the From implementation.

1080-1095: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use distinct addresses in the tuple-order test.

This test is meant to catch wire-position regressions, but Lines 1091-1094 feed the same address into every slot. A swap between curator, governance, asset_token, and share_token would still pass here. Give each position a distinct address and assert the named fields explicitly.

Also applies to: 1104-1125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/shared-types/src/lib.rs` around lines 1080 - 1095, The
test for ProxyViewFields currently uses the same sdk_address() for all four
tuple slots, so swaps between curator, governance, asset_token, and share_token
won't be caught; update the tuple in the ProxyViewFields::from(...) construction
to supply four distinct addresses (e.g., curator_addr, governance_addr,
asset_token_addr, share_token_addr created via sdk_address or Env::from_seed
variants) and then assert each named field on the resulting fields variable
explicitly (check ProxyViewFields::curator, ::governance, ::asset_token,
::share_token) to ensure the test fails on wire-position regressions; apply the
same change pattern to the other similar block around lines 1104-1125.
contract/vault/soroban/src/contract/entrypoints.rs (1)

1623-1632: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Preview limits vs atomic withdrawal floor"
rg -n -C5 '\b(max_withdraw|max_redeem|MIN_WITHDRAWAL_ASSETS|min_withdrawal_assets|atomic_withdraw|atomic_redeem)\b' \
  contract/vault/soroban/src \
  contract/vault/kernel/src

Repository: Templar-Protocol/contracts

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for atomic withdraw/redeem + MIN_WITHDRAWAL_ASSETS"
rg -n --hidden -S '\batomic_(withdraw|redeem)\b' contract/vault/soroban/src/contract contract/vault/kernel/src
echo
rg -n '\bMIN_WITHDRAWAL_ASSETS\b|min_withdrawal_assets' contract/vault/kernel/src contract/vault/soroban/src/contract | head -n 200
echo
echo "Inspect handle_atomic_withdraw/redeem in kernel actions"
rg -n 'handle_atomic_(withdraw|redeem)' contract/vault/kernel/src/actions/mod.rs
echo
# Print a focused window around the functions (best effort)
awk 'NR>=1&&NR<=220{print}' contract/vault/kernel/src/actions/mod.rs > /tmp/kernel_actions_head.txt
# locate line numbers for handle_atomic_withdraw and then print around
for fn in handle_atomic_withdraw handle_atomic_redeem; do
  ln=$(rg -n "$fn" contract/vault/kernel/src/actions/mod.rs | head -n1 | cut -d: -f1)
  echo "---- $fn at line $ln ----"
  if [ -n "${ln:-}" ]; then
    start=$((ln-40)); end=$((ln+80));
    sed -n "${start},${end}p" contract/vault/kernel/src/actions/mod.rs
  fi
done
echo
echo "Inspect curator_vault.rs atomic_withdraw/atomic_redeem"
curator="contract/vault/soroban/src/contract/curator_vault.rs"
if [ -f "$curator" ]; then
  for fn in atomic_withdraw atomic_redeem; do
    ln=$(rg -n "$fn" "$curator" | head -n1 | cut -d: -f1)
    echo "---- $fn at line $ln ----"
    if [ -n "${ln:-}" ]; then
      start=$((ln-40)); end=$((ln+120));
      sed -n "${start},${end}p" "$curator"
    fi
  done
else
  echo "Missing $curator (file not present in repo)"
fi
echo
echo "Inspect proxy_view max_withdraw/max_redeem computation in entrypoints"
entry="contract/vault/soroban/src/contract/entrypoints.rs"
if [ -f "$entry" ]; then
  rg -n 'proxy_view' "$entry"
  # show around the snippet area ~1623-1632
  sed -n '1590,1670p' "$entry"
fi

Repository: Templar-Protocol/contracts

Length of output: 27682


🏁 Script executed:

# (end)

Repository: Templar-Protocol/contracts

Length of output: 52


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for atomic withdraw/redeem + MIN_WITHDRAWAL_ASSETS"
rg -n --hidden -S '\batomic_(withdraw|redeem)\b' contract/vault/soroban/src/contract contract/vault/kernel/src

echo
echo "Search for MIN_WITHDRAWAL_ASSETS / min_withdrawal_assets usages (limited)"
rg -n '\bMIN_WITHDRAWAL_ASSETS\b|min_withdrawal_assets' contract/vault/kernel/src contract/vault/soroban/src/contract | head -n 200

echo
echo "Locate handle_atomic_withdraw/handle_atomic_redeem in kernel actions"
rg -n 'handle_atomic_(withdraw|redeem)' contract/vault/kernel/src/actions/mod.rs

echo
echo "Focused code windows for kernel atomic handlers"
for fn in handle_atomic_withdraw handle_atomic_redeem; do
  ln=$(rg -n "$fn" contract/vault/kernel/src/actions/mod.rs | head -n1 | cut -d: -f1 || true)
  echo "---- $fn at line ${ln:-?} ----"
  if [ -n "${ln:-}" ]; then
    start=$((ln-40)); end=$((ln+120));
    sed -n "${start},${end}p" contract/vault/kernel/src/actions/mod.rs
  fi
done

echo
echo "Inspect curator_vault.rs atomic_withdraw/atomic_redeem"
curator="contract/vault/soroban/src/contract/curator_vault.rs"
if [ -f "$curator" ]; then
  for fn in atomic_withdraw atomic_redeem; do
    ln=$(rg -n "$fn" "$curator" | head -n1 | cut -d: -f1 || true)
    echo "---- $fn at line ${ln:-?} ----"
    if [ -n "${ln:-}" ]; then
      start=$((ln-40)); end=$((ln+160));
      sed -n "${start},${end}p" "$curator"
    fi
  done
else
  echo "Missing $curator (file not present in repo)"
fi

echo
echo "Inspect proxy_view max_withdraw/max_redeem computation in entrypoints"
entry="contract/vault/soroban/src/contract/entrypoints.rs"
if [ -f "$entry" ]; then
  rg -n 'proxy_view' "$entry" | head -n 20
  sed -n '1590,1670p' "$entry"
fi

Repository: Templar-Protocol/contracts

Length of output: 33102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files existence:"
ls -1 contract/vault/soroban/src/contract | rg -n 'curator_vault\.rs|entrypoints\.rs' || true
ls -1 contract/vault/kernel/src/actions/mod.rs || true

echo
echo "MIN_WITHDRAWAL_ASSETS usage sites in kernel + soroban contract:"
rg -n '\bMIN_WITHDRAWAL_ASSETS\b|min_withdrawal_assets' contract/vault/kernel/src contract/vault/soroban/src/contract | head -n 80

echo
echo "kernel atomic handler blocks (small):"
for fn in handle_atomic_withdraw handle_atomic_redeem; do
  ln=$(rg -n "$fn" contract/vault/kernel/src/actions/mod.rs | head -n1 | cut -d: -f1 || true)
  echo "$fn at $ln"
  if [ -n "${ln:-}" ]; then
    sed -n "$((ln-20)),$((ln+60))p" contract/vault/kernel/src/actions/mod.rs
  fi
done

echo
echo "curator_vault.rs atomic blocks (small):"
curator="contract/vault/soroban/src/contract/curator_vault.rs"
if [ -f "$curator" ]; then
  for fn in atomic_withdraw atomic_redeem; do
    ln=$(rg -n "$fn" "$curator" | head -n1 | cut -d: -f1 || true)
    echo "$fn at $ln"
    if [ -n "${ln:-}" ]; then
      sed -n "$((ln-20)),$((ln+80))p" "$curator"
    fi
  done
else
  echo "Missing $curator"
fi

echo
echo "entrypoints.rs proxy_view max limits section:"
entry="contract/vault/soroban/src/contract/entrypoints.rs"
sed -n '1585,1665p' "$entry" | rg -n 'max_withdraw|max_redeem|min_withdrawal|idle_assets|convert_to_shares|convert_to_assets|paused|op_state' -n || true

Repository: Templar-Protocol/contracts

Length of output: 17219


proxy_view previews ignore the atomic-path dust floor (MIN_WITHDRAWAL_ASSETS).

Kernel atomic handlers (handle_atomic_withdraw / handle_atomic_redeem) only enforce “idle + non-zero” plus idle-liquidity/silippage bounds, and there is no config.min_withdrawal_assets / MIN_WITHDRAWAL_ASSETS check in those atomic branches; the min_withdrawal_assets guard shows up in the queued/idle-payout planning paths instead. Since proxy_view’s max_withdraw_value / max_redeem_value becomes positive based on state.idle_assets alone, previews can overstate what withdraw() / redeem() will accept via the atomic idle path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/src/contract/entrypoints.rs` around lines 1623 - 1632,
proxy_view currently computes max_withdraw_value/max_redeem_value from
state.idle_assets and owner_shares but omits the same atomic-path dust floor
check used by handle_atomic_withdraw/handle_atomic_redeem; update the proxy_view
logic (the block that calculates max_withdraw_u128 and max_redeem_u128 in
entrypoints.rs) to apply the min_withdrawal guard (compare against
config.min_withdrawal_assets or MIN_WITHDRAWAL_ASSETS) so that if the computed
max_withdraw_u128 is below that dust floor it is treated as zero (and similarly
ensure redeem follows the same floor rule), using the existing helpers
convert_to_assets/convert_to_shares and state.idle_assets to decide whether to
return zero or the to_i128(...) value.

@carrion256

Copy link
Copy Markdown
Collaborator Author

@coderabbitai ur comments failed to post

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check review state and inline review comments on PR `#423`
gh pr view 423 --repo Templar-Protocol/contracts --json reviews,reviewDecision,state

echo ""
echo "=== REVIEW COMMENTS (inline) ==="
gh api repos/Templar-Protocol/contracts/pulls/423/comments --jq '[.[] | {id: .id, path: .path, line: .line, body: (.body | .[0:120])}]'

echo ""
echo "=== REVIEW THREADS ==="
gh api repos/Templar-Protocol/contracts/pulls/423/reviews --jq '[.[] | {id: .id, user: .user.login, state: .state, submitted_at: .submitted_at, body: (.body | .[0:80])}]'

Length of output: 44322



@carrion256 — confirmed. The last review run successfully submitted the review but GitHub rejected all 5 inline comment postings. The review body contains a caution banner with this message:

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" → "Code review limits" under your organization settings.

Here are the 5 comments that failed to post inline, reproduced in full:


1 · contract/vault/soroban/justfile — Lines 775–787 ⚠️ Minor / Quick win

Add an explicit owner/operator guard in the withdraw recipe.

The contract requires operator == owner and will reject delegated withdrawals. The recipe will fail, but only after invoking the contract. Fail fast instead:

 withdraw assets receiver owner max_shares_burned="":
 	`@echo` "Atomic withdrawal of {{assets}} assets..."
 	`@addr`="$(stellar keys address ${SOROBAN_IDENTITY:-{{default_identity}}})"; \
+	if [ "$addr" != "{{owner}}" ]; then \
+		echo "Error: delegated withdraw is not supported; owner must equal current identity ($addr)."; \
+		exit 1; \
+	fi; \
 	max_shares="{{max_shares_burned}}"; \
 	if [ -z "$max_shares" ]; then max_shares="{{assets}}"; fi; \
 	just vault-execute atomic-withdraw \
 		--owner "{{owner}}" \
 		--operator "$addr" \
 		--assets "{{assets}}" \
 		--receiver "{{receiver}}" \
 		--max-shares-burned "$max_shares"

2 · contract/vault/soroban/scripts/vault_payload.py — Lines 117–130 and 215–227 ⚠️ Minor / Quick win

Reject operator != owner in the atomic-withdraw / atomic-redeem CLI paths.

Both branches let users pass an arbitrary --operator, but the proxy enforces operator == owner on-chain, so any payload with a different operator is guaranteed to fail. Add a validation guard:

# In both atomic-withdraw and atomic-redeem branches, before push_string(out, args.operator):
if args.operator and args.operator != args.owner:
    raise SystemExit(
        f"Error: delegated operators are not supported; operator must equal owner ({args.owner!r})."
    )
operator = args.owner  # enforce operator == owner

3 · contract/vault/soroban/shared-types/src/lib.rs — Lines 30–33 and 76–79 🧹 Nitpick / Trivial

Replace ProxyCapGroupView tuple alias with a named struct.

ProxyViewFields was introduced to eliminate positional footguns, but cap_groups still exposes raw (String, i128, i128) tuples in the public API. A ProxyCapGroupFields named struct + From<ProxyCapGroupView> would complete the job:

#[derive(Clone, Debug, PartialEq)]
pub struct ProxyCapGroupFields {
    pub label: String,
    pub current: i128,
    pub maximum: i128,
}

impl From<ProxyCapGroupView> for ProxyCapGroupFields {
    fn from((label, current, maximum): ProxyCapGroupView) -> Self {
        Self { label, current, maximum }
    }
}

4 · contract/vault/soroban/shared-types/src/lib.rs — Lines 1080–1095 and 1104–1125 ⚠️ Minor / Quick win

Use distinct addresses in the ProxyViewFields tuple-order test.

All four slots currently receive the same address, so a swap between curator, governance, asset_token, and share_token positions would still pass. Provide four distinct addresses and assert each named field explicitly:

let curator_addr    = sdk_address_from_seed(&env, 1);
let governance_addr = sdk_address_from_seed(&env, 2);
let asset_token_addr = sdk_address_from_seed(&env, 3);
let share_token_addr = sdk_address_from_seed(&env, 4);

let fields = ProxyViewFields::from((curator_addr.clone(), governance_addr.clone(),
                                    asset_token_addr.clone(), share_token_addr.clone()));
assert_eq!(fields.curator,      curator_addr);
assert_eq!(fields.governance,   governance_addr);
assert_eq!(fields.asset_token,  asset_token_addr);
assert_eq!(fields.share_token,  share_token_addr);

5 · contract/vault/soroban/src/contract/entrypoints.rs — Lines 1623–1632 🔴 Critical

proxy_view previews ignore the atomic-path dust floor.

handle_atomic_withdraw / handle_atomic_redeem enforce a non-zero idle check but there is no MIN_WITHDRAWAL_ASSETS floor in those atomic branches. The proxy_view max_withdraw_value / max_redeem_value become positive based on state.idle_assets alone, so maxWithdraw / maxRedeem can return values that withdraw() / redeem() will actually reject. Apply the same floor check used in the queued-payout path:

// After computing max_withdraw_u128 / max_redeem_u128:
let min_floor = config.min_withdrawal_assets as u128; // or MIN_WITHDRAWAL_ASSETS
let max_withdraw_value = if max_withdraw_u128 < min_floor { 0 } else { to_i128(max_withdraw_u128) };
let max_redeem_value   = if max_redeem_u128  < min_floor { 0 } else { to_i128(max_redeem_u128)   };

To fix the posting issue: check your GitHub organization's Settings → Moderation → Code review limits. If "Limit to users who have explicitly been granted access" or similar restrictions are enabled, ensure the CodeRabbit bot account has the necessary permissions. This has been happening consistently across multiple review runs on this PR.


🧠 Learnings used
Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `withdraw()` and `redeem()` include an idle-only atomic path that bypasses the queued withdrawal lifecycle. Do not change queue semantics without checking both paths

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), delegated withdrawal operators are explicitly rejected. The `withdraw` and `redeem` entrypoints enforce `operator == owner` and return `ContractError::InsufficientAllowance` when a different caller attempts to act on behalf of an owner. Do not flag missing allowance-decrement logic or suggest supporting delegated operators — the design intentionally prohibits delegation.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/soroban/src/contract/entrypoints.rs:806-836
Timestamp: 2026-04-28T08:29:30.164Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), the `SorobanVaultContract::initialize()` entrypoint intentionally has no caller-authorization check. Deployment is expected to go through a controlled/factory flow where `initialize` is called atomically with contract deployment, eliminating any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : Review auth on every privileged Soroban entrypoint. Do not rely on outer routing alone

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), async redemption flows (`withdraw`, `redeem`, `request_withdraw`) emit a `RedeemRequest` event rather than a generic ERC-4626 `Withdraw` event. This is intentional — it correctly reflects that the operation queues a withdrawal rather than immediately transferring assets. Do not flag the absence of a standard `Withdraw` event on these entrypoints as a compliance gap.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `RemoveMarket`, skim recipient changes, and share-token authority changes can all become authority drift or asset-loss bugs if altered casually

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/governance/**/*.rs : Governance abdication is irreversible. Any change to governance action kind mapping or timelock policy needs a high-suspicion review

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Watch for `OpState` transitions, escrow accounting, keeper-routed withdrawals, callback ordering, idle-balance resync, fee accrual, and reconciliation after partial failures in the vault contract

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Read `contract/vault/soroban/README.md`, `contract/vault/soroban/STRIDE.md`, `contract/vault/soroban/SIZE_BUDGET.md`, `contract/vault/soroban/src/contract/entrypoints.rs`, `contract/vault/soroban/src/effects/mod.rs`, `contract/vault/soroban/src/storage/mod.rs`, and the related shared-type and contract library files before making non-trivial changes to the Soroban executor layer

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 418
File: contract/proxy-4626-soroban/src/contract.rs:281-303
Timestamp: 2026-04-24T14:18:52.424Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts), the `initialize` function on `Soroban4626ProxyContract` intentionally has no caller-authorization check. The deployer is expected to call `initialize` atomically with contract deployment (Soroban's `create_contract` + init in a single transaction), which eliminates any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Serialized `VaultState` remains a practical resource boundary because Soroban persists a single `StateBlob`. Pending withdrawals are the main long-lived growth vector

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/market/AGENTS.md:0-0
Timestamp: 2026-05-22T13:51:30.955Z
Learning: Review the full initial-call plus finalize path for asynchronous execution flows (borrow, collateral withdrawal, liquidation, supply-withdrawal), not only the first function

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/curator-primitives/tests/integration_tests.rs:32-35
Timestamp: 2026-04-27T12:49:20.949Z
Learning: In `contract/vault/curator-primitives/tests/integration_tests.rs`, the call `compute_payout_failure_outcome(escrow_shares, restore_idle, restore_idle)` is intentional. The function signature is `compute_payout_failure_outcome(escrow_shares, payout_amount, restore_idle)`, and in a failed-payout test the payout amount being restored to idle equals the restore_idle amount — so both arguments being the same value is correct test design. Do not flag this as a duplicate-argument bug.

Learnt from: peer2f00l
Repo: Templar-Protocol/contracts PR: 404
File: contract/vault/kernel/src/actions/mod.rs:1869-1871
Timestamp: 2026-04-14T07:35:09.869Z
Learning: In `contract/vault/kernel/src/actions/mod.rs` (Templar-Protocol/contracts, Rust), the vault kernel crate is compiled for Soroban (wasm32 target). Extracting helper functions from large dispatcher functions like `dispatch::apply_action` risks increasing wasm binary size due to additional call frames and reduced compiler inlining. Therefore, `#[allow(clippy::too_many_lines)]` suppressions on large dispatch/entrypoint functions in this file are intentional and should not be flagged as needing refactoring.

@carrion256

carrion256 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

Tip

For best results, initiate chat on the files or code changes.
🧩 Analysis chain

@carrion256 — confirmed. The last review run successfully submitted the review but GitHub rejected all 5 inline comment postings. The review body contains a caution banner with this message:

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" → "Code review limits" under your organization settings.

Here are the 5 comments that failed to post inline, reproduced in full:

1 · contract/vault/soroban/justfile — Lines 775–787 ⚠️ Minor / Quick win

Add an explicit owner/operator guard in the withdraw recipe.

The contract requires operator == owner and will reject delegated withdrawals. The recipe will fail, but only after invoking the contract. Fail fast instead:

 withdraw assets receiver owner max_shares_burned="":
 	`@echo` "Atomic withdrawal of {{assets}} assets..."
 	`@addr`="$(stellar keys address ${SOROBAN_IDENTITY:-{{default_identity}}})"; \
+	if [ "$addr" != "{{owner}}" ]; then \
+		echo "Error: delegated withdraw is not supported; owner must equal current identity ($addr)."; \
+		exit 1; \
+	fi; \
 	max_shares="{{max_shares_burned}}"; \
 	if [ -z "$max_shares" ]; then max_shares="{{assets}}"; fi; \
 	just vault-execute atomic-withdraw \
 		--owner "{{owner}}" \
 		--operator "$addr" \
 		--assets "{{assets}}" \
 		--receiver "{{receiver}}" \
 		--max-shares-burned "$max_shares"

2 · contract/vault/soroban/scripts/vault_payload.py — Lines 117–130 and 215–227 ⚠️ Minor / Quick win

Reject operator != owner in the atomic-withdraw / atomic-redeem CLI paths.

Both branches let users pass an arbitrary --operator, but the proxy enforces operator == owner on-chain, so any payload with a different operator is guaranteed to fail. Add a validation guard:

# In both atomic-withdraw and atomic-redeem branches, before push_string(out, args.operator):
if args.operator and args.operator != args.owner:
    raise SystemExit(
        f"Error: delegated operators are not supported; operator must equal owner ({args.owner!r})."
    )
operator = args.owner  # enforce operator == owner

3 · contract/vault/soroban/shared-types/src/lib.rs — Lines 30–33 and 76–79 🧹 Nitpick / Trivial

Replace ProxyCapGroupView tuple alias with a named struct.

ProxyViewFields was introduced to eliminate positional footguns, but cap_groups still exposes raw (String, i128, i128) tuples in the public API. A ProxyCapGroupFields named struct + From<ProxyCapGroupView> would complete the job:

#[derive(Clone, Debug, PartialEq)]
pub struct ProxyCapGroupFields {
    pub label: String,
    pub current: i128,
    pub maximum: i128,
}

impl From<ProxyCapGroupView> for ProxyCapGroupFields {
    fn from((label, current, maximum): ProxyCapGroupView) -> Self {
        Self { label, current, maximum }
    }
}

4 · contract/vault/soroban/shared-types/src/lib.rs — Lines 1080–1095 and 1104–1125 ⚠️ Minor / Quick win

Use distinct addresses in the ProxyViewFields tuple-order test.

All four slots currently receive the same address, so a swap between curator, governance, asset_token, and share_token positions would still pass. Provide four distinct addresses and assert each named field explicitly:

let curator_addr    = sdk_address_from_seed(&env, 1);
let governance_addr = sdk_address_from_seed(&env, 2);
let asset_token_addr = sdk_address_from_seed(&env, 3);
let share_token_addr = sdk_address_from_seed(&env, 4);

let fields = ProxyViewFields::from((curator_addr.clone(), governance_addr.clone(),
                                    asset_token_addr.clone(), share_token_addr.clone()));
assert_eq!(fields.curator,      curator_addr);
assert_eq!(fields.governance,   governance_addr);
assert_eq!(fields.asset_token,  asset_token_addr);
assert_eq!(fields.share_token,  share_token_addr);

5 · contract/vault/soroban/src/contract/entrypoints.rs — Lines 1623–1632 🔴 Critical

proxy_view previews ignore the atomic-path dust floor.

handle_atomic_withdraw / handle_atomic_redeem enforce a non-zero idle check but there is no MIN_WITHDRAWAL_ASSETS floor in those atomic branches. The proxy_view max_withdraw_value / max_redeem_value become positive based on state.idle_assets alone, so maxWithdraw / maxRedeem can return values that withdraw() / redeem() will actually reject. Apply the same floor check used in the queued-payout path:

// After computing max_withdraw_u128 / max_redeem_u128:
let min_floor = config.min_withdrawal_assets as u128; // or MIN_WITHDRAWAL_ASSETS
let max_withdraw_value = if max_withdraw_u128 < min_floor { 0 } else { to_i128(max_withdraw_u128) };
let max_redeem_value   = if max_redeem_u128  < min_floor { 0 } else { to_i128(max_redeem_u128)   };

To fix the posting issue: check your GitHub organization's Settings → Moderation → Code review limits. If "Limit to users who have explicitly been granted access" or similar restrictions are enabled, ensure the CodeRabbit bot account has the necessary permissions. This has been happening consistently across multiple review runs on this PR.
🧠 Learnings used

@coderabbitai
justfile withdraw: invalid as stated. The contract does not require operator == owner. Delegated atomic withdraw is supported via share allowance. There is an existing test for this:
test_atomic_withdraw_requires_allowance_for_delegated_operator.

  • vault_payload.py --operator: same issue. Arbitrary operator is valid if approved; forcing operator = owner would remove delegated atomic exit support.
  • shared-types/src/lib.rs: stale. Current file has no ProxyViewFields, ProxyCapGroupView, or proxy tuple aliases. The file is 872 lines, so the cited line ranges do not exist on current feat(soroban): split idle and async withdrawals #423.
  • proxy_view dust floor: invalid. Atomic withdraw/redeem do not enforce min_withdrawal_assets; that floor is for queued withdrawal planning/execution. Adding it to proxy_view would under-report valid atomic exits.

The current CodeRabbit comments look like they’re based on an older branch state or a mistaken model of atomic exits.

@carrion256
carrion256 dismissed coderabbitai[bot]’s stale review June 5, 2026 12:39

nonsense/old commit

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@carrion256 — thank you for these corrections. All four points are valid; I acknowledge each below and will record them to avoid repeating the same mistakes.

  1. justfile / vault_payload.py — operator guard: Withdrawn. Delegated atomic withdraw is supported via share allowance (confirmed by test_atomic_withdraw_requires_allowance_for_delegated_operator). The vault does not enforce operator == owner; it validates the share allowance instead. The operator guard I suggested would have broken delegated atomic exits.

  2. shared-types/src/lib.rs — ProxyViewFields / ProxyCapGroupView: Withdrawn. The current file on this branch does not contain ProxyViewFields, ProxyCapGroupView, or proxy tuple aliases. The cited line ranges were stale and do not exist in the 872-line file. Both the struct-replacement and the test-improvement comments were invalid against the actual code.

  3. proxy_view dust floor: Withdrawn. Atomic withdraw/redeem do not enforce min_withdrawal_assets; that floor applies only to queued withdrawal planning/execution. Applying it to proxy_view would incorrectly suppress valid atomic exits and under-report true capacity.


✏️ Learnings added
Learnt from: carrion256
URL: https://github.com/Templar-Protocol/contracts/pull/423

Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` and `contract/vault/kernel/src/actions/mod.rs` (Templar-Protocol/contracts, Soroban vault), atomic `withdraw`/`redeem` (`AtomicWithdraw`/`AtomicRedeem`) support delegated operators via share token allowance. The vault does NOT enforce `operator == owner`; it validates share allowance instead. Test `test_atomic_withdraw_requires_allowance_for_delegated_operator` covers this. Do not suggest adding an operator == owner guard to `AtomicWithdraw`/`AtomicRedeem` entrypoints or tooling.

Learnt from: carrion256
URL: https://github.com/Templar-Protocol/contracts/pull/423

Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/scripts/vault_payload.py` (Templar-Protocol/contracts), the `--operator` argument for `atomic-withdraw` and `atomic-redeem` commands is intentionally free-form. An operator that differs from owner is valid when the owner has granted a share token allowance to that operator. Do not suggest forcing `operator = owner` or adding a validation guard that rejects operator != owner in this script.

Learnt from: carrion256
URL: https://github.com/Templar-Protocol/contracts/pull/423

Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/shared-types/src/lib.rs` (Templar-Protocol/contracts, PR `#423` branch), the file does not contain `ProxyViewFields`, `ProxyCapGroupView`, or any proxy tuple type aliases. The file is approximately 872 lines. Do not cite these types or their associated line ranges when reviewing this file.

Learnt from: carrion256
URL: https://github.com/Templar-Protocol/contracts/pull/423

Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), `min_withdrawal_assets` is a floor enforced only for queued withdrawal planning and execution (`plan_idle_payout`, `execute_withdraw`). Atomic `withdraw`/`redeem` (`AtomicWithdraw`/`AtomicRedeem`) do not enforce this floor. Do not suggest applying `min_withdrawal_assets` to `proxy_view` `max_withdraw_value`/`max_redeem_value` computations — doing so would under-report valid atomic exit capacity.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `withdraw()` and `redeem()` include an idle-only atomic path that bypasses the queued withdrawal lifecycle. Do not change queue semantics without checking both paths

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), delegated withdrawal operators are explicitly rejected. The `withdraw` and `redeem` entrypoints enforce `operator == owner` and return `ContractError::InsufficientAllowance` when a different caller attempts to act on behalf of an owner. Do not flag missing allowance-decrement logic or suggest supporting delegated operators — the design intentionally prohibits delegation.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), async redemption flows (`withdraw`, `redeem`, `request_withdraw`) emit a `RedeemRequest` event rather than a generic ERC-4626 `Withdraw` event. This is intentional — it correctly reflects that the operation queues a withdrawal rather than immediately transferring assets. Do not flag the absence of a standard `Withdraw` event on these entrypoints as a compliance gap.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/soroban/src/contract/entrypoints.rs:806-836
Timestamp: 2026-04-28T08:29:30.164Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), the `SorobanVaultContract::initialize()` entrypoint intentionally has no caller-authorization check. Deployment is expected to go through a controlled/factory flow where `initialize` is called atomically with contract deployment, eliminating any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `RemoveMarket`, skim recipient changes, and share-token authority changes can all become authority drift or asset-loss bugs if altered casually

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 418
File: contract/proxy-4626-soroban/src/contract.rs:281-303
Timestamp: 2026-04-24T14:18:52.424Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts), the `initialize` function on `Soroban4626ProxyContract` intentionally has no caller-authorization check. The deployer is expected to call `initialize` atomically with contract deployment (Soroban's `create_contract` + init in a single transaction), which eliminates any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Serialized `VaultState` remains a practical resource boundary because Soroban persists a single `StateBlob`. Pending withdrawals are the main long-lived growth vector

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Watch for `OpState` transitions, escrow accounting, keeper-routed withdrawals, callback ordering, idle-balance resync, fee accrual, and reconciliation after partial failures in the vault contract

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Read `contract/vault/soroban/README.md`, `contract/vault/soroban/STRIDE.md`, `contract/vault/soroban/SIZE_BUDGET.md`, `contract/vault/soroban/src/contract/entrypoints.rs`, `contract/vault/soroban/src/effects/mod.rs`, `contract/vault/soroban/src/storage/mod.rs`, and the related shared-type and contract library files before making non-trivial changes to the Soroban executor layer

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : Review auth on every privileged Soroban entrypoint. Do not rely on outer routing alone

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), `ProxyPreviewView` (the 8-tuple returned by `proxy_view`) is mapped to the named struct `ProxyPreviewFields` defined in `contract/vault/soroban/shared-types/src/lib.rs`. Access inside the proxy uses named fields (e.g., `preview.convert_to_shares`), not anonymous tuple indices (`.0`–`.7`). Do not flag anonymous index access on `ProxyPreviewView` in this proxy as a maintenance risk.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/market/AGENTS.md:0-0
Timestamp: 2026-05-22T13:51:30.955Z
Learning: Applies to contract/market/src/**/*.rs : Prefer explicit `require!` guards for user-controlled bounds before mutating state, rather than relying on overflow panic behavior for bounds enforcement (e.g., in collateral withdrawal operations)

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/curator-primitives/tests/integration_tests.rs:32-35
Timestamp: 2026-04-27T12:49:20.949Z
Learning: In `contract/vault/curator-primitives/tests/integration_tests.rs`, the call `compute_payout_failure_outcome(escrow_shares, restore_idle, restore_idle)` is intentional. The function signature is `compute_payout_failure_outcome(escrow_shares, payout_amount, restore_idle)`, and in a failed-payout test the payout amount being restored to idle equals the restore_idle amount — so both arguments being the same value is correct test design. Do not flag this as a duplicate-argument bug.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-04T13:47:21.372Z
Learning: In Templar-Protocol/contracts, when re-reviewing a PR, only comment on issues introduced by the changes in that specific PR. Do not surface pre-existing issues or out-of-scope concerns from the broader codebase. Incremental review is expected.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : Changes to postcard serialization, versioning, migration, or storage keys are security-relevant and require careful review

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/market/AGENTS.md:0-0
Timestamp: 2026-05-22T13:51:30.955Z
Learning: Review the full initial-call plus finalize path for asynchronous execution flows (borrow, collateral withdrawal, liquidation, supply-withdrawal), not only the first function

Learnt from: peer2f00l
Repo: Templar-Protocol/contracts PR: 404
File: contract/vault/kernel/src/actions/mod.rs:1869-1871
Timestamp: 2026-04-14T07:35:09.869Z
Learning: In `contract/vault/kernel/src/actions/mod.rs` (Templar-Protocol/contracts, Rust), the vault kernel crate is compiled for Soroban (wasm32 target). Extracting helper functions from large dispatcher functions like `dispatch::apply_action` risks increasing wasm binary size due to additional call frames and reduced compiler inlining. Therefore, `#[allow(clippy::too_many_lines)]` suppressions on large dispatch/entrypoint functions in this file are intentional and should not be flagged as needing refactoring.

@carrion256

Copy link
Copy Markdown
Collaborator Author

Tip

For best results, initiate chat on the files or code changes.

@carrion256 — thank you for these corrections. All four points are valid; I acknowledge each below and will record them to avoid repeating the same mistakes.

1. **justfile / vault_payload.py — operator guard**: Withdrawn. Delegated atomic withdraw is supported via share allowance (confirmed by `test_atomic_withdraw_requires_allowance_for_delegated_operator`). The vault does **not** enforce `operator == owner`; it validates the share allowance instead. The operator guard I suggested would have broken delegated atomic exits.

2. **shared-types/src/lib.rs — ProxyViewFields / ProxyCapGroupView**: Withdrawn. The current file on this branch does not contain `ProxyViewFields`, `ProxyCapGroupView`, or proxy tuple aliases. The cited line ranges were stale and do not exist in the 872-line file. Both the struct-replacement and the test-improvement comments were invalid against the actual code.

3. **proxy_view dust floor**: Withdrawn. Atomic withdraw/redeem do not enforce `min_withdrawal_assets`; that floor applies only to queued withdrawal planning/execution. Applying it to `proxy_view` would incorrectly suppress valid atomic exits and under-report true capacity.

✏️ Learnings added

🧠 Learnings used

@CodeRabbit ai u need to approve the PR then since i resolved all ur comments :/

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

@carrion256 — fair point. All five inline comments were retracted because they were incorrect, not because they required code changes from you. There are no remaining blocking concerns from my side — the design-level observations (slippage lock, no cancellation path, etc.) were informational for human reviewers rather than blocking issues. Approving now.


🧠 Learnings used
Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `withdraw()` and `redeem()` include an idle-only atomic path that bypasses the queued withdrawal lifecycle. Do not change queue semantics without checking both paths

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` and `contract/vault/kernel/src/actions/mod.rs` (Templar-Protocol/contracts, Soroban vault), atomic `withdraw`/`redeem` (`AtomicWithdraw`/`AtomicRedeem`) support delegated operators via share token allowance. The vault does NOT enforce `operator == owner`; it validates share allowance instead. Test `test_atomic_withdraw_requires_allowance_for_delegated_operator` covers this. Do not suggest adding an operator == owner guard to `AtomicWithdraw`/`AtomicRedeem` entrypoints or tooling.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), delegated withdrawal operators are explicitly rejected. The `withdraw` and `redeem` entrypoints enforce `operator == owner` and return `ContractError::InsufficientAllowance` when a different caller attempts to act on behalf of an owner. Do not flag missing allowance-decrement logic or suggest supporting delegated operators — the design intentionally prohibits delegation.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), `min_withdrawal_assets` is a floor enforced only for queued withdrawal planning and execution (`plan_idle_payout`, `execute_withdraw`). Atomic `withdraw`/`redeem` (`AtomicWithdraw`/`AtomicRedeem`) do not enforce this floor. Do not suggest applying `min_withdrawal_assets` to `proxy_view` `max_withdraw_value`/`max_redeem_value` computations — doing so would under-report valid atomic exit capacity.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T10:40:47.074Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts, Soroban ERC-4626 proxy), async redemption flows (`withdraw`, `redeem`, `request_withdraw`) emit a `RedeemRequest` event rather than a generic ERC-4626 `Withdraw` event. This is intentional — it correctly reflects that the operation queues a withdrawal rather than immediately transferring assets. Do not flag the absence of a standard `Withdraw` event on these entrypoints as a compliance gap.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 417
File: contract/vault/soroban/src/contract/entrypoints.rs:806-836
Timestamp: 2026-04-28T08:29:30.164Z
Learning: In `contract/vault/soroban/src/contract/entrypoints.rs` (Templar-Protocol/contracts, Soroban vault), the `SorobanVaultContract::initialize()` entrypoint intentionally has no caller-authorization check. Deployment is expected to go through a controlled/factory flow where `initialize` is called atomically with contract deployment, eliminating any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/**/*.rs : `RemoveMarket`, skim recipient changes, and share-token authority changes can all become authority drift or asset-loss bugs if altered casually

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/scripts/vault_payload.py` (Templar-Protocol/contracts), the `--operator` argument for `atomic-withdraw` and `atomic-redeem` commands is intentionally free-form. An operator that differs from owner is valid when the owner has granted a share token allowance to that operator. Do not suggest forcing `operator = owner` or adding a validation guard that rejects operator != owner in this script.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 418
File: contract/proxy-4626-soroban/src/contract.rs:281-303
Timestamp: 2026-04-24T14:18:52.424Z
Learning: In `contract/proxy-4626-soroban/src/contract.rs` (Templar-Protocol/contracts), the `initialize` function on `Soroban4626ProxyContract` intentionally has no caller-authorization check. The deployer is expected to call `initialize` atomically with contract deployment (Soroban's `create_contract` + init in a single transaction), which eliminates any front-running window. Do not flag the missing auth check on `initialize` in this contract as a security issue.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Serialized `VaultState` remains a practical resource boundary because Soroban persists a single `StateBlob`. Pending withdrawals are the main long-lived growth vector

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-04T13:47:21.372Z
Learning: In Templar-Protocol/contracts, when re-reviewing a PR, only comment on issues introduced by the changes in that specific PR. Do not surface pre-existing issues or out-of-scope concerns from the broader codebase. Incremental review is expected.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Applies to contract/vault/soroban/governance/**/*.rs : Governance abdication is irreversible. Any change to governance action kind mapping or timelock policy needs a high-suspicion review

Learnt from: peer2f00l
Repo: Templar-Protocol/contracts PR: 404
File: contract/vault/kernel/src/actions/mod.rs:1869-1871
Timestamp: 2026-04-14T07:35:09.869Z
Learning: In `contract/vault/kernel/src/actions/mod.rs` (Templar-Protocol/contracts, Rust), the vault kernel crate is compiled for Soroban (wasm32 target). Extracting helper functions from large dispatcher functions like `dispatch::apply_action` risks increasing wasm binary size due to additional call frames and reduced compiler inlining. Therefore, `#[allow(clippy::too_many_lines)]` suppressions on large dispatch/entrypoint functions in this file are intentional and should not be flagged as needing refactoring.

Learnt from: carrion256
Repo: Templar-Protocol/contracts PR: 0
File: :0-0
Timestamp: 2026-06-05T12:40:18.815Z
Learning: In `contract/vault/soroban/shared-types/src/lib.rs` (Templar-Protocol/contracts, PR `#423` branch), the file does not contain `ProxyViewFields`, `ProxyCapGroupView`, or any proxy tuple type aliases. The file is approximately 872 lines. Do not cite these types or their associated line ranges when reviewing this file.

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Applies to contract/vault/src/**/*.rs : Read and inspect `contract/vault/src/lib.rs`, `contract/vault/src/impl_callbacks.rs`, `contract/vault/src/governance.rs`, and `common/src/vault/*` before making changes to the vault contract

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Read crate-local documentation before changing high-impact areas, especially `contract/vault/README.md`, `contract/vault/STRIDE.md`, `contract/universal-account/README.md`, and `service/relayer/README.md`

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-24T14:44:16.597Z
Learning: Watch for `OpState` transitions, escrow accounting, keeper-routed withdrawals, callback ordering, idle-balance resync, fee accrual, and reconciliation after partial failures in the vault contract

Learnt from: CR
Repo: Templar-Protocol/contracts PR: 0
File: contract/vault/soroban/AGENTS.md:0-0
Timestamp: 2026-06-04T16:07:43.185Z
Learning: Read `contract/vault/soroban/README.md`, `contract/vault/soroban/STRIDE.md`, `contract/vault/soroban/SIZE_BUDGET.md`, `contract/vault/soroban/src/contract/entrypoints.rs`, `contract/vault/soroban/src/effects/mod.rs`, `contract/vault/soroban/src/storage/mod.rs`, and the related shared-type and contract library files before making non-trivial changes to the Soroban executor layer
✅ Action performed

Comments resolved and changes approved.

@carrion256
carrion256 merged commit f6c17f2 into dev Jun 5, 2026
27 checks passed
carrion256 added a commit that referenced this pull request Jun 5, 2026
…6066-2ebb-4b87-9e22-844ce7913a9c)

Make Soroban withdrawal semantics explicit: withdraw/redeem stay atomic and idle-liquidity-only, request_withdraw remains the async queued exit path, and execute_withdraw only progresses fully backed queued withdrawals.

Preserves PR #423 tree; amends the commit subject for Halborn remediation tracking.
@carrion256
carrion256 deleted the feat/soroban-idle-withdraw-async-requests branch June 5, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant