PullRouter

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.20;

/* ========== ERC20 基础接口(含 approve/balanceOf 等) ========== */
interface IERC20 {
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function decimals() external view returns (uint8);
}

/* ========== EIP-2612(可选) ========== */
interface IERC20Permit {
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v, bytes32 r, bytes32 s
    ) external;
}

/* ========== SafeERC20:兼容 USDT 等非标准返回(无自定义错误/提示) ========== */
library SafeERC20 {
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        (bool ok, bytes memory data) =
            address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
        require(ok && (data.length == 0 || abi.decode(data, (bool))));
    }
}

/* ========== PullRouter(多操作员 + 单笔限额,精简版) ========== */
contract PullRouter {
    using SafeERC20 for IERC20;

    /* -------- Storage -------- */
    address public owner; // 部署者即 owner(无构造参数)

    mapping(address => bool) public isOperator;                          // 操作员白名单
    mapping(address => mapping(address => uint256)) public perCallLimit; // perCallLimit[operator][token]

    /* -------- Events -------- */
    event Pulled(address indexed token, address indexed from, address indexed to, uint256 amount, address invoker);
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);
    event OperatorUpdated(address indexed operator, bool enabled);
    event PerCallLimitSet(address indexed operator, address indexed token, uint256 maxAmount);

    /* -------- Constructor -------- */
    constructor() {
        owner = msg.sender;
    }

    /* -------- Modifiers(无提示版) -------- */
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    modifier onlyOwnerOrOperator() {
        require(msg.sender == owner || isOperator[msg.sender]);
        _;
    }

    /* -------- Admin -------- */
    function setOwner(address newOwner) external onlyOwner {
        require(newOwner != address(0));
        emit OwnerChanged(owner, newOwner);
        owner = newOwner;
    }

    function setOperator(address operator, bool enabled) external onlyOwner {
        require(operator != address(0));
        isOperator[operator] = enabled;
        emit OperatorUpdated(operator, enabled);
    }

    /// @notice 设置某 operator 对某 token 的单次最大划转额度(最小单位);设为 0 表示禁止
    function setPerCallLimit(address operator, address token, uint256 maxAmount) external onlyOwner {
        require(operator != address(0) && token != address(0));
        perCallLimit[operator][token] = maxAmount;
        emit PerCallLimitSet(operator, token, maxAmount);
    }

    /* -------- Core -------- */

    /// @notice 从 from 扣代币到 to(须先由 from 对本合约 approve)
    function pull(address token, address from, address to, uint256 amount) external onlyOwnerOrOperator {
        if (_basicChecks(token, from, to, amount)) return; // amount==0 直接返回
        _checkLimitIfOperator(token, amount);
        IERC20(token).safeTransferFrom(from, to, amount);
        emit Pulled(token, from, to, amount, msg.sender);
    }

    /// @notice 一笔完成授权+扣款(目标代币需支持 EIP-2612)
    function permitAndPull(
        address token,
        address from,
        address to,
        uint256 amount,
        uint256 deadline,
        uint8 v, bytes32 r, bytes32 s
    ) external onlyOwnerOrOperator {
        if (_basicChecks(token, from, to, amount)) return; // amount==0 直接返回
        _checkLimitIfOperator(token, amount);

        // 代币内部会校验 deadline/nonce 等,失败直接回滚
        IERC20Permit(token).permit(from, address(this), amount, deadline, v, r, s);

        IERC20(token).safeTransferFrom(from, to, amount);
        emit Pulled(token, from, to, amount, msg.sender);
    }

    /* -------- Internal helpers -------- */
    /// @dev 基础入参校验;如果 amount==0,返回 true 表示"已完成(无需后续逻辑)"
    function _basicChecks(address token, address from, address to, uint256 amount) private pure returns (bool) {
        require(token != address(0) && from != address(0) && to != address(0));
        if (amount == 0) return true;
        return false;
    }

    /// @dev 仅在 msg.sender 是 operator 时检查单笔上限;owner 不受限
    function _checkLimitIfOperator(address token, uint256 amount) private view {
        if (msg.sender != owner) {
            uint256 maxAmt = perCallLimit[msg.sender][token];
            require(maxAmt != 0 && amount <= maxAmt);
        }
    }
}

Tags:
ERC20, Token|addr:0x7b9034813b66dbcf45248ac9882c5ae3d116d25b|verified:true|block:23477422|tx:0x18879b42e6af78d39b0dabbd2dfa6ae6151a40d6b382610f841b5e107eab410d|first_check:1759258447

Submitted on: 2025-09-30 20:54:08

Comments

Log in to comment.

No comments yet.