Usual Tech Docs
Usual WebsiteGeneral DocsContract DeploymentsAuditsAccess dApp
  • 🚀GM GM
    • Usual Tech Hub
  • 🔭Overview
    • Usual Protocol Primer
    • Features
      • Mint & Redeem Engine
      • USD0
      • USD0++
      • USUAL
      • USUAL*
      • USUALx (USUAL staking)
      • USUAL distribution
      • USUAL Airdrop
      • Usual USD0++ Investment Vault
    • Architecture
      • Role Management
      • USUAL Distribution Model
  • ⛓️Smart Contracts
    • Protocol Contracts
      • DaoCollateral
      • Swapper Engine
      • USUAL staking
      • USUAL* Vested Allocation Staking
      • USUAL Distribution
        • Distribution Module
        • Yield Module
      • Airdrop Module
    • Token Contracts
      • USD0
      • USD0++
      • USUAL
      • USUAL*
      • USUALx
      • Usual USD0++ Investment Vault
        • VaultRouter
    • Utility Contracts
      • ClassicalOracle
      • Abstract Oracle
      • Chainlink Oracles
      • Pyth Oracles
      • RedStone Oracles
    • Real World Assets
      • USYC (by Hashnote)
      • M (by M0)
        • UsualM
      • USDtb
    • Contract Deployments
  • 🛡️Security & Audits
    • Security Practices
    • Testing Framework
    • Monitoring Framework
    • Audits
    • Bug Bounty
  • 🧩Integrations
    • Integrate USD0++
      • Reward redistribution by integration partner
      • Claim Address Redirect
      • Daily Distribution powered by Brevis (coming soon)
  • 📚Resources
    • Community & Support
    • References
  • 📖Support
    • FAQ
    • Glossary
  • ⚖️Legal
    • Terms of Services
    • Legal Notice
    • Privacy Policy
Powered by GitBook
On this page
  • High-Level Overview
  • Contract Summary
  • Inherited Contracts
  • Functionality Breakdown
  • Security Analysis

Was this helpful?

  1. Smart Contracts
  2. Token Contracts

Usual USD0++ Investment Vault

High-Level Overview

The WrappedDollarVault contract is an upgradeable ERC4626-compliant yield-bearing vault. The contract leverages OpenZeppelin's upgradeable contracts for enhanced security and flexibility, including pausability and reentrancy protection.

The vault holds another USD-denominated ERC4626 and allows users to deposit and withdraw this token, with fees accrued in USD, and a monotonic increase in USD value per share only, rather than a monotonic increase in assets or share. The price in units of asset decreases over time due to fees accruing.

Only the registered router via the setRouter() function can call deposit, mint, withdraw, redeem . The goal of the router is to allow for swaps and permits for approvals, ensuring that arbitrary tokens are accepted as input and USD0++ is accepted as output.

The primary objective of the investment vault is to have a generic contract to allow wrapping of yield bearing token from external strategies without leaving the Usual ecosystem.

Contract Summary

The contract provides the following main functions:

  • pause: Pause the vault's functionality.

  • unpause: Unpause the vault's functionality.

  • setRouter: Add or remove a router.

  • setFeeRateBps: Set the fee rate in basis points.

  • harvest: Harvest management fees by minting shares to the treasury.

  • feeRateBps: Returns the fee rate in basis points.

  • getRouterState: Returns the state of a router.

  • decimals: Returns the number of decimals of the vault.

  • withdraw / redeem: Handles asset withdrawals and share redemptions, incorporating withdrawal fees.

  • mint / deposit : Handles depositing asset and mint shares of the vault

  • previewWithdraw / previewRedeem: Simulates withdrawal and redemption operations for users.

Inherited Contracts

  • PausableUpgradeable: Enables emergency halt of contract operations.

  • ReentrancyGuardUpgradeable: Prevents reentrancy attacks in critical functions.

  • ERC20PermitUpgradeable: Extends ERC20 to support gasless transactions through signed

  • ERC4626Upgradable: Provide a standard implementation for ERC4626 vault.

