Implement the core operations of the Arbellar Vault contract — the accounting and custody layer of the protocol.
The vault contract currently exists only as scaffolding: contracts/vault/src/lib.rs contains an empty #[contract] struct with no logic. The complete behavior specification is documented in contracts/vault/README.md.
This issue covers the user-facing vault lifecycle: initialize, create_vault, deposit, withdraw, and get_balance, together with a focused unit-test suite. Internal execution hooks (transfer_for_execution / return_from_execution) are intentionally out of scope — they belong to a later execution-focused issue.
The vault must enforce Arbellar's core principles: non-custodial ownership, explicit authorization, and accurate on-chain accounting.
📍 File Scope
Applies to:
contracts/vault/src/lib.rs
contracts/vault/Cargo.toml (only if a dependency adjustment is strictly required)
- Unit tests may live in
contracts/vault/src/lib.rs (#[cfg(test)]) or a contracts/vault/src/test.rs module
Use the shared types from the arbellar-types package (require Issue 1 to be merged, or depend on the types specified in packages/types/README.md).
🎯 Objective
Provide the on-chain foundation for the Arbellar protocol:
- One vault per user, with the user as the sole owner
- User deposits and withdrawals of supported assets (Phase 1: USDC, XLM)
- Strict authorization — only the vault owner can deposit to / withdraw from their vault
- Accurate balance accounting that maintains the accounting invariant
- A clear
initialize entry point that records the protocol admin and treasury
- Events emitted for every state change
📝 Implementation Requirements
Contract Entry Points
Implement the following public functions:
pub fn initialize(env: Env, admin: Address, treasury: Address);
pub fn create_vault(env: Env, user: Address) -> VaultId;
pub fn deposit(env: Env, user: Address, asset: Address, amount: i128) -> Result<(), Error>;
pub fn withdraw(env: Env, user: Address, asset: Address, amount: i128) -> Result<(), Error>;
pub fn get_balance(env: Env, user: Address, asset: Address) -> i128;
Behavior Details
initialize — must be callable exactly once; stores the admin and treasury addresses from arbellar-types::ProtocolConfig-style configuration.
create_vault — generates a unique VaultId, creates a Vault with zero balances and VaultStatus::Active, and emits a VaultCreated event.
deposit — requires the user's authorization, validates that the amount is positive and the asset is supported (USDC 0x... / XLM per the asset registry; see the spec), transfers the tokens from the user wallet into the contract, updates the vault balance by exactly amount, and emits a Deposit event.
withdraw — requires the user's authorization, enforces vault ownership, validates the amount, checks the vault is not InExecution, enforces the principal/balance limit, transfers tokens back to the user, decreases the balance by exactly amount, and emits a Withdrawal event.
get_balance — read-only query, returns the current vault balance for the asset (and 0 for a missing vault/asset).
Error — reuse arbellar_types::Error (e.g., Unauthorized, VaultNotFound, InvalidAsset, InsufficientBalance if extended consistently with the spec) for Result returns.
Key Security Requirements
- All state-changing operations require
user.require_auth().
- The vault owner is the only address authorized to withdraw.
- No overflow/underflow — the workspace release profile already enables
overflow-checks, but logic must handle bounded amounts defensively.
- Never let a failed or unauthorized operation change state.
- Emit events with
env.events().publish(...) using short symbols such as VaultCreated, Deposit, Withdrawal.
Suggested Storage Layout
Use a single storage key for the vault registry (e.g., DataKey::Vault(Address)), a counter for VaultId generation, and the arbellar-types enums for status. Document any storage-key decision with a short comment or add it to the module docs.
🧪 Verification
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features --package vault
cargo build --target wasm32-unknown-unknown --release
Add at minimum the following unit tests (using the Soroban test framework, Env::default()):
test_initialize_sets_admin_and_treasury
test_create_vault_assigns_unique_ids
test_deposit_increases_balance
test_deposit_unsupported_asset_fails
test_deposit_zero_or_negative_amount_fails
test_withdraw_by_owner
test_withdraw_by_non_owner_fails
test_withdraw_exceeding_balance_fails
test_get_balance_returns_zero_for_missing_vault
Verify:
- Unit tests pass
- No TypeScript-equivalent compile warnings; no
clippy warnings
- The contract builds for
wasm32-unknown-unknown in release mode
- Events are emitted with the expected topics/data
✅ Acceptance Criteria
🌿 Suggested Branch
git checkout -b feat/vault-core-operations
📝 Suggested Commit Message
feat(vault): implement core vault operations with unit tests
📦 PR Requirements
- Assignment required before starting
- Time Limit: ETA 24 hrs
Implement the core operations of the Arbellar Vault contract — the accounting and custody layer of the protocol.
The vault contract currently exists only as scaffolding:
contracts/vault/src/lib.rscontains an empty#[contract]struct with no logic. The complete behavior specification is documented incontracts/vault/README.md.This issue covers the user-facing vault lifecycle: initialize, create_vault, deposit, withdraw, and get_balance, together with a focused unit-test suite. Internal execution hooks (
transfer_for_execution/return_from_execution) are intentionally out of scope — they belong to a later execution-focused issue.The vault must enforce Arbellar's core principles: non-custodial ownership, explicit authorization, and accurate on-chain accounting.
📍 File Scope
Applies to:
contracts/vault/src/lib.rscontracts/vault/Cargo.toml(only if a dependency adjustment is strictly required)contracts/vault/src/lib.rs(#[cfg(test)]) or acontracts/vault/src/test.rsmodule🎯 Objective
Provide the on-chain foundation for the Arbellar protocol:
initializeentry point that records the protocol admin and treasury📝 Implementation Requirements
Contract Entry Points
Implement the following public functions:
Behavior Details
initialize— must be callable exactly once; stores the admin and treasury addresses fromarbellar-types::ProtocolConfig-style configuration.create_vault— generates a uniqueVaultId, creates aVaultwith zero balances andVaultStatus::Active, and emits aVaultCreatedevent.deposit— requires the user's authorization, validates that the amount is positive and the asset is supported (USDC0x.../ XLM per the asset registry; see the spec), transfers the tokens from the user wallet into the contract, updates the vault balance by exactlyamount, and emits aDepositevent.withdraw— requires the user's authorization, enforces vault ownership, validates the amount, checks the vault is notInExecution, enforces the principal/balance limit, transfers tokens back to the user, decreases the balance by exactlyamount, and emits aWithdrawalevent.get_balance— read-only query, returns the current vault balance for the asset (and0for a missing vault/asset).Error— reusearbellar_types::Error(e.g.,Unauthorized,VaultNotFound,InvalidAsset,InsufficientBalanceif extended consistently with the spec) forResultreturns.Key Security Requirements
user.require_auth().overflow-checks, but logic must handle bounded amounts defensively.env.events().publish(...)using short symbols such asVaultCreated,Deposit,Withdrawal.Suggested Storage Layout
Use a single storage key for the vault registry (e.g.,
DataKey::Vault(Address)), a counter forVaultIdgeneration, and thearbellar-typesenums for status. Document any storage-key decision with a short comment or add it to the module docs.🧪 Verification
cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings cargo test --all-features --package vault cargo build --target wasm32-unknown-unknown --releaseAdd at minimum the following unit tests (using the Soroban test framework,
Env::default()):test_initialize_sets_admin_and_treasurytest_create_vault_assigns_unique_idstest_deposit_increases_balancetest_deposit_unsupported_asset_failstest_deposit_zero_or_negative_amount_failstest_withdraw_by_ownertest_withdraw_by_non_owner_failstest_withdraw_exceeding_balance_failstest_get_balance_returns_zero_for_missing_vaultVerify:
clippywarningswasm32-unknown-unknownin release mode✅ Acceptance Criteria
initializeis implemented and guarded against double initializationcreate_vaultcreates a single-owner vault with a uniqueVaultIddeposittransfers tokens, validates amount/asset, and updates vault balancewithdrawenforces ownership, balance limits, and correct token transferget_balancereturns accurate balance for the requested assetError::UnauthorizedVaultCreated,Deposit,Withdrawal) are emitted on successcargo fmt --all -- --checkpassescargo clippy --all-targets --all-features -- -D warningspassescargo build --target wasm32-unknown-unknown --releasesucceedsarbellar-typesshared types rather than redefining them in the contract🌿 Suggested Branch
📝 Suggested Commit Message
📦 PR Requirements