Savings Module Integration

Integrate Parallel Savings Module to earn from your stablecoins

The Savings module provides a yield-bearing vault system built on the ERC4626 standard, allowing users to deposit Parallel stablecoins and earn interest through a configurable inflation rate mechanism.

These are simple ERC4626 contracts which do not invest the tokens deposited in it, but simply mint new tokens based on a rate defined by governance. This repo contains the contract implementation.

As such, apart from the savings module contract smart contract risk, there is no extra trust assumption between owning a stablecoin and owning a stablecoin in a staking contract to earn a yield from it.

Savings Module contracts addresses for Parallel stablecoins across all chains can be obtained here.

Overview

Savings module contracts act as interest-bearing vaults where users can deposit their Parallel stablecoins and automatically earn yield over time. The system uses a sophisticated inflation rate mechanism that compounds interest continuously, providing predictable returns for depositors.

Key Features

  • ERC4626 Standard: Full compatibility with the industry-standard vault interface

  • Continuous Compounding: Interest accrues continuously based on a configurable rate

  • Governance-Controlled: Interest rates are managed by trusted addresses and governance

  • Pause Functionality: Emergency pause mechanism for security

  • Upgradeable: UUPS proxy pattern for future improvements

How It Works

  1. Deposit: Users deposit Parallel stablecoins (eg. USDp) and receive vault shares (eg. sUSDp)

  2. Interest Accrual: The vault continuously mints new tokens based on the inflation rate

  3. Share Value Growth: As the total assets increase, each share becomes more valuable

  4. Withdrawal: Users can redeem shares for the underlying assets plus accrued interest

Core Functions

Depositing Assets

Deposit Exact Amount (deposit)

function deposit(uint256 assets, address receiver)
    external returns (uint256 shares);

Parameters:

  • assets: Exact amount of underlying tokens to deposit

  • receiver: Address that will receive the vault shares

Returns:

  • shares: Number of vault shares minted to the receiver

Prerequisites:

  • Approve the Savings module contract to spend your underlying tokens

  • Contract must not be paused

Mint Exact Shares (mint)

function mint(uint256 shares, address receiver)
    external returns (uint256 assets);

Parameters:

  • shares: Exact number of vault shares to mint

  • receiver: Address that will receive the vault shares

Returns:

  • assets: Amount of underlying tokens required for the deposit

Use Case: When you want a specific number of shares rather than a specific asset amount

Withdrawing Assets

Withdraw Exact Amount (withdraw)

function withdraw(
    uint256 assets,
    address receiver,
    address owner
) external returns (uint256 shares);

Parameters:

  • assets: Exact amount of underlying tokens to withdraw

  • receiver: Address that will receive the underlying tokens

  • owner: Address that owns the shares being redeemed

Returns:

  • shares: Number of vault shares burned

Redeem Exact Shares (redeem)

function redeem(
    uint256 shares,
    address receiver,
    address owner
) external returns (uint256 assets);

Parameters:

  • shares: Exact number of vault shares to redeem

  • receiver: Address that will receive the underlying tokens

  • owner: Address that owns the shares being redeemed

Returns:

  • assets: Amount of underlying tokens received

View Functions

Get Total Assets (totalAssets)

function totalAssets() external view returns (uint256);

Returns the total amount of underlying assets held by the vault, including accrued interest.

Estimate Annual Percentage Yield (estimatedAPR)

The contract comes with a wrapper estimatedAPR function which gives the estimated APY in base 18 for depositing in this contract.

A 1% APY for this function would correspond to a value of 10000000000000000.

function estimatedAPR() external view returns (uint256 apr);

Despite the function name, this returns the Annual Percentage Yield (APY) based on the current inflation rate, not the Annual Percentage Rate (APR). The function name is misleading but the return value represents the compounded yield.

Compute Updated Assets (computeUpdatedAssets)

You can anticipate (assuming the inflation rate does not change) how much you'll earn for a period of time by depositing in the contract by calling the computeUpdatedAssets function:

function computeUpdatedAssets(uint256 _totalAssets, uint256 exp)
    external view returns (uint256);

Parameters:

  • _totalAssets: Current total assets amount

  • exp: Time period in seconds

Returns:

  • Updated asset amount after the specified time period

Integration Patterns

Basic Deposit Flow

// 1. Get current yield information
uint256 currentAPY = savings.estimatedAPR(); // Note: function name is misleading, returns APY

// 2. Approve tokens
IERC20(underlyingAsset).approve(address(savings), depositAmount);

// 3. Deposit assets
uint256 shares = savings.deposit(depositAmount, msg.sender);

// 4. Track the deposit
emit Deposit(msg.sender, depositAmount, shares);

Withdrawal Flow

// 1. Check current share value
uint256 totalAssets = savings.totalAssets();
uint256 totalShares = savings.totalSupply();
uint256 shareValue = totalAssets * 1e18 / totalShares;

// 2. Calculate shares needed for desired amount
uint256 sharesNeeded = (desiredAmount * 1e18) / shareValue;

// 3. Redeem shares
uint256 assetsReceived = savings.redeem(sharesNeeded, msg.sender, msg.sender);

// 4. Track the withdrawal
emit Withdrawal(msg.sender, assetsReceived, sharesNeeded);

Monitoring Interest Accrual

// Track user's position over time
struct UserPosition {
    uint256 shares;
    uint256 depositTime;
    uint256 lastCheckpoint;
}

function getUserYield(address user) external view returns (uint256) {
    UserPosition memory position = userPositions[user];
    uint256 currentAssets = savings.totalAssets();
    uint256 currentShares = savings.totalSupply();

    // Calculate current value of user's shares
    uint256 currentValue = (position.shares * currentAssets) / currentShares;

    // Calculate original deposit value (approximate)
    uint256 originalValue = position.shares; // Assuming 1:1 at deposit

    return currentValue - originalValue;
}

Rate modifications

The inflation rate in a Savings module contract is encoded by keepers whitelisted by the DAO. As such, it is non dilutive and does not vary as people deposit more capital or withdraw their assets.

Depending on the stablecoin and on the setup, governance may follow different update schedules. And the frequency of updates may vary from every week to every several months. Between two updates, depositors in Savings module contract are guaranteed to earn a fixed rate on their assets.

Last updated

Was this helpful?