Skip to content

feat(proxy-4626): add Soroban ERC-4626 proxy#415

Closed
carrion256 wants to merge 1 commit into
refactor/extract-governancefrom
refactor/extract-governance-proxy
Closed

feat(proxy-4626): add Soroban ERC-4626 proxy#415
carrion256 wants to merge 1 commit into
refactor/extract-governancefrom
refactor/extract-governance-proxy

Conversation

@carrion256

@carrion256 carrion256 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add the Soroban ERC-4626 proxy crate
  • wire proxy command serialization to the compact vault command ABI
  • add unit, integration, and snapshot coverage for deposit/withdraw/redeem flows

Stack

Verification

  • cargo check -p templar-4626-proxy-soroban -p templar-soroban-runtime -p templar-soroban-governance --tests
  • cargo test -p templar-soroban-runtime -p templar-soroban-governance -p templar-4626-proxy-soroban --tests

This change is Reviewable

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Soroban ERC-4626 Proxy Implementation

Overview

Adds a new Soroban smart contract crate (templar-4626-proxy-soroban) implementing an ERC-4626 vault proxy interface. The proxy routes deposit, withdrawal, and redeem operations through an underlying vault contract while delegating view methods and managing share token authorization.

Key Changes

  • New contract module (contract.rs): 548 lines implementing Soroban4626ProxyContract with 21 public methods covering deposit/mint/withdraw/redeem flows and view methods
  • Error handling (error.rs): 6 contract error types with vault error code mapping and runtime error conversions
  • Comprehensive test coverage: 615 lines of unit tests and 449 lines of integration tests validating deposit, withdrawal, and queued execution flows
  • Configuration: Workspace integration via Cargo.toml with testutils feature flag

Critical Security Aspects

1. Authorization & Allowance Pattern ⚠️

  • require_auth_or_allowance() correctly checks caller authentication and token allowance against proxy contract
  • Deposit/mint correctly require direct caller authorization
  • Withdraw/redeem properly delegate via token allowance—matches ERC-4626 spec
  • Verify: Token contracts return truthful allowance values and don't exhibit strange state-mutation behavior

2. Vault Command Serialization & Address Handling ⚠️

  • Internal VaultCommandWireVaultCommand conversion serializes addresses via address_to_wire()
  • Uses Soroban's to_string().to_bytes() with UTF-8 validation; invalid UTF-8 returns InvalidInput
  • Critical assumption: Vault contract's execute() method correctly validates and processes serialized payloads
  • Risk: No validation that wire format produces canonical/consistent address representation across invocations

3. Return Type Validation

  • expect_*_result() helpers strictly validate vault response types (i128/u64/unit), preventing type confusion
  • Returns VaultError on mismatch, blocking silent failures from malicious vault implementations

4. Slippage Protection

  • DepositWithMin uses hardcoded min_shares_out: 0 for direct deposit() calls—users must use request_withdraw to set slippage
  • RequestWithdraw passes user-supplied min_assets_out without modification
  • Correct implementation; slippage responsibility properly delegated to users

5. Initialization & State Safety

  • Single-fire initialization with Initialized flag prevents reconfiguration
  • No reentrancy risks due to Soroban's contract isolation model

6. Token & Vault Contract Assumptions ⚠️

  • Dynamic invocation of token contracts (total_supply, balance, allowance, decimals, name, symbol)
  • Assumes tokens implement standard Stellar asset interface; malicious tokens cause generic VaultError propagation
  • call_proxy_view() delegates conversion and max calculations to vault—assumes vault respects caller context

Known Limitations

  • No arithmetic overflow checks (uses i128 directly); assumes vault validates amounts
  • Error propagation uses generic VaultError code—difficult to distinguish vault-side failures
  • No defense against token contracts with unusual behavior (zero decimals, balance manipulation, frozen accounts)

Recommendations for Reviewers

  1. Audit vault contract execute() and proxy_view() implementations for correct command validation and response types
  2. Verify Stellar asset tokens implement interface correctly and that allowance() is truthful
  3. Confirm address serialization consistency across all contract invocations—ensure no canonicalization issues
  4. Test edge cases: i128::MAX deposits, zero amounts, rapid allowance changes, mismatched token interfaces
  5. Validate vault error code mapping (error.rs lines 57–60) matches actual vault error codes

References

Walkthrough

A new Soroban ERC-4626 proxy contract is introduced alongside its configuration, error handling, and comprehensive test suite. The contract routes deposit, mint, withdraw, and redeem operations through a configured vault abstraction while maintaining share token balances and enforcing authorization.

