Curvance
  • Protocol Overview
    • Click Less, Earn More
    • Protocol Architecture
    • Asset Types
    • Liquidity Markets
      • Borrowing
      • Liquidations
      • Interest Rates
      • Oracles
      • Collateral Caps
      • Bad Debt Socialization
    • Application Specific Sequencing
    • New Age Liquidity Mining
      • Protocols
    • How Are New Assets Integrated
    • Plugin System
    • Universal Account Balance
    • Token Approval Management
    • Lending Risks
  • Security
    • Security and Audits
  • Miscellaneous
    • RPCs and Testnet Stability
    • Glossary
    • TL;DR
      • Customer Types and Benefits
    • Brand Assets
    • Weblinks
    • Disclaimer
    • Frequently Asked Questions
  • Developer Docs
    • Overview
    • Quick Start Guides
      • Atlas Fastlane Auctions (coming soon)
      • Plugin Integration
        • List of Delegable Actions
      • Loans & Collateral
        • Lend Assets
        • Deposit into pTokens
        • Withdraw Loans
        • Withdraw pTokens
      • Borrowing & Repayment
        • Borrow
        • Repaying Debt
      • Leverage
        • Leveraging
        • Deleveraging
    • Lending Protocol
      • Market Manager
      • Position Tokens (pToken)
      • Earn Tokens (eTokens)
      • Dynamic Interest Rate Model
      • Universal Balance
    • Position Management
      • Leverage
      • Deleverage / Fold
    • Dynamic Liquidation Engine (DLE)
      • Orderflow Auction System
      • Bad Debt Socialization
    • Plugin & Delegation System
      • Transfer Lock Mechanism
      • Delegable Actions
    • Cross-Chain Functionality
      • Messaging Hub
      • Fee Manager
      • Reward Manager
    • Auxiliary Functionality
Powered by GitBook
On this page
  • Overview
  • Core Architecture
  • Key Contracts and Interactions
  • Types of pTokens
  • State Management
  • Data Flow
  • Yield Distribution Mechanism
  • Security Features
  • Oracle Integration
  • User Interaction Functions
  • Deposits
  • Withdrawals
  • Compounding Vault Specifics
  • Considerations
Export as PDF
  1. Developer Docs
  2. Lending Protocol

Position Tokens (pToken)

Overview

Position Tokens (pTokens) are Curvance's collateral tokens that represent user deposits in various yield-generating strategies. These ERC4626-compliant tokens allow users to deposit assets into Curvance vaults and optionally use them as collateral for borrowing in the lending markets. pTokens are designed to be fully liquid, ensuring that assets can be immediately withdrawn or liquidated if needed.

Core Architecture

pTokens follow a hierarchical inheritance structure:

  • BasePToken: The foundational abstract contract implementing ERC4626 vault functionality.

  • SimplePToken: For basic assets that don't generate external rewards (e.g., WETH, stablecoins).

  • CompoundingPToken: Extended functionality for assets that generate yield (supports auto-compounding).

  • CompoundingWithExitFeePToken: Adds an exit fee mechanism to compounding vaults.

  • Protocol-Specific Implementations: Custom implementations for different DeFi protocols.

Key Contracts and Interactions

pTokens interact with several core Curvance contracts:

  • CentralRegistry: Protocol configuration and permissions management.

  • MarketManager: Handles collateral position registration and risk parameters.

  • Various External Protocol Contracts: For staking, providing liquidity, and claiming rewards.

Types of pTokens

Simple pTokens

Simple pTokens (SimplePToken) are designed for assets that don't generate external rewards. They provide a straightforward wrapper for assets like:

  • Wrapped ETH

  • Liquid Staking Tokens (LSTs)

  • Principal Tokens

  • Stablecoins

  • Yield-bearing stablecoins

Compounding pTokens

Compounding pTokens (CompoundingPToken) extend basic functionality by adding auto-compounding yield features. These vaults:

  • Automatically harvest rewards.

  • Convert rewards back into the underlying asset.

  • Reinvest into the yield-generating position.

  • Distribute yield to all vault users through an increasing share value.

Protocol-Specific Implementations

Curvance offers various protocol-specific pToken implementations, but not limited to:

  • AuraPToken: For Aura Finance (Balancer) LP positions.

  • Convex2PoolPToken/Convex3PoolPToken: For Curve/Convex 2-token and 3-token LP positions.

  • VelodromeStablePToken/VelodromeVolatilePToken: For Velodrome stable and volatile LP positions.

  • AerodromeStablePToken/AerodromeVolatilePToken: For Aerodrome stable and volatile LP positions.

  • StakedGMXPToken: For staked GMX positions.

  • PendleLPPToken: For Pendle LP positions.

Each implementation handles the specific deposit, withdrawal, and reward harvesting logic for its respective protocol.

State Management

pTokens maintain several key state variables:

