Labels / Complexity: feature, soroban, protocol, rust · Extremely High — 500
Context
LoopingContract (quantara/soroban/contracts/looping/src/lib.rs) is the leverage-loop engine, but open_position only increments a counter and discards every input:
let key = symbol_short!("pos_cnt");
let count: u64 = env.storage().instance().get(&key).unwrap_or(0u64);
let position_id = count + 1;
env.storage().instance().set(&key, &position_id);
position_id // collateral, leverage, and user are never stored
close_position is an empty stub whose body is the comment // Stub: full unwind logic will be implemented in a future PR. The off-chain backend already models positions (Position, Transaction, ExtraDeposit in quantara/web_app/db/models.py) and writes a PositionOpened outbox event (quantara/web_app/tasks/outbox_relay.py), but none of that state exists on-chain.
This blocks the protocol's core loop: without an on-chain position record, there is no way to unwind a position, enforce leverage limits at close, or reconcile the backend's position lifecycle with the contract. A naive hardcoded version (a single u64 balance) would create a migration trap the moment multi-asset positions or partial closes are added.
Goal
Give the looping contract a persistent per-user position model and a working close path. Opening a position must store enough state (owner, collateral, borrowed asset, leverage) to later identify and close it, and closing must be authenticated and idempotent.
Scope
1. Position storage
Define a Position contract type (owner, collateral asset/amount, debt asset/amount, leverage, status) stored under a monotonically increasing position id. Preserve the existing pos_cnt counter semantics or migrate it deliberately.
2. Open position
Persist the full position in open_position, validating the leverage range already asserted (100..=500) and recording the collateral/debt amounts passed by the caller.
3. Close position
Implement close_position to authenticate the owner, verify the position exists and is open, mark it closed, and return a result the caller can use. Keep it idempotent (closing a closed position is a defined error, not a panic).
4. Tests
Add deterministic unit tests for open/close round-trips, double-close rejection, and non-owner close rejection, following the existing common crate's proptest style (quantara/soroban/contracts/common/src/math.rs).
Downstream impact
The backend's Position lifecycle and outbox relay (quantara/web_app/tasks/outbox_relay.py) assume an opened/closed status exists off-chain; this change should define the on-chain status values that the relay will eventually mirror. There are no generated bindings to regenerate, but the frontend deployContract / invokeSorobanContract path (quantara/frontend/src/services/contract.js, soroban.js) will need the new entry-point signatures when the flow is wired end-to-end.
Acceptance criteria
Contract
Tests
Out of scope
Do not implement the actual borrow-swap-redeposit mechanics or multi-asset support in this issue; only the position record and its lifecycle.
Getting started
Files in scope: quantara/soroban/contracts/looping/src/lib.rs, quantara/soroban/contracts/looping/Cargo.toml. Verify with:
cd quantara/soroban/contracts
cargo test -p looping
cargo build --target wasm32-unknown-unknown --release
Good first files to read: quantara/soroban/contracts/looping/src/lib.rs, quantara/soroban/contracts/liquidation/src/lib.rs (contracttype/storage pattern), quantara/soroban/contracts/common/src/auth.rs.
Labels / Complexity: feature, soroban, protocol, rust · Extremely High — 500
Context
LoopingContract(quantara/soroban/contracts/looping/src/lib.rs) is the leverage-loop engine, butopen_positiononly increments a counter and discards every input:close_positionis an empty stub whose body is the comment// Stub: full unwind logic will be implemented in a future PR.The off-chain backend already models positions (Position,Transaction,ExtraDepositinquantara/web_app/db/models.py) and writes aPositionOpenedoutbox event (quantara/web_app/tasks/outbox_relay.py), but none of that state exists on-chain.This blocks the protocol's core loop: without an on-chain position record, there is no way to unwind a position, enforce leverage limits at close, or reconcile the backend's position lifecycle with the contract. A naive hardcoded version (a single
u64balance) would create a migration trap the moment multi-asset positions or partial closes are added.Goal
Give the looping contract a persistent per-user position model and a working close path. Opening a position must store enough state (owner, collateral, borrowed asset, leverage) to later identify and close it, and closing must be authenticated and idempotent.
Scope
1. Position storage
Define a
Positioncontract type (owner, collateral asset/amount, debt asset/amount, leverage, status) stored under a monotonically increasing position id. Preserve the existingpos_cntcounter semantics or migrate it deliberately.2. Open position
Persist the full position in
open_position, validating the leverage range already asserted (100..=500) and recording the collateral/debt amounts passed by the caller.3. Close position
Implement
close_positionto authenticate the owner, verify the position exists and is open, mark it closed, and return a result the caller can use. Keep it idempotent (closing a closed position is a defined error, not a panic).4. Tests
Add deterministic unit tests for open/close round-trips, double-close rejection, and non-owner close rejection, following the existing
commoncrate's proptest style (quantara/soroban/contracts/common/src/math.rs).Downstream impact
The backend's
Positionlifecycle and outbox relay (quantara/web_app/tasks/outbox_relay.py) assume an opened/closed status exists off-chain; this change should define the on-chain status values that the relay will eventually mirror. There are no generated bindings to regenerate, but the frontenddeployContract/invokeSorobanContractpath (quantara/frontend/src/services/contract.js,soroban.js) will need the new entry-point signatures when the flow is wired end-to-end.Acceptance criteria
Contract
open_positionpersists the position record keyed by a returned id.close_positionis authenticated, idempotent, and restricted to the position owner.Tests
cd quantara/soroban/contracts && cargo test -p looping.Out of scope
Do not implement the actual borrow-swap-redeposit mechanics or multi-asset support in this issue; only the position record and its lifecycle.
Getting started
Files in scope:
quantara/soroban/contracts/looping/src/lib.rs,quantara/soroban/contracts/looping/Cargo.toml. Verify with:Good first files to read:
quantara/soroban/contracts/looping/src/lib.rs,quantara/soroban/contracts/liquidation/src/lib.rs(contracttype/storage pattern),quantara/soroban/contracts/common/src/auth.rs.