Trade

circle-info

Available on BNB Chain Core Pool.

Overview

Venus Trade is a relative performance trading system built as a peripheral orchestration layer on top of Venus Protocol's existing lending and borrowing infrastructure. It allows users to express a view that one asset will outperform another — packaged into a single, easy-to-manage position with automated execution, proportional closing, and built-in yield generation.

Users deposit a stablecoin collateral (the Default Settlement Asset, or DSA), select a long and short vToken pair, and Trade handles all leveraged execution — flash loans, swaps, supply, and borrow — atomically in a single transaction.

This is not directional trading. Trade positions profit (or lose) based on the relative price movement between two assets, regardless of whether the overall market is going up or down. While held, positions also generate yield: supply APY on the long asset, DSA APY on collateral, minus borrow APY on the short asset.

Component
Description

RelativePositionManager

The main orchestration contract that manages the full lifecycle of Trade positions — from activation and opening to proportional closing and deactivation

PositionAccount

A dedicated smart contract account deployed per user per trading pair. All collateral, borrow positions, and yield accrual live here, fully isolated from other positions

Paired Positions

Long and short legs treated as a single unit with combined PnL, health, and lifecycle management

Default Settlement Asset (DSA)

A designated stablecoin (USDT or USDC) used as collateral and as the currency for all PnL settlement

Proportional Closing

Flexible partial or full position closing using on-chain flash loans and token swaps, with automatic dust handling

Capital Utilization Tracking

Real-time calculation of how much deposited collateral is locked by open positions, enabling accurate withdrawable balance reporting

No changes were made to the Venus Core Pool, Comptroller, vToken contracts, interest rate models, oracles, or any existing protocol infrastructure. Existing users are not affected.


Architecture

The Trade system consists of four contracts:

Contract
Role

RelativePositionManager

Central orchestration contract. Manages position state, validates operations, calculates utilization, and delegates execution to PositionAccount.

PositionAccount

A minimal proxy clone deployed per user per trading pair. Holds all funds, enters Venus markets, and delegates leverage execution to LeverageStrategiesManager.

LeverageStrategiesManager

Pre-existing Venus periphery contract. Executes flash loans via the Comptroller and invokes the SwapHelper.

SwapHelper

Pre-existing Venus periphery contract. Executes authorized on-chain swaps using signed multicall data.

Trade Architecture Overview

Position Isolation

Every unique (user, longVToken, shortVToken) triple gets its own PositionAccount — a minimal EIP-1167 clone deployed with a deterministic CREATE2 salt. Collateral, debt, and Health Factor are entirely independent across accounts. A liquidation or loss on one PositionAccount cannot affect any other.

Trade Position Isolation
Layer
Mechanism

Fund custody

Each PositionAccount is a separate on-chain address. Venus vTokens and underlying balances are held there, not in the RelativePositionManager.

Access restriction

PositionAccount.onlyRelativePositionManager reverts any call not originating from the single trusted manager address.

Comptroller membership

Each PositionAccount enters Venus markets independently. Its Health Factor is computed solely from its own supplied and borrowed balances.

Delegate approval

During initialization the PositionAccount approves both the RelativePositionManager and LeverageStrategiesManager as delegates in the Comptroller — this allows flash-loan execution while keeping all debt on the PositionAccount, not on the manager.

Deterministic deployment

Clones are deployed with ClonesUpgradeable.cloneDeterministic(impl, keccak256(user, long, short)). A given user cannot create two accounts for the same pair; the address is unique and pre-determined.


Key Concepts

Long Leg and Short Leg

A Trade position always consists of two legs:

  • Long Leg — the asset expected to outperform. Supplied into Venus to earn supply APY.

  • Short Leg — the asset expected to underperform. Borrowed from Venus, creating an interest cost.

The two legs are treated as a single position with combined PnL — users never manage them separately.

Default Settlement Asset (DSA)