Functionality Breakdown

  1. Access Control and Security:

    • Utilizes a registry contract for role-based access control.

    • Implements specific role to set router VAULT_SET_ROUTER_ROLE, set fee VAULT_SET_FEE_ROLE and harvest VAULT_HARVESTER_ROLE to prevent specific addresses from interacting with the contract.

    • Enforces router registration for all the creation and destruction of vault shares, allowing only previously set router addresses to interact with the vault.

  1. Fee Management:

    • Allows admin-controlled daily harvest of fee to the treasury.

    • Accrues fees over time based on configurable parameters. Fees are due when withdrawing and redeeming

    • The number of underlying assets behind a share decreases over time due to fee accruing.

  2. Asset Management:

    • Implements ERC4626 standard for standardized vault interactions.

    • Handles withdrawals, and redemption with consideration for accrued fees.

    • Asset is yield bearing so its USD value will increase overtime.

  3. Upgradability and Pause Mechanism:

    • Utilizes OpenZeppelin's upgradeable contract pattern for future improvements.

    • Includes pause functionality for emergency situations.

Security Analysis

Method: initialize

Initializes the vault, setting up the registry contract, underlying asset, name, symbol, and initial vault state.

    function initialize(
        address registryContract_,
        address underlyingAsset_,
        string memory name_,
        string memory symbol_
    )
        public
        initializer
    {
        if (registryContract_ == address(0) || underlyingAsset_ == address(0)) {
            revert NullAddress();
        }
        __ReentrancyGuard_init();
        __ERC20_init(name_, symbol_);
        __ERC20Permit_init(name_);
        __ERC20Pausable_init();
        __ERC4626_init(IERC20(underlyingAsset_));
        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();
        $.registryContract = IRegistryContract(registryContract_);
        $.registryAccess = IRegistryAccess(
            $.registryContract.getContract(CONTRACT_REGISTRY_ACCESS)
        );
        $.treasury = $.registryContract.getContract(CONTRACT_YIELD_TREASURY);
        $.feeRateBps = DEFAULT_FEE_RATE_BPS;
        $.lastFeeRateUpdateTimestamp = block.timestamp;
        _mint(DEAD_ADDRESS, INITIAL_SHARES_SUPPLY);
    }

11-12. Ensures valid registry contract and underlying asset addresses, reverting if either is zero to prevent null references.

  1. Initializes inherited contracts for reentrancy protection

14-15. ERC20 functionality sets the vault name, symbol and permit support.

16-17. Pausability, and ERC4626 vault standards with the underlying asset.

19-22. Sets up contract storage with the registry contract, access control from the registry.

23-26. treasury address, default fee rate, last fee update timestamp, and mints initial shares to a dead address.


Method: setRouter

Add or disable a router, updating its active state.

    function setRouter(address router, bool isActive) public whenNotPaused {
        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();
        if (!$.registryAccess.hasRole(VAULT_SET_ROUTER_ROLE, _msgSender())) {
            revert NotAuthorized();
        }
        if (router == address(0)) revert NullAddress();
        if ($.routers[router] == isActive) {
            revert SameRouterActivity(router, isActive);
        }

        $.routers[router] = isActive;
        emit RouterUpdated(router, isActive);
    }

1. Sets the router address is only callable when the vault is not paused. 3-4. Checks that the caller has the VAULT_SET_ROUTER_ROLE via the registry’s access control, reverting if unauthorized. 5. Ensures the router address is not zero, reverting if null to prevent invalid settings. 7-8. Verifies the router’s current state differs from the requested state, reverting if unchanged to avoid redundant updates. 9-10. Updates the router’s active state in storage and emits a RouterUpdated event with the router address and new status.


Method: setFeeRateBps

Set the fee rate in basis points, updating the vault’s fee structure.

    function setFeeRateBps(uint32 newFeeRateBps) external whenNotPaused {
        if (newFeeRateBps > MAX_FEE_RATE_BPS) revert InvalidFeeRate();

        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();
        if (!$.registryAccess.hasRole(VAULT_SET_FEE_ROLE, _msgSender())) {
            revert NotAuthorized();
        }
        if (newFeeRateBps == $.feeRateBps) revert SameFeeRate();
        if (block.timestamp < $.lastFeeRateUpdateTimestamp + ONE_WEEK) {
            revert FeeRateUpdateTooFrequent();
        }
        uint32 oldFeeRateBps = $.feeRateBps;
        $.feeRateBps = newFeeRateBps;
        $.lastFeeRateUpdateTimestamp = block.timestamp;
        emit FeeRateUpdated(oldFeeRateBps, newFeeRateBps);
    }

