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

USUALx

High-Level Overview

The UsualX contract is an upgradeable ERC4626-compliant yield-bearing vault. It extends the YieldBearingVaultUpgradeable contract, incorporating features such as whitelisting, blacklisting, withdrawal fees, and yield distribution linearly over a predefined yield period. The contract leverages OpenZeppelin's upgradeable contracts for enhanced security and flexibility, including pausability and reentrancy protection. It also implements EIP712 for secure off-chain signing capabilities.

The primary objective of Usualx is to provide a secure, controllable environment for yield generation and distribution, while maintaining strict control over who can interact with the contract. This design allows for potential regulatory compliance and risk management in decentralized finance applications.

Contract Summary

The contract provides the following main functions:

  • initialize: Sets up the contract with customizable parameters.

  • initializeV1: Sets up the contract with new customizable parameters.

  • pause / unpause: Controls the operational state of the contract.

  • blacklist / unBlacklist: Manages addresses prohibited from interacting with the contract.

  • whitelist / unWhitelist: Controls addresses permitted to transfer tokens.

  • transfer / transferFrom: Overridden to enforce whitelist restrictions.

  • startYieldDistribution: Initiates a new yield accrual period with specified parameters.

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

  • previewWithdraw / previewRedeem: Simulates withdrawal and redemption operations for users. The contract uses a separate storage structure (UsualXStorageV0) to store state variables for UsualX implementation.

  • sweepFees: Sweeps accumulated fees to the distribution module contract.

Inherited Contracts

  • YieldBearingVaultUpgradeable: Provides core yield accrual and distribution mechanisms.

  • PausableUpgradeable: Enables emergency halt of contract operations.

  • ReentrancyGuardUpgradeable: Prevents reentrancy attacks in critical functions.

  • EIP712Upgradeable: Implements EIP712 for secure off-chain message signing.

Functionality Breakdown

  1. Access Control and Security:

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

    • Implements blacklist to prevent specific addresses from interacting with the contract.

  • Enforces whitelist for token transfers, allowing only approved addresses to transfer tokens at launch but anyone not blacklisted to mint or interact with the vault.

  1. Yield Management:

    • Allows admin-controlled yield distribution periods.

    • Accrues yield over time based on configurable parameters.

    • Integrates yield accrual with deposit and withdrawal operations.

  2. Asset Management:

    • Implements ERC4626 standard for standardized vault interactions.

    • Handles deposits, withdrawals, and redemptions with consideration for accrued yield.

    • Applies withdrawal fees, potentially for protocol revenue or discouraging rapid withdrawals.

  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, token, yield module, EIP712 domain, registry contract and access control, setting up the vault's initial state.

 1  function initialize(
 2      address _registryContract,
 3      uint256 _withdrawFeeBps,
 4      string memory _name,
 5      string memory _symbol,
 6      IERC20 _underlying,
 7      uint256 _maxPeriodLength
 8  ) external initializer {
 9      __YieldBearingVault_init(_maxPeriodLength);
10      __ERC4626_init(_underlying);
11      __ERC20_init(_name, _symbol);
12      __Pausable_init_unchained();
13      __ReentrancyGuard_init();
14      __EIP712_init_unchained(_name, "1");
15  
16      if (_withdrawFeeBps > MAX_WITHDRAW_FEE) {
17          revert AmountTooBig();
18      }
19  
20      if (_registryContract == address(0)) {
21          revert NullContract();
22      }
23  
24      UsualXStorageV0 storage $ = _usualXStorageV0();
25      $.withdrawFeeBps = _withdrawFeeBps;
26      $.registryContract = IRegistryContract(_registryContract);
27      $.registryAccess = IRegistryAccess($.registryContract.getContract(CONTRACT_REGISTRY_ACCESS));
28  }

1-8. Set the registry contract, withdrawal fee in BPS, token name and symbol for the vault, underlying asset, and the max yield period length.

9-14. Initializes inherited contracts, with initializer parameters.

16-18. Validates withdrawal fee is below 25% preventing excessive fees that could harm users.

20-22. Ensures a valid registry contract, reverts if zero address.

24-26. Sets up contract storage with validated parameter.

  1. Points at the access control registry in the registry contract.

Method: initializeV1 (decrepated)

Initializes the burn ration in bips and a variable linked to the USUAL token.

1 function initializeV1() public reinitializer(2) {
2    UsualXStorageV0 storage $ = _usualXStorageV0();
3    if (INITIAL_BURN_RATIO_BPS > BASIS_POINT_BASE) {
4        revert AmountTooBig();
5    }
6    $.burnRatioBps = INITIAL_BURN_RATIO_BPS;
7    $.usualToken = IUsual($.registryContract.getContract(CONTRACT_USUAL));
8    emit BurnRatioUpdated($.burnRatioBps);
9 }

1. Uses the reintializer modifier to set the second version of the the initialize function.

2. Load the contract storage.

3-5. Validates INITIAL_BURN_RATIO_BPS constant isns't higher 100% preventing excessive burning fees.

6-7. Sets up contract storage with validated parameter.

8. Emits an event with the new burn ration.

Method: blacklist

Adds an address to the blacklist, preventing it from interacting with the contract.

 1  function blacklist(address account) external {
 2      if (account == address(0)) {
 3          revert NullAddress();
 4      }
 5      UsualXStorageV0 storage $ = _usualXStorageV0();
 6      $.registryAccess.onlyMatchingRole(DEFAULT_ADMIN_ROLE);
 7      if ($.isBlacklisted[account]) {
 8          revert SameValue();
 9      }
10      $.isBlacklisted[account] = true;
11  
12      emit Blacklist(account);
13  }
  1. Mark function as external to save gas.

