USUALx Lockup Contract

High-Level Overview

The UsualXLockup contract is an upgradeable vault that enables users to lock their UsualX tokens for predefined durations. It implements a flexible locking mechanism with features such as position creation, top-ups within the same UTC calendar day, and position releases after lock expiration. The contract leverages OpenZeppelin's upgradeable contracts for enhanced security and flexibility, including pausability and reentrancy protection. The primary objective of UsualXLockup is to provide a secure mechanism for users to lock their UsualX tokens, potentially for governance or reward-earning purposes, while maintaining strict control over lock durations and position management.

Inherited Contracts

  • PausableUpgradeable: Emergency halt capabilities

  • ReentrancyGuardUpgradeable: Reentrancy attack prevention

Reward Boost Mechanism

The contract enables users to lock their UsualX tokens for various durations to receive boosted rewards. Key points:

  • Boost calculations are performed off-chain

  • Contract provides only necessary on-chain data through events and state tracking

Contract Summary

Main Functions

  • initialize: Contract setup with registry contract address

  • lock: New lock position creation

  • intraDayTopUp: Same-day position top-ups

  • release: Token release after lock expiration

  • releaseAndLock: Combined release and lock operations

  • unlockUserPosition: Forced position unlock by privileged roles

  • unlockUsersPositionsBatch: Forced batch position unlocking by privileged roles

Storage:

  • Uses UsualXLockupStorageV0 structure for state variables

Functionality Breakdown

A. Access Control and Security

  • Registry contract for role-based access

  • Position unlocking controls through dedicated roles

B. Position Management

  • Multiple lock positions per user support

  • Same-day position top-ups

  • Post-expiration release functionality

  • Privileged forced unlock capabilities

C. Lock Duration Management

  • Predefined durations (1, 3, 6, and 12 months)

  • Lock duration validation enforcement

Method Analysis

Public/External Functions

1. Initialize

function initialize(address registryContract) external initializer {
    if (registryContract == address(0)) {
        revert NullAddress();
    }
    UsualXLockupStorageV0 storage $ = _usualXLockupStorageV0();
    $.registryContract = IRegistryContract(registryContract);
    $.registryAccess = IRegistryAccess($.registryContract.getContract(CONTRACT_REGISTRY_ACCESS));
    $.usualX = IERC20($.registryContract.getContract(CONTRACT_USUALX));
    $.lockDurations[ONE_MONTH] = true;
    $.lockDurations[THREE_MONTHS] = true;
    $.lockDurations[SIX_MONTHS] = true;
    $.lockDurations[ONE_YEAR] = true;
}

Purpose: Contract initialization Key Features:

  • Registry contract validation

  • Access control setup

  • Default lock duration enablement Security:

  • Null address checks

  • Single initialization enforcement

  1. Lock

function lock(address receiver, uint256 amount, uint256 lockDuration)
    external
    nonReentrant
    whenNotPaused
{
    if (receiver == address(0)) {
        revert NullAddress();
    }
    if (amount == 0) {
        revert AmountIsZero();
    }
    UsualXLockupStorageV0 storage $ = _usualXLockupStorageV0();
    _lock($, msg.sender, receiver, amount, lockDuration);
}

Purpose: New position creation Key Features:

  • Amount and receiver validation

  • Lock duration verification

  • Position creation and event emission

  • Reentrancy protection

  • Pause mechanism

  • Null address checks

  1. IntraDayTopUp

function intraDayTopUp(address receiver, uint256 amount, uint256 positionIndex)
    external
    nonReentrant
    whenNotPaused
{
    if (receiver == address(0)) {
        revert NullAddress();
    }
    if (amount == 0) {
        revert AmountIsZero();
    }
    UsualXLockupStorageV0 storage $ = _usualXLockupStorageV0();
    _topUp($, msg.sender, receiver, amount, positionIndex);
}

Purpose: Same-day position enhancement Key Features:

  • Same-day validation

  • Position amount update

  • Event emission

  • Reentrancy protection

  • Position existence checks

  • Active status verification

  1. Release

function release(address user, uint256 positionIndex) external nonReentrant whenNotPaused {
    if (user == address(0)) {
        revert NullAddress();
    }
    UsualXLockupStorageV0 storage $ = _usualXLockupStorageV0();
    _release($, user, positionIndex);
}

Purpose: Lock expiration token release Key Features:

  • Lock expiration verification

  • Token transfer

  • Position status update

  • Reentrancy protection

  • Position validation

  • Active status checks

Internal Functions

  1. _lock

