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

ERC20 insufficient allowance错误排查:Lottery合约placeABid函数问题

ERC20授权不足错误分析与修复

调用Lottery合约的placeABid函数时触发「ERC20 insufficient allowance」回滚错误,目标是实现用户出价后将对应数量代币转回LotteryToken.owner()的逻辑,相关代码如下:

LotteryToken.sol

//SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


pragma solidity ^0.8.4;

contract LotteryToken is Ownable, ERC20{

    mapping(address=>uint)private _balances;


    constructor(string memory name, string memory symbol, uint totalSupply)ERC20(name, symbol){
        _mint(msg.sender, totalSupply);
        _balances[msg.sender] = totalSupply;

    }

    

    function increaseAllowance(address spender, uint256 addedValue) public virtual override returns (bool) {
        address owner = owner();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }



    function _transfer(
        address _from,
        address _to,
        uint _amount
        )internal virtual override{
            _balances[_from] -= _amount;
            _balances[_to] += _amount;
            super._transfer(_from, _to, _amount);
        }
    


    function balanceOf(address account)public view virtual override returns(uint){
        return _balances[account];
    }

}

Lottery.sol

//SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import "./LotteryToken.sol";
import "./LotteryInfo.sol";

pragma solidity ^0.8.4;

contract Lottery is Ownable{

    LotteryInfo private lotteryInfo;
    LotteryToken private lotteryToken;

    event BidCorrect(address bidder, uint tokenId, uint defaultBid);

    constructor(){

    }


    function initLotteryTokenContract(address _lotteryTokenContract)public onlyOwner{
        lotteryToken = LotteryToken(_lotteryTokenContract);
        lotteryToken.increaseAllowance(address(this), lotteryToken.balanceOf(lotteryToken.owner()));
    }



    function initLotteryInfoContract(address _lotteryInfoContract)public onlyOwner{
        lotteryInfo = LotteryInfo(_lotteryInfoContract);
    }

    

    function setItemToWin(
        string memory _name,
        uint _defaultBid,
        uint _endingTimeLottery
        )public onlyOwner{
            require(_endingTimeLottery > block.timestamp, "Cannot set a date in the Past!");
            lotteryInfo.setItemToWin(_name, _defaultBid, _endingTimeLottery);
    }



    function buyTokens(uint _quantity)external payable{
        uint singleTokenPrice = 0.02 ether;
        uint finalPrice = singleTokenPrice * _quantity;
        require(msg.sender != owner() && msg.sender != lotteryToken.owner(), "Admin Cannot Buy Tokens!");
        require(msg.value == finalPrice, "Please set the right price!");
        payable(owner()).transfer(msg.value);
        address lotterytokenOwner = lotteryToken.owner();
        lotteryToken.transferFrom(lotterytokenOwner, msg.sender, _quantity);
    }



    function placeABid(uint _tokenIds)external{
        require(block.timestamp < lotteryInfo.getEndingTimeLottery(_tokenIds), "Lottery is closed!");
        require(msg.sender != owner() && msg.sender != lotteryToken.owner(), "Admin Cannot Place A Bid!");
        uint requiredTokenAmounts = lotteryInfo.getDefaultItemBid(_tokenIds);
        require(lotteryToken.balanceOf(msg.sender) >= requiredTokenAmounts, "No Necessary Funds To Partecipate!");
        address lotteryTokenOwner = lotteryToken.owner();
        lotteryToken.transferFrom(msg.sender, lotteryTokenOwner, requiredTokenAmounts);
        lotteryInfo.updateBid(_tokenIds, requiredTokenAmounts);
        lotteryInfo.updateAddress(_tokenIds, msg.sender);
        emit BidCorrect(msg.sender, _tokenIds, requiredTokenAmounts);
    }
}

错误原因

  1. placeABid中的授权缺失:
    函数内执行lotteryToken.transferFrom(msg.sender, lotteryTokenOwner, requiredTokenAmounts),该操作要求用户(msg.sender)必须预先授权Lottery合约可以从其账户中转走对应数量的代币。当前代码未处理用户授权步骤,用户未授权时直接触发「insufficient allowance」错误。

  2. LotteryToken中increaseAllowance函数逻辑错误:
    该函数硬编码使用owner()(LotteryToken合约所有者)作为授权主体,而非调用函数的msg.sender。这违背ERC20授权设计逻辑——授权应由代币持有者主动发起,当前实现会导致只有LotteryToken所有者能通过该函数授权,其他地址调用时会错误操作所有者的授权额度。

  3. 冗余的代币余额管理:
    LotteryToken自行实现_balances映射、balanceOf和_transfer函数,但ERC20合约已内置这些功能。重复实现不仅多余,还可能导致余额数据不一致(如构造函数中_mint已更新ERC20余额,又手动赋值_balances[msg.sender] = totalSupply,后续修改易出问题)。

修复方案

1. 修正LotteryToken的increaseAllowance函数

恢复OpenZeppelin ERC20默认逻辑,让授权由调用者(代币持有者)发起:

function increaseAllowance(address spender, uint256 addedValue) public virtual override returns (bool) {
    address owner = _msgSender();
    _approve(owner, spender, allowance(owner, spender) + addedValue);
    return true;
}

2. 移除LotteryToken中冗余的余额管理代码

删除自定义的_balances映射、balanceOf和_transfer函数,直接使用ERC20内置实现:

// 移除以下代码块:
mapping(address=>uint)private _balances;

function _transfer(
    address _from,
    address _to,
    uint _amount
    )internal virtual override{
        _balances[_from] -= _amount;
        _balances[_to] += _amount;
        super._transfer(_from, _to, _amount);
    }

function balanceOf(address account)public view virtual override returns(uint){
    return _balances[account];
}

3. 处理用户出价前的授权逻辑

  • 前端层面:在用户调用placeABid前,引导用户调用LotteryToken的approve或increaseAllowance函数,授权Lottery合约转走至少requiredTokenAmounts数量的代币。
  • 合约层面:在placeABid中增加授权检查,若授权不足则回滚并提示:
function placeABid(uint _tokenIds)external{
    require(block.timestamp < lotteryInfo.getEndingTimeLottery(_tokenIds), "Lottery is closed!");
    require(msg.sender != owner() && msg.sender != lotteryToken.owner(), "Admin Cannot Place A Bid!");
    uint requiredTokenAmounts = lotteryInfo.getDefaultItemBid(_tokenIds);
    require(lotteryToken.balanceOf(msg.sender) >= requiredTokenAmounts, "No Necessary Funds To Partecipate!");
    // 新增授权检查
    require(lotteryToken.allowance(msg.sender, address(this)) >= requiredTokenAmounts, "Insufficient allowance to place bid");
    address lotteryTokenOwner = lotteryToken.owner();
    lotteryToken.transferFrom(msg.sender, lotteryTokenOwner, requiredTokenAmounts);
    lotteryInfo.updateBid(_tokenIds, requiredTokenAmounts);
    lotteryInfo.updateAddress(_tokenIds, msg.sender);
    emit BidCorrect(msg.sender, _tokenIds, requiredTokenAmounts);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:57:21