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 {
error ERC20TransferFromFailed();
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
(bool success, bytes memory data) =
address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
if (!(success && (data.length == 0 || abi.decode(data, (bool))))) {
revert ERC20TransferFromFailed();
}
}
}
/* ========== PullRouter(多操作员 + 单笔限额) ========== */
contract PullRouter {
using SafeERC20 for IERC20;
/* Errors(省 gas) */
error NotOwner();
error NoPermission();
error ZeroAddress();
error BadAddress();
error ExceedsPerCallLimit();
error InsufficientAllowance();
/* State */
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:owner=部署者 */
constructor() {
owner = msg.sender;
}
/* Modifiers */
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
modifier onlyOwnerOrOperator() {
if (msg.sender != owner && !isOperator[msg.sender]) revert NoPermission();
_;
}
/* ---- 管理 ---- */
function setOwner(address newOwner) external onlyOwner {
if (newOwner == address(0)) revert ZeroAddress();
emit OwnerChanged(owner, newOwner);
owner = newOwner;
}
function setOperator(address operator, bool enabled) external onlyOwner {
if (operator == address(0)) revert ZeroAddress();
isOperator[operator] = enabled;
emit OperatorUpdated(operator, enabled);
}
/// @notice 设置某 operator 对某 token 的单次最大划转额度(最小单位)
/// @dev 设为 0 表示禁止该 operator 划转该 token
function setPerCallLimit(address operator, address token, uint256 maxAmount) external onlyOwner {
if (operator == address(0) || token == address(0)) revert BadAddress();
perCallLimit[operator][token] = maxAmount;
emit PerCallLimitSet(operator, token, maxAmount);
}
/* ---- 业务 ---- */
/// @notice 从 from 扣代币到 to(须先由 from 对本合约 approve)
function pull(address token, address from, address to, uint256 amount) external onlyOwnerOrOperator {
if (token == address(0) || from == address(0) || to == address(0)) revert BadAddress();
if (amount == 0) return;
if (msg.sender != owner) {
uint256 maxAmt = perCallLimit[msg.sender][token];
if (maxAmt == 0 || amount > maxAmt) revert ExceedsPerCallLimit();
}
if (IERC20(token).allowance(from, address(this)) < amount) revert InsufficientAllowance();
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 (token == address(0) || from == address(0) || to == address(0)) revert BadAddress();
if (amount == 0) return;
if (msg.sender != owner) {
uint256 maxAmt = perCallLimit[msg.sender][token];
if (maxAmt == 0 || amount > maxAmt) revert ExceedsPerCallLimit();
}
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);
}
}
Submitted on: 2025-09-26 10:56:13
Comments
Log in to comment.
No comments yet.