function _lock(
    UsualXLockupStorageV0 storage $,
    address sender,
    address receiver,
    uint256 amount,
    uint256 lockDuration
) internal {
    if ($.lockDurations[lockDuration] == false) {
        revert LockDurationIsNotEnabled();
    }

    $.usualX.safeTransferFrom(sender, address(this), amount);

    LockPosition memory lockPosition = LockPosition({
        amount: amount,
        startTime: block.timestamp,
        endTime: block.timestamp + lockDuration,
        isActive: true
    });
    $.userLockPositions[receiver].push(lockPosition);
    $.userLockedAmount[receiver] += amount;

    emit PositionCreated(
        receiver,
        $.userLockPositions[receiver].length - 1,
        lockPosition.amount,
        lockPosition.startTime,
        lockPosition.endTime
    );
}

Purpose: Core lock position creation Implementation:

  • Lock duration validation

  • Token transfer handling

  • Position creation

  • Total amount tracking

  • Event emission

  1. _topUp

function _topUp(
    UsualXLockupStorageV0 storage $,
    address sender,
    address receiver,
    uint256 amount,
    uint256 positionIndex
) internal {
    if (positionIndex >= $.userLockPositions[receiver].length) {
        revert PositionDoesNotExist();
    }
    LockPosition storage lockPosition = $.userLockPositions[receiver][positionIndex];
    if (!lockPosition.isActive) {
        revert PositionIsNotActive();
    }

    // Check if current time is within the same UTC calendar day as start time
    uint256 startDay = lockPosition.startTime / ONE_DAY;
    uint256 currentDay = block.timestamp / ONE_DAY;
    if (currentDay > startDay) {
        revert TopUpDelayHasPassed();
    }

    $.usualX.safeTransferFrom(sender, address(this), amount);
    lockPosition.amount += amount;
    $.userLockedAmount[receiver] += amount;
    emit PositionToppedUp(
        receiver,
        positionIndex,
        amount,
        lockPosition.amount,
        lockPosition.startTime,
        lockPosition.endTime
    );
}

Purpose: Position top-up logic Implementation:

  • Position validation

  • Same-day verification

  • Amount updates

  • Event emission

  1. _release

function _release(UsualXLockupStorageV0 storage $, address user, uint256 positionIndex)
    internal
{
    if (positionIndex >= $.userLockPositions[user].length) {
        revert PositionDoesNotExist();
    }
    LockPosition storage lockPosition = $.userLockPositions[user][positionIndex];
    if (!lockPosition.isActive) {
        revert PositionIsNotActive();
    }
    if (block.timestamp < lockPosition.endTime) {
        revert PositionIsStillLocked();
    }
    uint256 positionAmount = lockPosition.amount;

    lockPosition.isActive = false;
    $.userLockedAmount[user] -= positionAmount;
    $.usualX.safeTransfer(user, positionAmount);
    emit PositionReleased(user, positionIndex, positionAmount);
}

Purpose: Token release implementation Implementation:

  • Position validation

  • Lock expiration check

  • Token transfer

  • State updates

  1. _unlockBatchPositionsForUser

function _unlockBatchPositionsForUser(
    UsualXLockupStorageV0 storage $,
    address user,
    uint256 startIndex,
    uint256 endIndex
) private returns (uint256[] memory) {
    if (user == address(0)) {
        revert NullAddress();
    }
    if (startIndex > endIndex) {
        revert StartIndexGreaterThanEndIndex();
    }
    if (endIndex > $.userLockPositions[user].length) {
        revert PositionDoesNotExist();
    }
    uint256 totalAmount = 0;
    uint256[] memory unlockedIndexes = new uint256[](endIndex - startIndex + 1);
    uint256 unlockedCount = 0;
    for (uint256 j = startIndex; j <= endIndex; j++) {
        LockPosition storage lockPosition = $.userLockPositions[user][j];
        if (!lockPosition.isActive) {
            continue;
        }
        lockPosition.isActive = false;
        totalAmount += lockPosition.amount;
        unlockedIndexes[unlockedCount] = j;
        unlockedCount++;
    }
    // Optimize array length using assembly
    // solhint-disable-next-line no-inline-assembly
    assembly {
        mstore(unlockedIndexes, unlockedCount)
    }

    // Update the user's locked amount and transfer the assets
    $.userLockedAmount[user] -= totalAmount;
    $.usualX.safeTransfer(user, totalAmount);

    return unlockedIndexes;
}Purpose: Batch position unlocking
Implementation:
  • Range validation

  • Position processing

  • Amount accumulation

  • State updates

  • Token transfers

Flow Diagram

Security Considerations

1. Access Control

  • Role-based permissions

  • Registry contract integration

  • Function-level access restrictions

2. Input Validation

  • Address validation

  • Amount verification

  • Index range checks

3. Protection Mechanisms

  • Reentrancy guards

  • Pause functionality

  • Duration controls

Last updated

Was this helpful?