When activating a position, the user selects a Default Settlement Asset (USDT or USDC). The DSA serves as:

  • The initial collateral deposited into the PositionAccount

  • The asset that backs borrow risk (its collateral factor determines how much can be borrowed)

  • The currency in which realized profits are accumulated

  • The asset used to cover losses on close

Leverage

Leverage amplifies exposure to relative price movement. The maximum leverage is derived from the collateral factors of the DSA and long asset:

maxLeverage=CFDSA1CFlong×(1tolerance)\text{maxLeverage} = \frac{CF_{DSA}}{1 - CF_{long} \times (1 - \text{tolerance})}

Where tolerance is the proportionalCloseTolerance, used as a buffer to prevent activations that would immediately be at the boundary of the safe borrow range. Leverage is recorded at activation and is fixed for the position's lifecycle.

Capital Utilization

Capital utilization measures how much of the deposited DSA principal is "locked" by the open leveraged position. The remainder — Available Capital — can be withdrawn or used to scale the position further. See Capital Utilization for the full formula.

Yield Generation

Trade positions generate yield while held:

Component
Description

Supply APY

The long asset is supplied into Venus and earns lending yield

DSA APY

The stablecoin collateral is supplied into Venus and earns yield

Borrow APY

Interest paid on the borrowed short asset (deducted from yield)

Net APY

DSA APY + Supply APY − Borrow APY

All yield accrues automatically and is reflected in position balances in real time. There is no claim or harvest step.


Implementation Details

1. RelativePositionManager

The primary external interface for all Trade operations. All user-facing calls pass through this contract, which enforces lifecycle rules, validates parameters, computes capital utilization, and delegates execution to the PositionAccount.

Inheritance:

  • AccessControlledV8 — governance-gated admin functions via AccessControlManager

  • ReentrancyGuardUpgradeable — reentrancy protection on all state-changing calls

  • Custom two-level pause — isPartiallyPaused / isCompletelyPaused with whenNotPaused and whenNotCompletelyPaused modifiers

Key state:

activateAndOpenPosition

Atomically activates a new position for the caller and executes the first leveraged open in a single transaction. If a PositionAccount clone does not yet exist for this (user, long, short) triple, it is deployed in the same transaction.

The function executes three sequential phases:

  1. Validation — verifies that longVToken and shortVToken are listed in the Comptroller and are not the same market, that neither is vBNB, that the DSA index maps to an active registered DSA, that effectiveLeverage is in [1e18, maxLeverage], and that no active position already exists for this pair.

  2. Activation — deploys the PositionAccount clone if needed (via ClonesUpgradeable.cloneDeterministic), increments cycleId, records the position configuration, calls Comptroller.enterMarketBehalf to register the DSA market for the PositionAccount, and calls vToken.mintBehalf to deposit initialPrincipal. The returned vToken amount is stored as suppliedPrincipalVTokens.

  3. Open — validates shortAmount ≤ maxBorrow (available capital × clamped leverage / short price), then calls positionAccount.enterLeverage(longVToken, 0, shortVToken, shortAmount, minLongAmount, swapData). The LeverageStrategiesManager flash-loans shortAmount, swaps to the long asset, and supplies it to the PositionAccount's long vToken position.

The caller must approve initialPrincipal of the DSA underlying to the RelativePositionManager before calling. The function uses mintBehalf so that the vToken balance accrues directly on the PositionAccount — the RPM itself holds no collateral.


scalePosition

Adds additional short exposure to an existing active position. Optionally accepts more DSA principal if additionalPrincipal > 0.

Scaling recalculates maxBorrow from the updated available capital (including any newly deposited principal) before validating shortAmount. The stored effectiveLeverage is not updated by scaling — it remains fixed at the value set during activation and is used for utilization calculations across the position's lifecycle.


closeWithProfit

Proportionally closes a fraction of the position when the long asset has outperformed the short asset. Takes two swap legs: one to repay debt and one to harvest the profit into DSA principal.

