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);
}
}
Submitted on: 2025-10-16 11:16:37
Comments
Log in to comment.
No comments yet.