FirmPositionCloserPulling

Description:

ERC20 token contract. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

// -----------------------------------------------------------------------------
// Separate, unique interface names to avoid collisions with other files.
// -----------------------------------------------------------------------------
interface IFirmERC20 {
    function balanceOf(address) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IFirmDBR {
    function deficitOf(address user) external view returns (uint256);
    function balanceOf(address user) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IFirmMarket {
    function repay(address user, uint256 amount) external;
    function debts(address user) external view returns (uint256);
    function dbr() external view returns (IFirmDBR);
    function dola() external view returns (IFirmERC20);
}

/// @title FiRM Position Closer (Pull-From-Caller)
/// @notice Helper that *pulls funds from the tx caller* to close a user's FiRM Market position:
///         - Repays the user's full Market debt using caller's DOLA (pulled via transferFrom)
///         - If the user has a DBR deficit after repay, pulls DBR from the caller and sends it to the user
/// @dev    Assumptions:
///         - Caller has approved this contract to spend enough DOLA and DBR.
///         - Market.repay() always pulls DOLA from msg.sender (this contract), so we first pull DOLA
///           from caller -> this, then approve Market and call repay.
///         - No pre-funding of this helper is required.
contract FirmPositionCloserPulling {
    // --------------------
    // Errors & Events
    // --------------------
    error NotAuthorized();
    error InsufficientAllowance(address token, address owner, address spender, uint256 need, uint256 have);
    error CapExceeded(string what);

    event Authorized(address indexed caller, bool allowed);
    event OwnerUpdated(address indexed owner);
    event PositionClosed(address indexed market, address indexed user, address indexed payer, uint256 repaidDebt, uint256 dbrTopUp);
    event Swept(address indexed token, address indexed to, uint256 amount);

    // --------------------
    // Access Control
    // --------------------
    address public owner;
    mapping(address => bool) public isAuthorized; // callers allowed to use close functions

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotAuthorized();
        _;
    }

    modifier onlyAuth() {
        if (!isAuthorized[msg.sender] && msg.sender != owner) revert NotAuthorized();
        _;
    }

    // --------------------
    // Reentrancy Guard (minimal)
    // --------------------
    uint256 private _locked = 1;
    modifier nonReentrant() {
        require(_locked == 1, "REENTRANCY");
        _locked = 2;
        _;
        _locked = 1;
    }

    constructor(address _owner) {
        owner = _owner;
        emit OwnerUpdated(_owner);
    }

    function setOwner(address _owner) external onlyOwner {
        owner = _owner;
        emit OwnerUpdated(_owner);
    }

    function setAuthorized(address caller, bool allowed) external onlyOwner {
        isAuthorized[caller] = allowed;
        emit Authorized(caller, allowed);
    }

    // --------------------
    // Internal utils
    // --------------------
    function _requireAllowance(IFirmERC20 token, address from, address spender, uint256 minNeeded) internal view {
        uint256 a = token.allowance(from, spender);
        if (a < minNeeded) revert InsufficientAllowance(address(token), from, spender, minNeeded, a);
    }

    // --------------------
    // Core logic (pulls from caller)
    // --------------------
    /// @notice Fully repays a user's debt on a Market using caller's DOLA and tops up any DBR deficit using caller's DBR.
    /// @param market The FiRM Market
    /// @param user   The borrower to close
    /// @return repaidDebt Amount of DOLA repaid
    /// @return dbrTopUp   Amount of DBR transferred to user
    function closePosition(IFirmMarket market, address user)
        external
        onlyAuth
        nonReentrant
        returns (uint256 repaidDebt, uint256 dbrTopUp)
    {
        address payer = msg.sender;
        IFirmERC20 dola = market.dola();
        IFirmDBR dbr = market.dbr();

        // 1) Pull exact DOLA debt from caller, approve Market, and repay all.
        uint256 debt = market.debts(user);
        if (debt > 0) {
            _requireAllowance(dola, payer, address(this), debt);
            require(dola.transferFrom(payer, address(this), debt), "pull DOLA failed");

            // Approve exactly what's needed (reset first for safety)
            require(dola.approve(address(market), 0));
            require(dola.approve(address(market), debt));

            // Market will pull from this contract
            market.repay(user, type(uint256).max);
            repaidDebt = debt;
        }

        // 2) Compute fresh DBR deficit and pull DBR from caller directly to the user.
        uint256 deficit = dbr.deficitOf(user);
        if (deficit > 0) {
            _requireAllowance(IFirmERC20(address(dbr)), payer, address(this), deficit);
            require(dbr.transferFrom(payer, user, deficit), "DBR xferFrom failed");
            dbrTopUp = deficit;
        }

        emit PositionClosed(address(market), user, payer, repaidDebt, dbrTopUp);
    }

    /// @notice Same as closePosition but with spend caps to guard the caller's funds.
    function closePositionWithCaps(
        IFirmMarket market,
        address user,
        uint256 maxDolaFromCaller,
        uint256 maxDBRFromCaller
    ) external onlyAuth nonReentrant returns (uint256 repaidDebt, uint256 dbrTopUp) {
        address payer = msg.sender;
        IFirmERC20 dola = market.dola();
        IFirmDBR dbr = market.dbr();

        uint256 debt = market.debts(user);
        if (debt > 0) {
            if (debt > maxDolaFromCaller) revert CapExceeded("DOLA");
            _requireAllowance(dola, payer, address(this), debt);
            require(dola.transferFrom(payer, address(this), debt), "pull DOLA failed");
            require(dola.approve(address(market), 0));
            require(dola.approve(address(market), debt));
            market.repay(user, type(uint256).max);
            repaidDebt = debt;
        }

        uint256 deficit = dbr.deficitOf(user);
        if (deficit > 0) {
            if (deficit > maxDBRFromCaller) revert CapExceeded("DBR");
            _requireAllowance(IFirmERC20(address(dbr)), payer, address(this), deficit);
            require(dbr.transferFrom(payer, user, deficit), "DBR xferFrom failed");
            dbrTopUp = deficit;
        }

        emit PositionClosed(address(market), user, payer, repaidDebt, dbrTopUp);
    }

    // --------------------
    // Admin utilities
    // --------------------
    /// @notice Sweep any ERC20 tokens that were left in this contract (e.g., refunds) to the owner.
    function sweepToken(address token, uint256 amount) external onlyOwner {
        IFirmERC20(token).transfer(owner, amount);
        emit Swept(token, owner, amount);
    }

    /// @notice Convenience views
    function preview(address market, address user) external view returns (uint256 debt, uint256 deficit) {
        debt = IFirmMarket(market).debts(user);
        deficit = IFirmMarket(market).dbr().deficitOf(user);
    }
}

Tags:
ERC20, Token|addr:0xef79e5aad4b85b31f6d6f896dc9abe05e7f28f72|verified:true|block:23588386|tx:0xd9cf8a15408e1e3d7a73a69de5e5fc71fb21212ac2ea2bb4b31e1c3dc23b743f|first_check:1760606195

Submitted on: 2025-10-16 11:16:37

Comments

Log in to comment.

No comments yet.