You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ERC20合约暂停售卖连带禁用购买的技术问询

蜜罐合约差异分析与问题排查

问题背景

为了解并防范蜜罐骗局(一种仅允许用户买入但无法卖出的常见诈骗手段),我通过学习编码尝试复现这类合约的大致结构并逐步拆解分析。以下是我参考资料编写的ERC20合约(仅用于学习,请勿用于实际交易):

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

import "@openzeppelin/contracts@4.9.2/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts@4.9.2/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts@4.9.2/token/ERC20/extensions/ERC20Snapshot.sol";
import "@openzeppelin/contracts@4.9.2/access/AccessControl.sol";
import "@openzeppelin/contracts@4.9.2/security/Pausable.sol";
import "@openzeppelin/contracts@4.9.2/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts@4.9.2/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts@4.9.2/token/ERC20/extensions/ERC20FlashMint.sol";

contract PepeTime is ERC20, ERC20Burnable, ERC20Snapshot, AccessControl, Pausable, ERC20Permit, ERC20Votes, ERC20FlashMint {
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    
    bool public pauseSelling;
    address public creator;

    constructor() ERC20("Pepe Time", "PPT") ERC20Permit("Pepe Time") {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(SNAPSHOT_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);

        creator = msg.sender;
    }

    function snapshot() public onlyRole(SNAPSHOT_ROLE) {
        _snapshot();
    }

    function pause() public onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() public onlyRole(PAUSER_ROLE) {
        _unpause();
    }
    
    function toggleSelling() public {
        require(msg.sender == creator, "Only the contract creator can perform this action");
        pauseSelling = !pauseSelling;
    }

    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }

    modifier canTransfer(address from, address to) {
        if (pauseSelling && from != creator) {
            require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Selling is currently paused for other wallets");
        }
        _;
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override(ERC20, ERC20Snapshot)
        canTransfer(from, to)
    {
        super._beforeTokenTransfer(from, to, amount);
    }

    // The following functions are overrides required by Solidity.

    function _afterTokenTransfer(address from, address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._mint(to, amount);
    }

    function _burn(address account, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._burn(account, amount);
    }
}

测试后发现,开启pauseSelling暂停售卖时,购买功能也被一并禁用。现咨询:

  • 该合约与允许买入但禁止卖出的蜜罐合约有何差异?
  • 为何pauseSelling功能会同时影响购买?
  • 设置暂停售卖后所有钱包的购买都被禁用,问题出在何处?

问题分析与解答

1. 与典型蜜罐合约的核心差异

典型的"允许买入、禁止卖出"蜜罐合约,核心是精准区分转账场景:

  • 买入场景:代币从流动性池/合约地址转到用户钱包,此时允许转账;
  • 卖出场景:代币从用户钱包转到流动性池/合约地址,此时禁止转账。

你的合约没有做场景区分,仅通过from != creator就限制所有非创造者发起的转出操作,这会误拦截流动性池向用户转账的买入流程,和蜜罐的核心逻辑完全相悖。

2. pauseSelling影响购买的原因

在DEX(如Uniswap)上的购买流程,本质是流动性池合约调用代币的transfer函数,将代币转到用户钱包。此时from是流动性池地址,并非合约创造者,你的canTransfer modifier会触发pauseSelling && from != creator的判断,直接阻断转账,导致购买失败。

3. 问题根源与修复思路

问题出在canTransfer modifier的逻辑设计上,没有区分"用户卖出"和"用户买入"两种转账方向:

错误逻辑问题点

modifier canTransfer(address from, address to) {
    if (pauseSelling && from != creator) {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Selling is currently paused for other wallets");
    }
    _;
}

该逻辑会拦截所有非创造者地址作为from的转账,包括流动性池给用户转代币的买入操作。

修复示例(实现允许买入、禁止卖出)

要实现蜜罐的核心逻辑,需要只拦截"用户向外部转出代币"的操作,允许其他方向的转账:

modifier canTransfer(address from, address to) {
    if (pauseSelling) {
        // 允许场景:创造者转账、代币转入用户钱包(买入)
        // 禁止场景:普通用户发起的代币转出(卖出)
        require(from == creator || to == msg.sender, "Selling is paused");
    }
    _;
}

如果针对特定DEX场景,还可以提前设置流动性池地址,精准拦截向池地址的转账:

address public liquidityPool;

function setLiquidityPool(address _pool) public onlyRole(DEFAULT_ADMIN_ROLE) {
    liquidityPool = _pool;
}

modifier canTransfer(address from, address to) {
    if (pauseSelling) {
        // 仅禁止普通用户向流动性池转账(卖出)
        require(from == creator || to != liquidityPool, "Selling is paused");
    }
    _;
}

内容的提问来源于stack exchange,提问作者cryptofanboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 17:33:17