The caller specifies closeFractionBps (1–10000) to indicate what fraction of the position to close. The protocol validates that the total long amount specified (longAmountToRedeemForRepay + longAmountToRedeemForProfit) is within proportionalCloseTolerance of the expected proportional amount:

Execution flow:

  1. Redeem longAmountToRedeemForRepay from the PositionAccount's long vToken position

  2. Swap long → short via positionAccount.exitLeverage() to repay the proportional debt

  3. Redeem longAmountToRedeemForProfit from the long vToken position

  4. Swap long → DSA and mint as DSA principal on the PositionAccount

  5. Update suppliedPrincipalVTokens += newly minted DSA vTokens (profit increases Available Capital)

One-leg close (skip profit conversion): The profit swap leg is optional. Pass longAmountToRedeemForProfit = 0, minAmountOutProfit = 0, and swapDataProfit = "0x" to skip it. The repay leg always executes. If there is outstanding short debt, longAmountToRedeemForRepay must be non-zero — passing zero while debt exists reverts with MinAmountOutRepayBelowDebt.


closeWithLoss

Proportionally closes a fraction of the position when the long asset value is insufficient to cover the proportional short debt. Uses a two-leg approach to repay the full debt fraction.

  • Leg 1 — redeems long collateral, swaps long → short, repays as much debt as possible

  • Leg 2 — redeems DSA collateral, swaps DSA → short (or uses directly if DSA == short via exitSingleAssetLeverage), repays remaining debt

Leg 2 is optional — pass dsaAmountToRedeemForSecondSwap = 0 if leg 1 fully covers the debt. The suppliedPrincipalVTokens counter is decremented by the DSA vTokens burned in leg 2, reflecting the principal consumed to cover the loss.

One-leg close behavior: Both legs are individually optional, enabling single-asset close paths:

Close path
How to invoke
When to use

Long only

Set dsaAmountToRedeemForSecondSwap = 0

Leg 1 (long → short swap) fully covers the proportional debt; no DSA needs to be burned

DSA only

Set longAmountToRedeemForFirstSwap = 0 and shortAmountToRepayForFirstSwap = 0

Long collateral has been partially or fully seized by a liquidator; only DSA principal remains to repay debt

Restriction: If longAmountToRedeemForFirstSwap = 0 but shortAmountToRepayForFirstSwap != 0, the call reverts with InvalidLongAmountToRedeem. Skipping the long leg while claiming a non-zero repay from it would illegitimately reduce the second-leg repay obligation without any actual collateral being redeemed.


supplyPrincipal

Deposits additional DSA collateral into the PositionAccount without opening a new leveraged trade. Transfers DSA from the caller, calls vToken.mintBehalf to supply it on the PositionAccount, and increments suppliedPrincipalVTokens.

This is the primary defensive Health Factor management tool. Supplying more principal increases the DSA collateral backing the borrow, directly raising the Health Factor without any position unwind or swap. Users facing a declining Health Factor should call this before the position becomes liquidatable. Allowed during partial pause — classified as a defensive operation and never blocked during incidents.


withdrawPrincipal

Withdraws Available Capital (unused DSA collateral) from the PositionAccount to the caller's wallet.

The maximum withdrawable amount is computed by getUtilizationInfo() as availableCapitalUSD / dsaPrice. The function redeems the corresponding DSA vTokens from the PositionAccount and decrements suppliedPrincipalVTokens. Blocked during partial pause.


deactivatePosition

Fully exits a position after the short debt has been repaid to zero. Exits all Venus markets, redeems all remaining collateral (both DSA and long), and transfers everything to the caller's wallet.

After deactivation, isActive is set to false and suppliedPrincipalVTokens is cleared. The PositionAccount contract remains deployed — a subsequent call to activateAndOpenPosition increments cycleId and starts a fresh cycle without redeploying the clone.

When DSA ≠ long, collateral is redeemed from two separate markets. When DSA == long, a single redemption is performed with a treasury fee grossup to avoid over-redemption:


2. PositionAccount