1-2. Sets a new fee rate in micro basis points is only callable when the vault is not paused, with a check to ensure it doesn’t exceed the maximum allowed fee rate. 5-6. Verifies the caller has the VAULT_SET_FEE_ROLE via the registry’s access control, reverting if unauthorized. 8. Ensures the new fee rate differs from the current rate, reverting if unchanged to avoid redundant updates. 9-10. Confirms at least one week has passed since the last fee update, reverting if too frequent to maintain stability. 12-15. Stores the old fee rate, updates the fee rate and timestamp in storage, and emits a FeeRateUpdated event with the old and new rates.


Method: harvest

Harvest management fees by minting shares to the treasury based on the current fee rate

    function harvest()
        external
        whenNotPaused
        nonReentrant
        returns (uint256 sharesMinted)
    {
        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();

        if (!$.registryAccess.hasRole(VAULT_HARVESTER_ROLE, _msgSender())) {
            revert NotAuthorized();
        }

        if (block.timestamp < $.lastHarvestTimestamp + ONE_DAY) {
            revert HarvestTooFrequent();
        }

        uint256 currentSupply = totalSupply();
        sharesMinted = _feeOnTotal(currentSupply, $.feeRateBps);

        if (sharesMinted == 0) revert ZeroAmount();

        $.lastHarvestTimestamp = block.timestamp;

        _mint($.treasury, sharesMinted);

        emit Harvested(_msgSender(), sharesMinted);

        return sharesMinted;
    }

1-5. Initiates the harvest process to mint shares for management fees, callable only when not paused and non-reentrant, returning the number of shares minted. 9-10. Verifies the caller has the VAULT_HARVESTER_ROLE via the registry’s access control, reverting if unauthorized. 13-14. Ensures at least one day has passed since the last harvest, reverting if too frequent to prevent abuse. 17-20. Calculates the shares to mint based on the current total supply and fee rate, reverting if the result is zero to avoid empty operations. 22-28. Updates the last harvest timestamp, mints the calculated shares to the treasury, emits a Harvested event with the caller and shares minted, and returns the shares minted.


Method: previewWithdraw

    function previewWithdraw(uint256 assets)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();
        // Calculate fee amount
        uint256 fee = _feeOnTotal(assets, $.feeRateBps);

        // Calculate shares needed for assets + fee
        return super.previewWithdraw(assets + fee);
    }

1-6. Previews the withdrawal process by calculating the shares needed to withdraw a given amount of assets, including the applicable fee, overriding the ERC4626 implementation. 8-10. Retrieves the vault’s storage and calculates the fee based on the requested assets and the current fee rate in basis points. 13. Adds the fee to the requested assets and uses the parent ERC4626 previewWithdraw function to determine the total shares required, returning the result.


Method: previewRedeem

Previews the amount of assets that would be returned for redeeming a specified number of shares, after deducting fees

    function previewRedeem(uint256 shares)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        // Convert shares to assets
        uint256 assets = super.previewRedeem(shares);

        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();
        // Calculate and deduct fee
        uint256 fee = _feeOnRaw(assets, $.feeRateBps);

        return assets - fee;
    }

1-6. Previews the redemption process by calculating the assets returned for a given number of shares, after subtracting the applicable fee, overriding the ERC4626 implementation. 9. Uses the parent ERC4626 previewRedeem function to convert the shares into assets. 13. Calculates the fee based on the raw asset amount and the current fee rate in basis points. 12. Returns the assets minus the calculated fee as the final redeemable amount


Method: _withdraw

Internal function to handle the withdrawal of assets, including minting fee shares to the treasury.

    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    )
        internal
        virtual
        override
    {
        WrappedDollarVaultStorageV0 storage $ = _wrappedDollarVaultStorageV0();

        // Calculate fee shares
        uint256 feeShares = _feeOnRaw(shares, $.feeRateBps);

        // Call parent implementation to handle the withdrawal
        super._withdraw(caller, receiver, owner, assets, shares);

        // Mint fee shares to treasury
        if (feeShares > 0) {
            _mint($.treasury, feeShares);
        }
    }

1-9. Executes the withdrawal process internally, taking the caller, receiver, owner, assets, and shares as inputs, overriding the parent implementation to include fee handling. 15. Calculates the fee in shares based on the raw shares being withdrawn and the current fee rate in basis points. 18. Calls the parent _withdraw function to perform the core withdrawal logic for the specified assets and shares. 21-22. Mints the calculated fee shares to the treasury if the fee is greater than zero, ensuring fees are collected.

PreviousUSUALxNextVaultRouter

Last updated 1 month ago

Was this helpful?

⛓️