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
相关产品推荐
相关产品推荐