A minimal proxy clone (EIP-1167) deployed per user per trading pair. It holds all collateral and debt positions in Venus and is the sole entity that enters markets and executes borrows and supplies — the RelativePositionManager never holds funds itself.

Deployment: Clones use ClonesUpgradeable.cloneDeterministic with a CREATE2 salt:

This makes the PositionAccount address deterministic and pre-computable via getPositionAccountAddress() before the account is deployed.

Access control: The onlyRelativePositionManager modifier reverts any call not originating from the single trusted RelativePositionManager address. No EOA or other contract can directly move funds out of a PositionAccount.

initialize

Called once when the clone is first deployed. Sets the position owner, long vToken, and short vToken for this clone, and approves both the RelativePositionManager and LeverageStrategiesManager as delegates in the Venus Comptroller.

The delegate approval is what allows the RelativePositionManager to call Comptroller.enterMarketBehalf and vToken.mintBehalf on behalf of the PositionAccount while the PositionAccount remains the on-chain owner of all collateral and debt. The RelativePositionManager and LeverageStrategiesManager addresses are set in the constructor as immutables shared across all clones.


enterLeverage

Initiates the flash loan sequence to open a leveraged long position. Called by the RelativePositionManager as part of activateAndOpenPosition and scalePosition.

Internally delegates to LeverageStrategiesManager.flashLoan(...), which:

  1. Flash-loans borrowedAmountToFlashLoan of the short asset from the Comptroller's flash loan module

  2. Swaps the short asset to the long asset via the SwapHelper using swapData

  3. Supplies the resulting long tokens to the PositionAccount's long vToken position via vToken.mintBehalf

  4. Repays the flash loan by opening a borrow on the PositionAccount's short vToken position


exitLeverage

Initiates the flash loan sequence to close a leveraged position — redeem collateral, swap, and repay debt. Used in both closeWithProfit and closeWithLoss.

Internally delegates to LeverageStrategiesManager.flashLoan(...), which:

  1. Flash-loans borrowedAmountToFlashLoan of the short asset

  2. Redeems collateralAmountToRedeemForSwap from the PositionAccount's collateral vToken position

  3. Swaps the redeemed collateral to the short asset via the SwapHelper

  4. Repays the flash loan + the original borrow debt using the swap output


exitSingleAssetLeverage

Used when the DSA and the short asset are the same token (e.g., the position uses USDC as both DSA and borrow). In this case, no swap is required to repay the debt — DSA collateral is redeemed directly to cover the borrow.


Position Data Model

cycleId provides a monotonically increasing identifier for each activation. This allows off-chain systems and events to distinguish between different activation cycles of the same pair without ambiguity.

suppliedPrincipalVTokens is updated on every principal supply, withdrawal, profit conversion, and after liquidations (synced down if the actual balance has been reduced by a liquidator).


Position Lifecycle

Stage 1: Activation and Opening

activateAndOpenPosition() is the entry point. It combines activation and first open into one atomic transaction.

Activation steps:

  1. Validate that the long and short markets are listed in the Comptroller and are not the same market

  2. Validate that vBNB is not used (not supported)

  3. Validate that the DSA index refers to an active, registered DSA vToken

  4. Validate leverage is in the range [1e18, maxLeverage]

  5. Deploy a PositionAccount clone if one does not exist for this (user, long, short) triple

  6. Set isActive = true, increment cycleId, record dsaIndex, dsaVToken, effectiveLeverage

  7. Enter the DSA market on behalf of the PositionAccount via Comptroller.enterMarketBehalf

  8. Transfer DSA from the user and mint vTokens on behalf of the PositionAccount via vToken.mintBehalf

  9. Record suppliedPrincipalVTokens

  10. Emit PositionActivated and PrincipalSupplied