Changes

Cohort / File(s) Summary
Configuration
.gitignore, Cargo.toml
Added workspace member and updated ignore rules for proxy contract test snapshots.
Contract Manifest
contract/proxy-4626-soroban/Cargo.toml
New manifest defining the templar-4626-proxy-soroban crate with required dependencies for Soroban SDK, vault kernel, curator primitives, and a testutils feature.
Contract Implementation
contract/proxy-4626-soroban/src/contract.rs
Core contract with 21 public entrypoints for ERC-4626 operations (deposit, mint, withdraw, redeem, request/execute flows, preview/max functions, metadata). Routes all vault operations through VaultCommand abstraction with internal authorization, allowance, and event handling.
Error Handling
contract/proxy-4626-soroban/src/error.rs
Defines ContractError and RuntimeError enums with numeric codes and From implementations mapping vault error codes and codec errors.
Library Entrypoint
contract/proxy-4626-soroban/src/lib.rs
Wires contract and error modules, exposes contract struct and shared types via pub use, defines internal view type aliases for proxy state representation.
Unit Tests
contract/proxy-4626-soroban/src/tests.rs
Comprehensive test harness with mock vault/token fixtures validating initialization idempotency, command serialization, event emission, preview delegation, and failure paths (missing auth, insufficient allowance).
Integration Tests
contract/proxy-4626-soroban/tests/integration_tests.rs
End-to-end tests exercising full deposit, queued withdrawal/redeem flows with ledger time manipulation, vault synchronization, and state transition verification.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Proxy as Soroban4626Proxy
    participant Vault as Vault Contract
    participant Token as Share Token
    
    Caller->>Proxy: deposit(assets, receiver)
    activate Proxy
    Proxy->>Proxy: authorize(caller)
    Proxy->>Vault: proxy_view() [get preview]
    Vault-->>Proxy: preview shares
    Proxy->>Vault: execute(Deposit command)
    activate Vault
    Vault->>Vault: process deposit
    Vault-->>Proxy: command result
    deactivate Vault
    Proxy->>Token: invoke share mint
    Proxy->>Proxy: emit Deposit event
    Proxy-->>Caller: return shares
    deactivate Proxy
Loading
sequenceDiagram
    participant Caller
    participant Proxy as Soroban4626Proxy
    participant Token as Share Token
    participant Vault as Vault Contract
    
    Caller->>Proxy: request_withdraw(shares, min_assets)
    activate Proxy
    Proxy->>Token: check allowance(contract)
    Token-->>Proxy: allowance amount
    Proxy->>Vault: execute(RequestWithdraw)
    activate Vault
    Vault->>Vault: queue withdrawal
    Vault-->>Proxy: request_id
    deactivate Vault
    Proxy->>Proxy: emit Withdraw event
    Proxy-->>Caller: return request_id
    deactivate Proxy
    
    Caller->>Proxy: execute_withdraw()
    activate Proxy
    Proxy->>Vault: execute(ExecuteWithdraw)
    activate Vault
    Vault->>Vault: process queued withdrawals
    Vault-->>Proxy: success
    deactivate Vault
    Proxy-->>Caller: return unit
    deactivate Proxy
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a Soroban ERC-4626 proxy contract implementation, which aligns with the primary content of the changeset.
Description check ✅ Passed The description directly relates to the changeset by summarizing the addition of the Soroban ERC-4626 proxy crate, proxy command serialization, and comprehensive test coverage for key flows.
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 refactor/extract-governance-proxy

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
carrion256 force-pushed the refactor/extract-governance-proxy branch from 9ec3ebd to 1df2c0a Compare April 23, 2026 09:37
@carrion256
carrion256 force-pushed the refactor/extract-governance branch from 595c43a to 09b12ca Compare April 23, 2026 09:41
@carrion256
carrion256 force-pushed the refactor/extract-governance-proxy branch from 1df2c0a to 62a55fa Compare April 23, 2026 09:41
@carrion256
carrion256 force-pushed the refactor/extract-governance branch from 09b12ca to ec781b5 Compare April 23, 2026 09:44
@carrion256
carrion256 force-pushed the refactor/extract-governance-proxy branch from 62a55fa to f9987f6 Compare April 23, 2026 09:44
@carrion256

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 23, 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 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 the current code and only fix it if needed.

