DEV Community

Swatantra goswami
Swatantra goswami

Posted on

USDT Collateral Vault


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";

contract USDTCrossMarginVault
    is AccessControl,
       ReentrancyGuard,
       Pausable
{
    using SafeERC20 for IERC20;

    // =============================================================
    // ROLES
    // =============================================================

    bytes32 public constant RISK_ENGINE_ROLE =
        keccak256("RISK_ENGINE_ROLE");

    bytes32 public constant SETTLEMENT_ROLE =
        keccak256("SETTLEMENT_ROLE");

    bytes32 public constant LIQUIDATION_ROLE =
        keccak256("LIQUIDATION_ROLE");

    bytes32 public constant TREASURY_ROLE =
        keccak256("TREASURY_ROLE");

    // =============================================================
    // CONSTANTS
    // =============================================================

    uint256 public constant PRECISION = 1e18;

    // =============================================================
    // STATE
    // =============================================================

    IERC20 public immutable USDT;

    uint256 public totalDeposited;
    uint256 public totalLockedMargin;

    uint256 public insuranceFund;

    // =============================================================
    // ACCOUNT
    // =============================================================

    struct Account {
        uint256 deposited;

        // Margin reserved for open positions.
        uint256 lockedMargin;

        // Net realized PnL.
        int256 realizedPnL;

        // Trading fees paid.
        uint256 feesPaid;

        // Funding paid/received.
        int256 fundingPnL;

        // Number of active positions.
        uint256 activePositions;
    }

    mapping(address => Account) public accounts;

    // =============================================================
    // POSITION
    // =============================================================

    enum Side {
        LONG,
        SHORT
    }

    struct Position {
        bytes32 positionId;

        address trader;

        Side side;

        uint256 size;

        uint256 entryPrice;

        uint256 initialMargin;

        uint256 maintenanceMargin;

        bool active;
    }

    mapping(bytes32 => Position) public positions;

    mapping(address => bytes32[]) public traderPositions;

    // =============================================================
    // EVENTS
    // =============================================================

    event Deposited(
        address indexed trader,
        uint256 amount
    );

    event Withdrawn(
        address indexed trader,
        uint256 amount
    );

    event MarginLocked(
        address indexed trader,
        uint256 amount
    );

    event MarginUnlocked(
        address indexed trader,
        uint256 amount
    );

    event PositionOpened(
        bytes32 indexed positionId,
        address indexed trader,
        Side side,
        uint256 size,
        uint256 entryPrice,
        uint256 margin
    );

    event PositionClosed(
        bytes32 indexed positionId,
        address indexed trader,
        int256 pnl
    );

    event PnLSettled(
        address indexed trader,
        int256 pnl
    );

    event FeeCharged(
        address indexed trader,
        uint256 fee
    );

    event FundingSettled(
        address indexed trader,
        int256 funding
    );

    event Liquidated(
        bytes32 indexed positionId,
        address indexed trader,
        uint256 remainingCollateral
    );

    event InsuranceFundDeposited(
        uint256 amount
    );

    event InsuranceFundUsed(
        uint256 amount
    );

    // =============================================================
    // CONSTRUCTOR
    // =============================================================

    constructor(
        address usdt,
        address admin
    ) {
        require(
            usdt != address(0),
            "Invalid USDT"
        );

        require(
            admin != address(0),
            "Invalid admin"
        );

        USDT = IERC20(usdt);

        _grantRole(
            DEFAULT_ADMIN_ROLE,
            admin
        );
    }

    // =============================================================
    // USER DEPOSIT
    // =============================================================

    function deposit(
        uint256 amount
    )
        external
        nonReentrant
        whenNotPaused
    {
        require(
            amount > 0,
            "Amount is zero"
        );

        USDT.safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );

        accounts[msg.sender].deposited += amount;

        totalDeposited += amount;

        emit Deposited(
            msg.sender,
            amount
        );
    }

    // =============================================================
    // USER WITHDRAW
    // =============================================================

    function withdraw(
        uint256 amount
    )
        external
        nonReentrant
        whenNotPaused
    {
        Account storage account =
            accounts[msg.sender];

        uint256 available =
            availableMargin(msg.sender);

        require(
            amount <= available,
            "Insufficient free margin"
        );

        account.deposited -= amount;

        totalDeposited -= amount;

        USDT.safeTransfer(
            msg.sender,
            amount
        );

        emit Withdrawn(
            msg.sender,
            amount
        );
    }

    // =============================================================
    // LOCK MARGIN
    // =============================================================

    function lockMargin(
        address trader,
        uint256 amount
    )
        external
        onlyRole(RISK_ENGINE_ROLE)
        whenNotPaused
    {
        _lockMargin(trader, amount);
    }

    function _lockMargin(
        address trader,
        uint256 amount
    )
        internal
    {
        Account storage account =
            accounts[trader];

        require(
            amount <= availableMargin(trader),
            "Insufficient margin"
        );

        account.lockedMargin += amount;

        totalLockedMargin += amount;

        emit MarginLocked(
            trader,
            amount
        );
    }

    // =============================================================
    // UNLOCK MARGIN
    // =============================================================

    function unlockMargin(
        address trader,
        uint256 amount
    )
        external
        onlyRole(RISK_ENGINE_ROLE)
    {
        Account storage account =
            accounts[trader];

        require(
            amount <= account.lockedMargin,
            "Exceeds locked margin"
        );

        account.lockedMargin -= amount;

        totalLockedMargin -= amount;

        emit MarginUnlocked(
            trader,
            amount
        );
    }

    // =============================================================
    // OPEN POSITION
    // =============================================================

    function registerPosition(
        bytes32 positionId,
        address trader,
        Side side,
        uint256 size,
        uint256 entryPrice,
        uint256 initialMargin,
        uint256 maintenanceMargin
    )
        external
        onlyRole(RISK_ENGINE_ROLE)
        whenNotPaused
    {
        require(
            positionId != bytes32(0),
            "Invalid position"
        );

        require(
            !positions[positionId].active,
            "Position exists"
        );

        require(
            size > 0,
            "Invalid size"
        );

        require(
            entryPrice > 0,
            "Invalid price"
        );

        require(
            initialMargin > 0,
            "Invalid margin"
        );

        _lockMargin(
            trader,
            initialMargin
        );

        positions[positionId] = Position({
            positionId: positionId,
            trader: trader,
            side: side,
            size: size,
            entryPrice: entryPrice,
            initialMargin: initialMargin,
            maintenanceMargin: maintenanceMargin,
            active: true
        });

        traderPositions[trader].push(
            positionId
        );

        accounts[trader].activePositions++;

        emit PositionOpened(
            positionId,
            trader,
            side,
            size,
            entryPrice,
            initialMargin
        );
    }

    // =============================================================
    // SETTLE REALIZED PNL
    // =============================================================

    function settlePnL(
        address trader,
        int256 pnl
    )
        external
        onlyRole(SETTLEMENT_ROLE)
        whenNotPaused
    {
        Account storage account =
            accounts[trader];

        if (pnl > 0) {

            uint256 profit =
                uint256(pnl);

            require(
                USDT.balanceOf(address(this))
                    >= totalDeposited + profit,
                "Insufficient vault liquidity"
            );

            account.deposited += profit;

            totalDeposited += profit;

        } else if (pnl < 0) {

            uint256 loss =
                uint256(-pnl);

            require(
                loss <= account.deposited,
                "Loss exceeds collateral"
            );

            account.deposited -= loss;

            totalDeposited -= loss;
        }

        account.realizedPnL += pnl;

        emit PnLSettled(
            trader,
            pnl
        );
    }

    // =============================================================
    // FEE
    // =============================================================

    function chargeFee(
        address trader,
        uint256 fee
    )
        external
        onlyRole(SETTLEMENT_ROLE)
    {
        Account storage account =
            accounts[trader];

        require(
            fee <= availableEquity(trader),
            "Insufficient equity"
        );

        account.deposited -= fee;

        account.feesPaid += fee;

        totalDeposited -= fee;

        emit FeeCharged(
            trader,
            fee
        );
    }

    // =============================================================
    // FUNDING
    // =============================================================

    function settleFunding(
        address trader,
        int256 funding
    )
        external
        onlyRole(SETTLEMENT_ROLE)
    {
        Account storage account =
            accounts[trader];

        if (funding > 0) {

            uint256 received =
                uint256(funding);

            account.deposited += received;

            totalDeposited += received;

        } else if (funding < 0) {

            uint256 payment =
                uint256(-funding);

            require(
                payment <= account.deposited,
                "Insufficient collateral"
            );

            account.deposited -= payment;

            totalDeposited -= payment;
        }

        account.fundingPnL += funding;

        emit FundingSettled(
            trader,
            funding
        );
    }

    // =============================================================
    // CLOSE POSITION
    // =============================================================

    function closePosition(
        bytes32 positionId,
        int256 pnl
    )
        external
        onlyRole(SETTLEMENT_ROLE)
        whenNotPaused
    {
        Position storage position =
            positions[positionId];

        require(
            position.active,
            "Position inactive"
        );

        address trader =
            position.trader;

        uint256 margin =
            position.initialMargin;

        // First settle PnL.
        _settlePnLInternal(
            trader,
            pnl
        );

        // Release margin.
        Account storage account =
            accounts[trader];

        require(
            margin <= account.lockedMargin,
            "Margin error"
        );

        account.lockedMargin -= margin;

        totalLockedMargin -= margin;

        account.activePositions--;

        position.active = false;

        emit PositionClosed(
            positionId,
            trader,
            pnl
        );
    }

    // =============================================================
    // LIQUIDATION
    // =============================================================

    function liquidate(
        bytes32 positionId,
        int256 pnl
    )
        external
        onlyRole(LIQUIDATION_ROLE)
        whenNotPaused
    {
        Position storage position =
            positions[positionId];

        require(
            position.active,
            "Position inactive"
        );

        address trader =
            position.trader;

        Account storage account =
            accounts[trader];

        // Settle loss/profit.
        if (pnl < 0) {

            uint256 loss =
                uint256(-pnl);

            if (loss >= account.deposited) {

                uint256 remaining =
                    account.deposited;

                totalDeposited -= remaining;

                account.deposited = 0;

                // Bad debt can be handled
                // by the insurance fund.
                uint256 badDebt =
                    loss - remaining;

                if (badDebt > 0) {
                    _useInsuranceFund(
                        badDebt
                    );
                }

            } else {

                account.deposited -= loss;

                totalDeposited -= loss;
            }

        } else {

            uint256 profit =
                uint256(pnl);

            account.deposited += profit;

            totalDeposited += profit;
        }

        uint256 margin =
            position.initialMargin;

        if (margin <= account.lockedMargin) {

            account.lockedMargin -= margin;

            totalLockedMargin -= margin;
        }

        account.activePositions--;

        position.active = false;

        emit Liquidated(
            positionId,
            trader,
            account.deposited
        );
    }

    // =============================================================
    // INTERNAL PNL
    // =============================================================

    function _settlePnLInternal(
        address trader,
        int256 pnl
    )
        internal
    {
        Account storage account =
            accounts[trader];

        if (pnl > 0) {

            uint256 profit =
                uint256(pnl);

            account.deposited += profit;

            totalDeposited += profit;

        } else if (pnl < 0) {

            uint256 loss =
                uint256(-pnl);

            require(
                loss <= account.deposited,
                "Insufficient collateral"
            );

            account.deposited -= loss;

            totalDeposited -= loss;
        }

        account.realizedPnL += pnl;

        emit PnLSettled(
            trader,
            pnl
        );
    }

    // =============================================================
    // AVAILABLE MARGIN
    // =============================================================

    function availableMargin(
        address trader
    )
        public
        view
        returns (uint256)
    {
        Account memory account =
            accounts[trader];

        if (
            account.deposited
            <= account.lockedMargin
        ) {
            return 0;
        }

        return
            account.deposited
            - account.lockedMargin;
    }

    // =============================================================
    // AVAILABLE EQUITY
    // =============================================================

    function availableEquity(
        address trader
    )
        public
        view
        returns (uint256)
    {
        return accounts[trader].deposited;
    }

    // =============================================================
    // INSURANCE FUND
    // =============================================================

    function depositInsuranceFund(
        uint256 amount
    )
        external
        onlyRole(TREASURY_ROLE)
        nonReentrant
    {
        USDT.safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );

        insuranceFund += amount;

        emit InsuranceFundDeposited(
            amount
        );
    }

    function _useInsuranceFund(
        uint256 amount
    )
        internal
    {
        require(
            amount <= insuranceFund,
            "Insurance fund insufficient"
        );

        insuranceFund -= amount;

        emit InsuranceFundUsed(
            amount
        );
    }

    // =============================================================
    // ACCOUNT VIEW
    // =============================================================

    function getAccount(
        address trader
    )
        external
        view
        returns (
            uint256 deposited,
            uint256 lockedMargin,
            uint256 available,
            int256 realizedPnL,
            uint256 fees,
            int256 funding,
            uint256 activePositions
        )
    {
        Account memory account =
            accounts[trader];

        deposited =
            account.deposited;

        lockedMargin =
            account.lockedMargin;

        available =
            availableMargin(trader);

        realizedPnL =
            account.realizedPnL;

        fees =
            account.feesPaid;

        funding =
            account.fundingPnL;

        activePositions =
            account.activePositions;
    }

    // =============================================================
    // PAUSE
    // =============================================================

    function pause()
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        _pause();
    }

    function unpause()
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        _unpause();
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)