Opening steps (executed after activation):

  1. Calculate maxBorrow from available capital and clamped leverage

  2. Validate shortAmount <= maxBorrow

  3. Call positionAccount.enterLeverage(longVToken, 0, shortVToken, shortAmount, minLongAmount, swapData)

  4. LeverageStrategiesManager flash-loans shortAmount of the short asset, swaps to the long asset, and mints it on the PositionAccount

  5. Any dust from the swap is swept from the PositionAccount to the user's wallet

  6. Emit PositionOpened

Maximum leverage formula:

maxLeverage=CFDSA1CFlong×(1tolerance)\text{maxLeverage} = \frac{CF_{DSA}}{1 - CF_{long} \times (1 - \text{tolerance})}

Where tolerance is the proportionalCloseTolerance used as a buffer. This prevents activations that would immediately be at the boundary of the safe borrow range.


Stage 2: Scaling (Adding to a Position)

scalePosition() adds additional exposure to an existing active position.

  • Optionally supplies additional DSA principal if additionalPrincipal > 0

  • Recalculates maxBorrow using the updated available capital

  • Calls positionAccount.enterLeverage(...) for the additional amount

  • Emits PositionScaled

The stored effectiveLeverage is not changed by scaling. Only suppliedPrincipalVTokens increases if additional principal is supplied.


Stage 3: Closing

Closing is proportional. The caller specifies closeFractionBps (1–10000 BPS, where 10000 = 100%) and the system closes exactly that fraction of the current position.

There are two closing paths depending on whether the position is in profit or in loss.

Profit Close (closeWithProfit)

Used when the long asset value (converted to short asset) exceeds the proportional short debt.

Validation:

Execution:

  1. Redeem longAmountToRedeemForRepay from the PositionAccount's long vToken position

  2. Swap long → short via positionAccount.exitLeverage()

  3. Repay amountToRepay of short debt

  4. Redeem longAmountToRedeemForProfit from the long vToken position

  5. Swap long → DSA and mint as DSA principal on the PositionAccount

  6. Update suppliedPrincipalVTokens += newly minted DSA vTokens

  7. Emit PositionClosed and ProfitConverted

Loss Close (closeWithLoss)

Used when the proportional long asset value is insufficient to cover the full proportional short debt.

Two-leg repayment:

  • Leg 1: Redeem long collateral → swap to short → repay as much debt as possible

  • Leg 2: Redeem DSA collateral → swap to short (or use directly if DSA == short) → repay remaining debt

Both legs are individually optional. Set dsaAmountToRedeemForSecondSwap = 0 to use long collateral only (leg 1 fully covers debt). Set longAmountToRedeemForFirstSwap = 0 and shortAmountToRepayForFirstSwap = 0 together to use DSA only (leg 2 only) — this is the correct path when the long collateral has been partially or fully seized by a liquidator and only DSA principal remains.

Validation:

Execution:

  1. Leg 1 (if longAmountToRedeemForFirstSwap > 0): positionAccount.exitLeverage(longVToken → shortVToken) — redeems long collateral, swaps to short, repays debt

  2. Leg 2 (if dsaAmountToRedeemForSecondSwap > 0): positionAccount.exitLeverage(dsaVToken → shortVToken) — or exitSingleAssetLeverage when DSA == short

  3. Update suppliedPrincipalVTokens -= DSA vTokens burned in leg 2

  4. Emit PositionClosed


Stage 4: Principal Management

supplyPrincipal() — deposit additional DSA without opening a new trade. Transfers DSA from the user, mints vTokens on the PositionAccount, increments suppliedPrincipalVTokens. This is the recommended action when the Health Factor is declining: adding DSA principal directly increases the collateral backing the borrow and raises HF without any position unwind. A small partial close (closeWithProfit or closeWithLoss with a low closeFractionBps) is the alternative: it repays a proportional slice of debt, which similarly raises HF but also reduces position size.

withdrawPrincipal() — withdraw Available Capital back to the user's wallet. Validates that amount <= withdrawableAmount (from getUtilizationInfo()), redeems the corresponding DSA vTokens from the PositionAccount, and decrements suppliedPrincipalVTokens.