Inline comments:
In `@contract/proxy-4626-soroban/src/contract.rs`:
- Around line 281-304: The initialize function (initialize) currently lacks
access control so anyone can call it before initialization; add an authorization
check (e.g., require caller == deployer/admin) or accept an init-only-once
signer to prevent front-running: record or derive an admin/deployer address at
deployment (or add a parameter set by constructor) and check
env.invoker()/env.source() against it before setting ProxyDataKey::VaultAddress,
ProxyDataKey::AssetToken, ProxyDataKey::ShareToken and
ProxyDataKey::Initialized; alternatively document that initialize must be called
atomically in deployment scripts and add comments on is_initialized usage to
prevent misuse.
- Around line 472-492: The allowance check in require_auth_or_allowance is using
the proxy address as the spender, allowing any authenticated caller to act if
the proxy was approved; change the call_token_view_with_two_addresses invocation
so the allowance is fetched for (owner, caller) (i.e., use caller as the spender
argument instead of env.current_contract_address()/proxy) so the function
verifies owner approved this specific caller before returning Ok; keep the
existing caller.require_auth() and error handling for insufficient allowance.

In `@contract/proxy-4626-soroban/src/error.rs`:
- Around line 55-62: Replace the magic numbers in
ContractError::from_vault_error_code by defining named constants for the vault
error codes (e.g., VAULT_ERR_INVALID_INPUT and VAULT_ERR_ALREADY_INITIALIZED or
an enum/associated consts on ContractError or a VaultErrorCode type) and use
those constants in the match (instead of 3 and 8) so the mapping to
Self::InvalidInput and Self::AlreadyInitialized is explicit and maintainable;
update any related comments to reference the vault contract error meanings.

In `@contract/proxy-4626-soroban/src/lib.rs`:
- Around line 13-24: The tuple type aliases (ProxyCoreView, ProxyPolicyView,
ProxyPreviewView, ProxyViewResponse) are undocumented and hard to interpret; add
concise doc comments above each type alias describing the semantics of each
tuple position (e.g., for ProxyCoreView list what (0,0) through (0,3) represent,
what each element of the nested tuples means, and similarly enumerate
ProxyPolicyView and ProxyPreviewView positions), or add a single doc comment on
ProxyViewResponse that references the vault's proxy_view documentation and maps
tuple indices to named meanings so readers can quickly understand fields like
vault address, fee values, policy entries, and preview components.

