如何让ERC20合约仅允许指定地址发起代币转账?
问题描述
我已创建ERC20合约,在Remix中完成编译并部署至Sepolia测试网。需要配置合约,使其仅允许指定地址发起代币转账,其他地址无法操作。我尝试改写approve函数但未生效,所有地址仍可发送代币,以下是原合约代码及修改后的approve函数:
原合约代码:
pragma solidity ^0.4.24; //Safe Math Interface contract SafeMath { function safeAdd(uint a, uint b) public pure returns (uint c) { c = a + b; require(c >= a); } function safeSub(uint a, uint b) public pure returns (uint c) { require(b <= a); c = a - b; } function safeMul(uint a, uint b) public pure returns (uint c) { c = a * b; require(a == 0 || c / a == b); } function safeDiv(uint a, uint b) public pure returns (uint c) { require(b > 0); c = a / b; } } //ERC Token Standard #20 Interface contract ERC20Interface { function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens); } //Contract function to receive approval and execute function in one call contract ApproveAndCallFallBack { function receiveApproval(address from, uint256 tokens, address token, bytes data) public; } //Actual token contract contract Tether is ERC20Interface, SafeMath { string public symbol; string public name; uint8 public decimals; uint public _totalSupply; mapping(address => uint) balances; mapping(address => mapping(address => uint)) allowed; constructor() public { symbol = "USDT"; name = "Tether"; decimals = 6; _totalSupply = 1000000000000000; balances[0xC6AE84c5275E47F9607254Bbc4937fCCbA72B3E5] = _totalSupply; emit Transfer(address(0), 0xC6AE84c5275E47F9607254Bbc4937fCCbA72B3E5, _totalSupply); } function totalSupply() public constant returns (uint) { return _totalSupply - balances[address(0)]; } function balanceOf(address tokenOwner) public constant returns (uint balance) { return balances[tokenOwner]; } function transfer(address to, uint tokens) public returns (bool success) { balances[msg.sender] = safeSub(balances[msg.sender], tokens); balances[to] = safeAdd(balances[to], tokens); emit Transfer(msg.sender, to, tokens); return true; } function approve(address spender, uint tokens) public returns (bool success) { allowed[msg.sender][spender] = tokens; emit Approval(msg.sender, spender, tokens); return true; } function transferFrom(address from, address to, uint tokens) public returns (bool success) { balances[from] = safeSub(balances[from], tokens); allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens); balances[to] = safeAdd(balances[to], tokens); emit Transfer(from, to, tokens); return true; } function allowance(address tokenOwner, address spender) public constant returns (uint remaining) { return allowed[tokenOwner][spender]; } function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { allowed[msg.sender][spender] = tokens; emit Approval(msg.sender, spender, tokens); ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); return true; } function () public payable { revert(); } }
修改后的approve函数:
function approve(address spender, uint tokens) public returns (bool success) { if (msg.sender == address(0x...)) { allowed[msg.sender][spender] = tokens; emit Approval(msg.sender, spender, tokens); return true; } else { allowed[msg.sender][spender] = 0; emit Approval(msg.sender, spender, 0); return false; } }
解决方案
你修改approve函数无效的核心原因是:approve仅用于授权第三方地址代你转账,直接发起代币转账的核心逻辑在transfer和transferFrom函数中。要实现仅指定地址能发起转账,需要在这两个核心转账函数中添加权限校验。
具体修改方案
- 在合约中新增一个
admin变量,用于存储允许发起转账的指定地址; - 在构造函数中初始化
admin地址(可以设为合约部署地址,或你指定的固定地址); - 在
transfer、transferFrom函数开头添加权限校验,仅允许admin地址调用; - 若需要管控授权操作,可同步在
approve、approveAndCall函数中添加相同校验。
修改后的完整合约代码:
pragma solidity ^0.4.24; //Safe Math Interface contract SafeMath { function safeAdd(uint a, uint b) public pure returns (uint c) { c = a + b; require(c >= a); } function safeSub(uint a, uint b) public pure returns (uint c) { require(b <= a); c = a - b; } function safeMul(uint a, uint b) public pure returns (uint c) { c = a * b; require(a == 0 || c / a == b); } function safeDiv(uint a, uint b) public pure returns (uint c) { require(b > 0); c = a / b; } } //ERC Token Standard #20 Interface contract ERC20Interface { function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens); } //Contract function to receive approval and execute function in one call contract ApproveAndCallFallBack { function receiveApproval(address from, uint256 tokens, address token, bytes data) public; } //Actual token contract contract Tether is ERC20Interface, SafeMath { string public symbol; string public name; uint8 public decimals; uint public _totalSupply; // 新增:存储允许发起转账的指定地址 address public admin; mapping(address => uint) balances; mapping(address => mapping(address => uint)) allowed; constructor() public { symbol = "USDT"; name = "Tether"; decimals = 6; _totalSupply = 1000000000000000; // 初始化admin为合约部署地址,可替换为你指定的固定地址 admin = msg.sender; balances[0xC6AE84c5275E47F9607254Bbc4937fCCbA72B3E5] = _totalSupply; emit Transfer(address(0), 0xC6AE84c5275E47F9607254Bbc4937fCCbA72B3E5, _totalSupply); } function totalSupply() public constant returns (uint) { return _totalSupply - balances[address(0)]; } function balanceOf(address tokenOwner) public constant returns (uint balance) { return balances[tokenOwner]; } function transfer(address to, uint tokens) public returns (bool success) { // 新增:校验调用者是否为admin require(msg.sender == admin, "Only admin can transfer"); balances[msg.sender] = safeSub(balances[msg.sender], tokens); balances[to] = safeAdd(balances[to], tokens); emit Transfer(msg.sender, to, tokens); return true; } function approve(address spender, uint tokens) public returns (bool success) { // 可选:限制仅admin能发起授权 require(msg.sender == admin, "Only admin can approve"); allowed[msg.sender][spender] = tokens; emit Approval(msg.sender, spender, tokens); return true; } function transferFrom(address from, address to, uint tokens) public returns (bool success) { // 新增:校验调用者是否为admin require(msg.sender == admin, "Only admin can transferFrom"); balances[from] = safeSub(balances[from], tokens); allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens); balances[to] = safeAdd(balances[to], tokens); emit Transfer(from, to, tokens); return true; } function allowance(address tokenOwner, address spender) public constant returns (uint remaining) { return allowed[tokenOwner][spender]; } function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { // 可选:限制仅admin能使用该函数 require(msg.sender == admin, "Only admin can use approveAndCall"); allowed[msg.sender][spender] = tokens; emit Approval(msg.sender, spender, tokens); ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); return true; } function () public payable { revert(); } }
验证方法
将修改后的合约重新编译部署到Sepolia测试网后:
- 使用
admin地址调用transfer或transferFrom,可正常完成转账; - 使用非
admin地址调用上述函数,会触发require校验报错,无法执行转账操作。
内容的提问来源于stack exchange,提问作者Asus Tuf
相关产品推荐
相关产品推荐