Stage 5: Deactivation

deactivatePosition() fully exits the position account for a trading pair.

Prerequisites: The short debt must be fully repaid (zero borrow balance).

Steps:

  1. Set isActive = false, clear suppliedPrincipalVTokens

  2. Exit all markets and redeem all remaining collateral:

    • DSA ≠ long: Redeem all long vTokens, exit DSA market, redeem all DSA vTokens separately

    • DSA == long: Redeem all vTokens from the shared market (accounting for the treasury redemption fee grossup)

  3. Transfer all redeemed underlying to the user's wallet

  4. Emit PositionDeactivated

After deactivation, the PositionAccount contract remains deployed. A new activation (activateAndOpenPosition) will increment cycleId and start a fresh position cycle without redeploying the contract.


Capital Utilization

Capital utilization determines how much of the user's DSA principal is "in use" by the open position and how much can be withdrawn or used to open new positions.

Formula

Leverage Clamping

The stored effectiveLeverage is the leverage set at activation. However, if collateral factors change after activation (e.g., governance lowers a CF), the actual maximum leverage may drop below the stored value. In this case, clampedLeverage = min(storedLeverage, currentMaxLeverage) is used for all utilization and borrow capacity calculations.

This ensures that capital utilization reporting and borrow limits remain conservative and safe even when market parameters change post-activation.

Two Utilization Paths

The system tracks utilization from two perspectives simultaneously:

Nominal: "Given the leverage ratio I agreed to, how much capital does this position conceptually consume?"

  • This path uses the stored leverage and is predictable for users.

Actual: "Given the current value of my long collateral and short debt, how much DSA backing is actually needed to maintain the position safely?"

  • This path accounts for real market prices and collateral factors.

  • The long asset's CF reduces the DSA backing needed (long collateral partially covers the borrow).

  • As prices diverge from the entry, this number can grow.

The maximum of the two is used as the final utilization, ensuring the most conservative estimate is always applied.


DSA Mechanics

DSA ≠ Long (Standard Case)