In `@contract/proxy-4626-soroban/src/tests.rs`:
- Around line 489-524: Add a new unit test (e.g.,
test_redeem_fails_without_allowance) that mirrors
test_withdraw_fails_without_allowance but calls Soroban4626ProxyContract::redeem
via fixture.env.as_contract and verifies require_auth_or_allowance behavior:
create owner and receiver with Fixture and Address::generate, do not mock or
grant allowance for the caller, call redeem with some shares and preview config,
assert the call returns an Err indicating authorization/allowance failure
(matching the withdraw test's expected error), and assert no WireVaultCommand
payloads were recorded by fixture.recorded_payloads(); reference Fixture,
Address::generate, require_auth_or_allowance, Soroban4626ProxyContract::redeem,
and recorded_payloads to locate relevant helpers.
🪄 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: 5536db4d-610f-4434-81ce-329fcad74592

📥 Commits

Reviewing files that changed from the base of the PR and between ec781b5 and f9987f6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .gitignore
  • Cargo.toml
  • contract/proxy-4626-soroban/Cargo.toml
  • contract/proxy-4626-soroban/src/contract.rs
  • contract/proxy-4626-soroban/src/error.rs
  • contract/proxy-4626-soroban/src/lib.rs
  • contract/proxy-4626-soroban/src/tests.rs
  • contract/proxy-4626-soroban/tests/integration_tests.rs

Comment on lines +281 to +304
pub fn initialize(
env: Env,
vault_address: Address,
asset_token: Address,
share_token: Address,
) -> Result<(), ContractError> {
if is_initialized(&env) {
return Err(ContractError::AlreadyInitialized);
}

env.storage()
.instance()
.set(&ProxyDataKey::VaultAddress, &vault_address);
env.storage()
.instance()
.set(&ProxyDataKey::AssetToken, &asset_token);
env.storage()
.instance()
.set(&ProxyDataKey::ShareToken, &share_token);
env.storage()
.instance()
.set(&ProxyDataKey::Initialized, &true);
Ok(())
}

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.

⚠️ Potential issue | 🟡 Minor

Consider adding access control to initialize.

The initialize function can be called by anyone. While the AlreadyInitialized check prevents re-initialization, a malicious actor could front-run the deployment and initialize the proxy with arbitrary vault/token addresses, potentially redirecting funds.

Consider either:

  1. Adding an admin/deployer check
  2. Ensuring deployment and initialization are atomic (same transaction)
  3. Documenting that this is intentional and initialization should happen in the deployment script

Based on learnings: "Evaluate edge cases around... authorization... risks in smart contracts."

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

In `@contract/proxy-4626-soroban/src/contract.rs` around lines 281 - 304, The
initialize function (initialize) currently lacks access control so anyone can
call it before initialization; add an authorization check (e.g., require caller
== deployer/admin) or accept an init-only-once signer to prevent front-running:
record or derive an admin/deployer address at deployment (or add a parameter set
by constructor) and check env.invoker()/env.source() against it before setting
ProxyDataKey::VaultAddress, ProxyDataKey::AssetToken, ProxyDataKey::ShareToken
and ProxyDataKey::Initialized; alternatively document that initialize must be
called atomically in deployment scripts and add comments on is_initialized usage
to prevent misuse.

Comment on lines +472 to +492
pub(crate) fn require_auth_or_allowance(
env: &Env,
caller: &Address,
owner: &Address,
token: &Address,
amount: i128,
) -> Result<(), ContractError> {
if caller == owner {
owner.require_auth();
return Ok(());
}

caller.require_auth();
let proxy = env.current_contract_address();
let allowance: i128 =
call_token_view_with_two_addresses(env, token, "allowance", owner, &proxy)?;

(allowance >= amount)
.then_some(())
.ok_or(ContractError::InsufficientAllowance)
}

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check ERC-4626 spec for allowance semantics in withdraw/redeem
# Also check if there's a different authorization pattern intended

# Search for how allowance is used in similar vault/proxy contracts
rg -n "allowance" -C 5 --type rust contract/vault/soroban/

# Check if there are comments explaining the authorization model
rg -n "auth" -i -C 3 --type rust contract/proxy-4626-soroban/src/contract.rs

Repository: Templar-Protocol/contracts

Length of output: 6203


🏁 Script executed:

#!/bin/bash
# Check the test to understand the expected authorization flow
rg -n "test_atomic_withdraw_requires_allowance_for_delegated_operator" -A 70 --type rust contract/vault/soroban/src/tests.rs | head -100

# Check if there's any recent context about allowance semantics
rg -n "require_auth_or_allowance\|allowance.*caller\|allowance.*proxy" -B 2 -A 2 --type rust contract/proxy-4626-soroban/

Repository: Templar-Protocol/contracts

Length of output: 3121


🏁 Script executed:

#!/bin/bash
# Check if there are any comments or documentation about the authorization model
rg -n "withdraw|redeem" -B 5 -A 8 --type rust contract/proxy-4626-soroban/src/contract.rs | grep -A 8 "pub fn withdraw\|pub fn redeem"

# Check the function signature for withdraw/redeem to understand parameter order
ast-grep --pattern 'pub fn withdraw($$$) -> $_'

Repository: Templar-Protocol/contracts

Length of output: 740


Critical authorization flaw: allowance check uses wrong spender address.

In require_auth_or_allowance, when caller != owner, the code checks allowance(owner, proxy) (line 487). This is incorrect. The allowance should verify that owner has approved caller (the actual operator), not the proxy contract.

Current behavior: Any authenticated caller can withdraw or redeem shares from owner as long as owner has previously approved the proxy for any reason, regardless of whether the caller is authorized.

Expected behavior: A caller can only withdraw or redeem from owner if owner has explicitly approved that specific caller.

This is demonstrated by the test test_atomic_withdraw_requires_allowance_for_delegated_operator (vault tests), which explicitly approves the operator (line 1245) and verifies allowance is checked between owner and operator (line 1258).

Proposed fix
 pub(crate) fn require_auth_or_allowance(
     env: &Env,
     caller: &Address,
     owner: &Address,
     token: &Address,
     amount: i128,
 ) -> Result<(), ContractError> {
     if caller == owner {
         owner.require_auth();
         return Ok(());
     }

     caller.require_auth();
-    let proxy = env.current_contract_address();
     let allowance: i128 =
-        call_token_view_with_two_addresses(env, token, "allowance", owner, &proxy)?;
+        call_token_view_with_two_addresses(env, token, "allowance", owner, caller)?;

     (allowance >= amount)
         .then_some(())
         .ok_or(ContractError::InsufficientAllowance)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contract/proxy-4626-soroban/src/contract.rs` around lines 472 - 492, The
allowance check in require_auth_or_allowance is using the proxy address as the
spender, allowing any authenticated caller to act if the proxy was approved;
change the call_token_view_with_two_addresses invocation so the allowance is
fetched for (owner, caller) (i.e., use caller as the spender argument instead of
env.current_contract_address()/proxy) so the function verifies owner approved
this specific caller before returning Ok; keep the existing
caller.require_auth() and error handling for insufficient allowance.

Comment on lines +55 to +62
impl ContractError {
pub(crate) const fn from_vault_error_code(code: u32) -> Self {
match code {
3 => Self::InvalidInput,
8 => Self::AlreadyInitialized,
_ => Self::VaultError,
}
}

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.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find vault error code definitions to verify the magic numbers match
# Expected: Error enum with InvalidInput=3 and AlreadyInitialized=8 (or similar)

rg -n "#\[contracterror\]" -A 20 --type rust contract/vault/soroban/

Repository: Templar-Protocol/contracts

Length of output: 5112


🏁 Script executed:

#!/bin/bash
# Check proxy contract's Cargo.toml to see dependencies
echo "=== Proxy contract Cargo.toml ===" && \
cat contract/proxy-4626-soroban/Cargo.toml | head -50

echo -e "\n=== Check if vault error module is exported ===" && \
rg -n "pub use|pub mod" contract/vault/soroban/src/error/ -A 2

echo -e "\n=== Full error.rs context in proxy contract ===" && \
cat -n contract/proxy-4626-soroban/src/error.rs

Repository: Templar-Protocol/contracts

Length of output: 3095


🏁 Script executed:

#!/bin/bash
# Check if vault error codes are publicly exported/accessible
echo "=== Vault soroban lib.rs to see public exports ===" && \
head -60 contract/vault/soroban/src/lib.rs

echo -e "\n=== Check vault soroban Cargo.toml ===" && \
cat contract/vault/soroban/Cargo.toml | head -60

Repository: Templar-Protocol/contracts

Length of output: 3499


🏁 Script executed:

#!/bin/bash
# Verify that proxy contract cannot import from vault runtime in normal deps
echo "=== Proxy contract dependencies check ===" && \
grep -A 10 "\[dependencies\]" contract/proxy-4626-soroban/Cargo.toml

echo -e "\n=== Check if from_vault_error_code is used and how ===" && \
rg -n "from_vault_error_code" contract/proxy-4626-soroban/

Repository: Templar-Protocol/contracts

Length of output: 1057


Define named constants for vault error codes.

The magic numbers in from_vault_error_code (3 → InvalidInput, 8 → AlreadyInitialized) match the vault contract's error codes and are used to translate cross-contract error responses. Extracting them as named constants improves readability and maintainability.

♻️ Suggested improvement
+// Vault error codes (from templar-soroban-runtime ContractError enum)
+const VAULT_ERROR_INVALID_INPUT: u32 = 3;
+const VAULT_ERROR_ALREADY_INITIALIZED: u32 = 8;
+
 impl ContractError {
     pub(crate) const fn from_vault_error_code(code: u32) -> Self {
         match code {
-            3 => Self::InvalidInput,
-            8 => Self::AlreadyInitialized,
+            VAULT_ERROR_INVALID_INPUT => Self::InvalidInput,
+            VAULT_ERROR_ALREADY_INITIALIZED => Self::AlreadyInitialized,
             _ => Self::VaultError,
         }
     }
 }
📝 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.

Suggested change
impl ContractError {
pub(crate) const fn from_vault_error_code(code: u32) -> Self {
match code {
3 => Self::InvalidInput,
8 => Self::AlreadyInitialized,
_ => Self::VaultError,
}
}
// Vault error codes (from templar-soroban-runtime ContractError enum)
const VAULT_ERROR_INVALID_INPUT: u32 = 3;
const VAULT_ERROR_ALREADY_INITIALIZED: u32 = 8;
impl ContractError {
pub(crate) const fn from_vault_error_code(code: u32) -> Self {
match code {
VAULT_ERROR_INVALID_INPUT => Self::InvalidInput,
VAULT_ERROR_ALREADY_INITIALIZED => Self::AlreadyInitialized,
_ => Self::VaultError,
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contract/proxy-4626-soroban/src/error.rs` around lines 55 - 62, Replace the
magic numbers in ContractError::from_vault_error_code by defining named
constants for the vault error codes (e.g., VAULT_ERR_INVALID_INPUT and
VAULT_ERR_ALREADY_INITIALIZED or an enum/associated consts on ContractError or a
VaultErrorCode type) and use those constants in the match (instead of 3 and 8)
so the mapping to Self::InvalidInput and Self::AlreadyInitialized is explicit
and maintainable; update any related comments to reference the vault contract
error meanings.

Comment on lines +13 to +24
pub(crate) type ProxyCoreView = (
(Address, Address, Address, Address),
(i128, i128, bool),
(i128, i128, i128, i128),
(i128, u64, i128, i128),
);
pub(crate) type ProxyPolicyView = (
soroban_sdk::Vec<u32>,
soroban_sdk::Vec<(soroban_sdk::String, i128, i128)>,
);
pub(crate) type ProxyPreviewView = (i128, i128, i128, i128, i128, i128, i128, i128);
pub(crate) type ProxyViewResponse = (ProxyCoreView, ProxyPolicyView, ProxyPreviewView);

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.

🧹 Nitpick | 🔵 Trivial

Consider documenting the tuple field semantics.

The proxy view type aliases use large tuples that can be difficult to interpret without context. Consider adding doc comments explaining what each tuple position represents (e.g., ProxyCoreView.0.0 = vault address, etc.), or at minimum a reference to the vault's proxy_view documentation.

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

In `@contract/proxy-4626-soroban/src/lib.rs` around lines 13 - 24, The tuple type
aliases (ProxyCoreView, ProxyPolicyView, ProxyPreviewView, ProxyViewResponse)
are undocumented and hard to interpret; add concise doc comments above each type
alias describing the semantics of each tuple position (e.g., for ProxyCoreView
list what (0,0) through (0,3) represent, what each element of the nested tuples
means, and similarly enumerate ProxyPolicyView and ProxyPreviewView positions),
or add a single doc comment on ProxyViewResponse that references the vault's
proxy_view documentation and maps tuple indices to named meanings so readers can
quickly understand fields like vault address, fee values, policy entries, and
preview components.

Comment on lines +489 to +524
#[test]
fn test_redeem_queued_flow() {
let fixture = Fixture::new();
let owner = Address::generate(&fixture.env);
let receiver = Address::generate(&fixture.env);

fixture.env.mock_all_auths();
fixture.initialize().expect("initialize succeeds");
fixture.set_preview(MockPreviewConfig {
convert_to_assets: 88,
..Default::default()
});

let request_id = fixture.env.as_contract(&fixture.proxy, || {
Soroban4626ProxyContract::redeem(
fixture.env.clone(),
owner.clone(),
55,
receiver.clone(),
owner.clone(),
)
});

assert_eq!(request_id, Ok(42));
let payloads = fixture.recorded_payloads();
assert_eq!(payloads.len(), 1);
assert_eq!(
decode_command(&payloads.get(0).expect("request payload exists")),
WireVaultCommand::RequestWithdraw {
owner: address_wire(&owner),
receiver: address_wire(&receiver),
shares: 55,
min_assets_out: 88,
}
);
}

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.

🧹 Nitpick | 🔵 Trivial

Consider adding a test for redeem with insufficient allowance.

The withdraw path has an allowance failure test (test_withdraw_fails_without_allowance), but redeem also performs allowance checks via require_auth_or_allowance. Consider adding a similar test for the redeem flow to ensure consistent coverage. Based on learnings: "Evaluate edge cases around refunds, withdrawals, account deletion, authorization, and replay or double-execution risks in smart contracts."

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

In `@contract/proxy-4626-soroban/src/tests.rs` around lines 489 - 524, Add a new
unit test (e.g., test_redeem_fails_without_allowance) that mirrors
test_withdraw_fails_without_allowance but calls Soroban4626ProxyContract::redeem
via fixture.env.as_contract and verifies require_auth_or_allowance behavior:
create owner and receiver with Fixture and Address::generate, do not mock or
grant allowance for the caller, call redeem with some shares and preview config,
assert the call returns an Err indicating authorization/allowance failure
(matching the withdraw test's expected error), and assert no WireVaultCommand
payloads were recorded by fixture.recorded_payloads(); reference Fixture,
Address::generate, require_auth_or_allowance, Soroban4626ProxyContract::redeem,
and recorded_payloads to locate relevant helpers.

@carrion256

Copy link
Copy Markdown
Collaborator Author

Superseded by the SPR-managed stack: #417 -> #418 -> #419.

@carrion256 carrion256 closed this Apr 23, 2026
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