// Modified ERC4626 state
// Total PToken underlying token assets, minus pending vesting.
uint256 internal _totalAssets;
// balanceOf seed for deterministic balance slot calculation
uint256 internal constant _BALANCE_SLOT_SEED = 0x87a211a2;

// Compounding-specific state
struct VaultData {
    uint176 rewardRate;
    uint40 vestingPeriodEnd;
    uint40 lastVestClaim;
}

// Protocol-specific strategy data
// (Example from AuraPToken)
struct StrategyData {
    IBalancerVault balancerVault;
    bytes32 balancerPoolId;
    uint256 pid;
    IBaseRewardPool rewarder;
    IBooster booster;
    address[] rewardTokens;
    address[] underlyingTokens;
}

Data Flow

Deposit Flow

  1. User calls deposit() with underlying assets.

  2. Contract calculates shares based on current exchange rate.

  3. If it's a compounding vault, the _afterDeposit() hook is called to stake tokens in the external protocol.

  4. Underlying tokens are transferred from the user to the contract.

  5. Vault shares (pTokens) are minted to the user.

  6. If user selects to use as collateral, the vault notifies the MarketManager.

Withdrawal Flow

  1. User calls withdraw() with the amount of assets to withdraw.

  2. If it's a compounding vault, the _beforeWithdraw() hook is called to unstake tokens from the external protocol.

  3. Contract calculates shares to burn based on current exchange rate.

  4. Underlying tokens are transferred from the contract to the user.

  5. Vault shares (pTokens) are burned.

  6. If user had posted the tokens as collateral, the vault notifies the MarketManager.

Collateral Management Flow

  1. User calls postCollateral() to use pTokens as borrowing collateral.

  2. Contract verifies the asset is eligible as collateral and under the global cap.

  3. MarketManager is notified of the new collateral position.

  4. User's pTokens are marked as collateral and transfer-restricted.

Reward Harvesting Flow (Compounding Vaults)

  1. harvest() function is called (permissioned or by a keeper).

  2. External rewards are claimed from the underlying protocol.

  3. Rewards are swapped back to the underlying asset.

  4. New tokens are deposited back into the strategy.

  5. Yield is gradually distributed to all vault users through a vesting mechanism.

Yield Distribution Mechanism

Compounding pTokens use a unique vesting approach to distribute yield:

  1. Harvested rewards are not immediately reflected in the vault's total assets.

  2. Instead, rewards vest linearly over a defined period (default is 1 day).

  3. This creates a smoother increase in share price and reduces MEV opportunities.

  4. The vesting mechanism is implemented through the following structure:

struct VaultData {
    uint176 rewardRate;       // Rate at which rewards vest per second
    uint40 vestingPeriodEnd;  // When current vesting period ends
    uint40 lastVestClaim;     // Last time vested rewards were claimed
}

Security Features

pTokens incorporate multiple security measures:

  • Collateral Restrictions: Assets posted as collateral cannot be transferred.

  • Pause Mechanisms: Deposit, withdrawal, and harvesting can be paused independently.

  • Reentrancy Protection: All critical functions have reentrancy guards.

  • Slippage Protection: Swap and liquidity operations have minimum output requirements.

  • Chain Validation: Protocol-specific implementations validate they're on the correct chain.

  • Permissioned Operations: Sensitive functions restricted to DAO or elevated permissions.

Oracle Integration

pTokens integrate with Curvance's oracle system for accurate valuation:

  • Protocol-Specific Adaptors: Custom oracle adaptors for complex assets like Pendle LP tokens.

  • TWAP Support: Time-weighted average prices for more accurate valuations.

  • Price Feeds: Integration with primary price oracles for underlying assets.


User Interaction Functions

Deposits

deposit()

Contract: pToken

Description: Deposits assets into the vault and receives shares.

Function signature:

function deposit(uint256 assets, address receiver) external returns (uint256 shares)
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

address

receiver

The account that should receive the pToken shares.

Return data:

Type
Name
Description

uint256

shares

The amount of pToken shares received by receiver.

Events:

// From pToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

depositAsCollateral()

Contract: pToken

Description: Deposits assets and marks shares as collateral in one transaction.

Function signature:

function depositAsCollateral(
    uint256 assets, 
    address receiver) external returns (uint256 shares)
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

uint256

receiver

The account that should receive the pToken shares.

Return Data:

Type
Name
Description

uint256

shares

The amount of pToken shares received by receiver.

Events:

// From PToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
event CollateralAdjusted(
     address account,
     address pToken,
     uint256 amount,
     bool increase
);
// Only emitted if this is the user's first position in this pToken.
event PositionAdjusted(address mToken, address account, bool open);

depositAsCollateralFor()

Contract: pToken

Description: The depositAsCollateralFor function enables users to deposit assets and automatically post them as collateral on behalf of another address, returning the amount of pToken shares received by the receiver. Unlike depositAsCollateral, this function requires explicit delegation permission, where the receiver must have previously approved the caller to act on their behalf. This makes it particularly useful for smart contract integrations, portfolio management tools, and protocols that assist users in capital optimization. The function operates by first checking delegation permissions, then transferring assets from the caller to the vault, minting shares to the receiver, and finally posting those shares as collateral through the marketManager.