The PositionAccount holds two separate vToken positions:

  • Long vTokens (from leveraged swaps)

  • DSA vTokens (user's principal)

Principal tracking: suppliedPrincipalVTokens directly maps to the DSA vToken balance. Long collateral balance is simply longVToken.balanceOfUnderlying(positionAccount).

DSA == Long (Shared Market Case)

When the user selects a DSA that is the same token as the long asset (e.g., USDC as both DSA and long), the PositionAccount holds only one vToken balance that represents the sum of both.

The RelativePositionManager separates them using:

When suppliedPrincipalVTokens exceeds totalVTokens (which can happen if the position is partially liquidated), the system syncs suppliedPrincipalVTokens down to totalVTokens and emits RefreshedSuppliedPrincipal. This prevents the system from treating liquidated collateral as still available.

Deactivation in this case redeems from the shared market in a single call, accounting for the treasury fee grossup to avoid over-redemption:


Proportional Close Tolerance

proportionalCloseTolerance (default 100 BPS = 1%) defines the acceptable deviation band for close amounts.

Why it is needed: Interest accrues continuously on the borrow position, and swap outputs are subject to slippage. The close amount derived from closeFractionBps × currentBalance will not match exactly by the time the transaction executes.

Tolerance band for long amounts:

Tolerance bump for 100% closes: For full closes (closeFractionBps = 10000), the repay amount is bumped by the tolerance to ensure that accumulated interest since the close was estimated does not cause a dust leftover:

Any short dust remaining after the repay is validated to be within:

Governance can adjust proportionalCloseTolerance via setProportionalCloseTolerance(). A wider tolerance allows more flexibility but reduces proportionality precision; a narrower tolerance enforces stricter accounting but may cause more reverts due to market movement between estimation and execution.


Flash Loan Integration

Trade uses the existing LeverageStrategiesManager for all flash loan execution. The PositionAccount calls into the LeverageStrategiesManager which uses the Venus Comptroller's flash loan module.

Opening a Position (enterLeverage)

Closing a Position (exitLeverage)

Same-Asset Close (exitSingleAssetLeverage)

When DSA == short (e.g., closing a loss using USDC as both DSA and borrow repayment), no swap is needed:


Liquidation

Trade positions are subject to the standard Venus liquidation mechanism. The PositionAccount is a regular address from the Comptroller's perspective — it enters markets, borrows, and supplies like any other account.

When the Health Factor drops below 1 (i.e., the borrow value exceeds the liquidation threshold-weighted collateral value), third-party liquidators can call liquidateBorrow on the appropriate vToken. This repays part of the debt and seizes a portion of the supplied collateral at a discount.

Post-liquidation behavior:

  • If DSA == long, suppliedPrincipalVTokens may now exceed the actual vToken balance. The next call to _getLongCollateralBalance or _getSuppliedPrincipalBalance will detect this and emit RefreshedSuppliedPrincipal to sync the counter down.

  • The position remains isActive = true after liquidation. The user can still manage (close, supply, withdraw) the reduced position.


Pause Mechanism

The contract supports two pause levels:

Pause Level
Blocked Operations
Allowed Operations

Partial Pause

activateAndOpenPosition, scalePosition, withdrawPrincipal, deactivatePosition

closeWithProfit, closeWithLoss, supplyPrincipal

Complete Pause

All state-changing operations

None

This design allows governance to freeze risky operations (opening new exposure, withdrawing, deactivating) while keeping defensive operations (closing, adding collateral) available during an incident.


Governance and Access Control

All privileged functions are gated via the Venus AccessControlManager.

Function
Permission Required
Notes

setPositionAccountImplementation

Admin

One-time; locked after first call

addDSAVToken

Admin

Adds a new stablecoin to the DSA registry

setDSAVTokenActive

Admin

Enables/disables DSA for new activations

setProportionalCloseTolerance

Admin

Adjusts the close tolerance band

partialPause / partialUnpause

Admin

Toggles partial pause

completePause / completeUnpause

Admin

Toggles complete pause

executePositionAccountCall

Admin

Emergency call forwarder (e.g., asset rescue)

DSA management: When a DSA is set to inactive (setDSAVTokenActive(index, false)), existing positions using that DSA are unaffected. Only new position activations are blocked from selecting that DSA index.

PositionAccount implementation: The EIP-1167 clone implementation is set once and then locked (isPositionAccountImplementationLocked = true). This prevents governance from changing the implementation after positions have been deployed, ensuring users' PositionAccount contracts are immutable.


Security Considerations

Access Control

  • Only the RelativePositionManager can call PositionAccount methods. All external position operations are gated through this single entrypoint.

  • All admin functions are controlled by the Venus AccessControlManager.

Reentrancy

  • ReentrancyGuardUpgradeable is applied to all state-changing external functions on the RelativePositionManager.

Oracle Dependency

  • All USD value calculations use the Venus ResilientOracle. If an oracle price is zero or stale, the relevant function reverts with InvalidOraclePrice.

Flash Loan Safety

  • The flash loan callback (executeOperation) on the LeverageStrategiesManager validates that the initiator is the LeverageStrategiesManager itself and that the caller is the Comptroller, preventing unauthorized replay.

Proportional Close Integrity

  • The tolerance band enforces that close amounts cannot deviate significantly from the intended BPS fraction. This prevents griefing attacks that would allow disproportionate collateral extraction relative to debt repayment.

PositionAccount Immutability

  • The EIP-1167 clone implementation is locked after the first call to setPositionAccountImplementation. This means the RelativePositionManager cannot upgrade the PositionAccount logic for existing positions after deployment.


Audits

RelativePositionManager and PositionAccount were audited by CertiK and Quantstamp before mainnet deployment. Audit reports are available in the venus-periphery repositoryarrow-up-right and in the Security & Audits section of the Venus documentation.

Last updated