2-4. Prevents blacklisting of zero address, and reverts if trying to pass zero address.

5-6. Utilizes the registry for role-based access control, restricting to admin.

7-9. Reverts if the account is already blacklisted.

  1. Adds the account to the blacklist in UsualXStorageV0.

  2. Emits an event to log the blacklisting action.

Method: _update

Internal hook ensuring that both sender and receiver are not blacklisted before updating the token balances.

 1  function _update(address from, address to, uint256 amount)
 2      internal
 3      override(ERC20Upgradeable)
 4  {
 5      UsualXStorageV0 storage $ = _usualXStorageV0();
 6      if ($.isBlacklisted[from] || $.isBlacklisted[to]) {
 7          revert Blacklisted();
 8      }
 9      super._update(from, to, amount);
10  }

1-4. Internal function overriding the base ERC20Upgradeable implementation.

  1. Retrieves storage pointer for UsualXStorageV0. 6-8. Checks both sender and receiver against blacklist, reverting if either is blacklisted.

  2. Passes through to parent implementation if checks pass.

Method: transfer

Overrides the standard ERC20 transfer function to enforce whitelist restrictions on token transfers when the contract is deployed. This can later be removed via smart contract upgrade.

 1  function transfer(address to, uint256 value)
 2      public
 3      override(ERC20Upgradeable, IERC20)
 4      returns (bool)
 5  {
 6      address owner = _msgSender();
 7      UsualXStorageV0 storage $ = _usualXStorageV0();
 8      if ($.isWhitelisted[owner]) {
 9          _transfer(owner, to, value);
10          return true;
11      }
12      revert NotWhitelisted();
13  }

1-5. Public function overriding ERC20 transfer base implementation.

6-7. Uses _msgSender() for potential meta-transaction support, and retrieve storage pointer.

8-11. Allows whitelisted senders to transfer tokens, otherwise reverts.

  1. Reverts if sender is not whitelisted.

Security considerations:

  • Correctly enforces whitelist for senders, but doesn't check recipient's whitelist status.

  • Consider adding a check for the contract's paused state.

  • The function doesn't emit a custom event for whitelisted transfers, which could aid in monitoring.

Method: startYieldDistribution

Initiates a new yield distribution period with specified parameters wrapping the internal call to add proper access control.

 1  function startYieldDistribution(uint256 yieldAmount, uint256 startTime, uint256 endTime)
 2      external
 3  {
 4      _requireOnlyAdmin();
 5      _startYieldDistribution(yieldAmount, startTime, endTime);
 6  }

1-3. External function for starting a new yield period.

  1. Ensures only admin set on registry access can call this function.

  2. Delegates to internal function for yield distribution logic.

Method: withdraw

Overrides the ERC4626 withdraw function to include withdrawal fees and enforce withdrawal limits, calculates shares internally to avoid another storage fetch from calling previewWithdraw.

 1  function withdraw(uint256 assets, address receiver, address owner)
 2      public
 3      override
 4      returns (uint256 shares)
 5  {
 6      UsualXStorageV0 storage $ = _usualXStorageV0();
 7      YieldDataStorage storage yieldStorage = _getYieldDataStorage();
 8  
 9      uint256 maxAssets = maxWithdraw(owner);
10      if (assets > maxAssets) {
11          revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
12      }
13  
14      uint256 fee = Math.mulDiv(assets, $.withdrawFeeBps, BPS_DENOMINATOR, Math.Rounding.Floor);
15      uint256 assetsWithFee = assets + fee;
16  
17      shares = convertToShares(assetsWithFee);
18  
19      yieldStorage.totalDeposits -= fee;
20  
21      super._withdraw(_msgSender(), receiver, owner, assets, shares);
22  }

1-5. Public function overriding the ERC4626 withdraw function.

6-7. Retrieves storage pointers for UsualXStorageV0 and YieldDataStorage.

9-12. Checks if the withdrawal amount exceeds the maximum allowed, and reverts if so.

14-15. Calculates the withdrawal fee based on the number of assets user wants to withdraw taking the precision into account.

  1. Converts assets to shares, considering the fee.

  2. Deducts the fee from the total deposits in the yield storage.

  3. Calls parent withdrawal function with calculated values.

Method: sweepFees

Sweeps accumulated fees to the specified collector address, optionally burning a portion of the fees based on the burn ratio. Enforces non-reentrancy, role-based access control, and validates input.

1 function sweepFees() external nonReentrant returns (uint256) {
2    UsualXStorageV0 storage $ = _usualXStorageV0();
3    $.registryAccess.onlyMatchingRole(FEE_SWEEPER_ROLE);
4
5    address distributionModule = $.registryContract.getContract(CONTRACT_DISTRIBUTION_MODULE);
6
7    uint256 feesToSweep = $.accumulatedFees;
8    if (feesToSweep == 0) {
9        return 0;
10    }
11
12    $.accumulatedFees = 0;
13
14    $.usualToken.safeTransfer(distributionModule, feesToSweep);
15
16    emit FeeSwept(msg.sender, distributionModule, feesToSweep);
17    return feesToSweep;
18 }

1. A public external function marked as nonReentrant to prevent reentrancy attacks.

2-3. Retrieves storage pointer for UsualXStorageV0 and validates that the caller has the FEE_SWEEPER_ROLE permission.

5. Get the Distribution Module contract.

7-10. Get the accumulated fees. Return 0 if no fees.

12. Reset the accumulatedFees variable.

14. Transfer fees to the Distribution Module contract.

16. Emits a FeeSwept event with the caller address, the distribution module address, and amount of fees swept.

PreviousUSUAL*NextUsual USD0++ Investment Vault

Last updated 3 months ago

Was this helpful?

⛓️