Users should exercise caution when delegating this permission, as delegates could potentially abuse it by repeatedly posting shares as collateral, which could temporarily prevent withdrawals and effectively lock a user's funds. If the caller lacks proper delegation permissions, the function will still deposit assets but won't post them as collateral.

Function signature:

function depositAsCollateralFor(
    uint256 assets,
    address receiver
) external nonReentrant returns (uint256 shares);
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

uint256

receiver

The account that should receive the pToken shares.

Return Data:

Type
Name
Description

uint256

shares

The amount of pToken shares received by receiver.

Events:

// From PToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
event CollateralAdjusted(
     address account,
     address pToken,
     uint256 amount,
     bool increase
);
// Only emitted if this is the user's first position in this pToken.
event PositionAdjusted(address mToken, address account, bool open);

mint()

Contract: pToken

Description: User specifies shares to receive and deposits corresponding assets.

Function signature:

function mint(
    uint256 shares, 
    address receiver) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of shares to mint.

address

receiver

The account that should receive the pToken shares.

Return Data:

Type
Name
Description

uint256

shares

The amount of pToken shares received by receiver.

Events:

// From PToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

Withdrawals

withdraw()

Contract: pToken

Description: Facilitates the withdrawal of assets from the market and the burning of shares. Initially, it verifies if the owner is eligible to redeem shares within the given market. Upon successful validation, it proceeds to burn the shares and subsequently returns the asset. Importantly, it does not force the withdrawals. If the caller is withdrawing another owner's pTokens, they must first have enough approval.

Function signature:

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public override nonReentrant returns (uint256 shares) {
Type
Name
Description

uint256

assets

The amount of underlying assets to withdraw.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to withdraw assets.

Return data:

Type
Name
Description

uint256

shares

The amount of shares that were burned.

Events:

// From PToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Only emitted if the withdrawal requires collateral removal.
event CollateralAdjusted(
    address account,
    address pToken,
    uint256 amount,
    bool increase
);

redeem()

Function: Burns shares to receive assets.

Function signature:

function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to receive assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From PToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Only emitted if the withdrawal requires collateral removal.
event CollateralAdjusted(
    address account,
    address pToken,
    uint256 amount,
    bool increase
);

redeemCollateral()

Description: Redeems collateralized shares to receive assets.

Function signature:

function redeemCollateral(
    uint256 shares, 
    address receiver) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From PToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Always emitted because collateral is being removed.
event CollateralAdjusted(
    address account,
    address pToken,
    uint256 amount,
    bool increase
);

redeemFor()

Description: Withdraws assets, quoted in shares from the market, and burns owner shares, on behalf of owner.

Function signature:

function redeemFor(
        uint256 shares,
        address receiver,
        address owner) public returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to withdraw assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From PToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Only emitted if the withdrawal requires collateral removal
event CollateralAdjusted(
    address account,
    address pToken,
    uint256 amount,
    bool increase
);

redeemCollateralFor()

Description: Redeems collateralized shares on behalf of an owner.

Function signature:

function redeemCollateralFor(
    uint256 shares,
    address receiver, 
    address owner) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that owns the shares being redeemed.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From PToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Always emitted because collateral is being removed.
event CollateralAdjusted(
    address account,
    address pToken,
    uint256 amount,
    bool increase
);

Compounding Vault Specifics

For compounding vaults like AuraPToken, Convex2PoolPToken, Convex3PoolPToken, and StakedGMXPToken:

Vesting Mechanism

Compounding vaults use a vesting mechanism to gradually distribute yield:

function harvest(bytes calldata data) external returns (uint256 yield)
Parameter
Description

data

Encoded swap parameters to convert rewards to underlying assets.

When yield is harvested:

  1. Rewards are claimed from the external protocol.

  2. A protocol fee is taken.

  3. Remaining rewards are swapped to the underlying asset.

  4. The yield is distributed over the vesting period (default 1 day).

Getting Vault Status

function getVaultYieldStatus() external view returns (VaultData memory)

Returns current information about the vault's yield distribution:

  • rewardRate: Rate at which yield is being distributed.

  • vestingPeriodEnd: When the current vesting period ends.

  • lastVestClaim: Last time vesting rewards were claimed.

Considerations

  • Collateral Caps: Restricts the total exogenous risk from any single asset.

  • 20-Minute Minimum Duration: Collateral must be posted for at least 20 minutes.

  • Safe Functions: Enhanced protection against reentrancy and other vulnerabilities.

  • Liquidation Safeguards: Only authorized contracts can seize collateral during liquidations.

PreviousMarket ManagerNextEarn Tokens (eTokens)

Last updated 15 days ago