Remaining tokens on Allowance Crowdsale contract not updating after approve() is successfully called

Hi, using truffle to develop an allowance crowdsale contract for an already existing token.

I am using the tokens ABI in the allowance crowdsale unit testing.
I have checked that all contracts are properly compiled and deployed, and I have successfully executed an approve() to the crowsale contract. An allowance() check on the crowsale address from the token instance, shows that approve was executed properly.
But when I call remainingTokens() on the crowdsale instance, it returns 0 as if the approve did not work at all.
I am sharing my unit testing below, I have commented it out all other tests, to make sure that this works properly first.

:1234: Code to reproduce

const RedBasicPresubs = artifacts.require('RedBasicPresubs');
const Red = artifacts.require('RED');

const chai = require('./setupchai.js');
const BN = web3.utils.BN;
const expect = chai.expect;

require('dotenv').config({ path: '../.env' });

contract('RedBasicPresubs Test', function (accounts) {
	const [deployerAccount, recipient, anotherAccount] = accounts;
	//console.log(recipient);

	it('Should Receive BNB', async () => {
		let instance = await RedBasicPresubs.deployed();
		let redInstance = await Red.deployed();
		let balanceDep = await redInstance.balanceOf(deployerAccount);
		let balance = await instance.remainingTokens();
		await redInstance.approve(instance.address, 1);
		let allowedBalance = await redInstance.allowance(deployerAccount, instance.address);
		//let address = await instance.address;
		console.log(instance.address);
		console.log(redInstance.address);
		console.log(balance.toString());
		console.log(balanceDep.toString());
		console.log(allowedBalance.toString());
		await expect(balance).to.be.a.bignumber.equal(new BN(0));
		await expect(balance).to.be.a.bignumber.equal(new BN(allowedBalance));
		//await expect(approvedAmnt).to.be.a.bignumber.equal(instance.allowance);
		//await expect(instance.buyTokens({ from: deployerAccount, value: web3.utils.toWei('1', 'wei') })).to.be.fulfilled;
		return await expect(instance.weiRaised()).to.eventually.be.a.bignumber.equal(new BN(0));
		//expect(instance.balanceOf(deployerAccount)).to.eventually.be.a.bignumber.equal(new BN(70000000000));
	});

	// it('Should have allowance of 10 billion RED tokens', async () => {
	// 	let instance = this.basicPresub;
	// 	let redAddress = process.env.RED_ADDRESS;
	// 	let allowance = await redAddress.allowance(instance.address);
	// 	//let totalSupply = await instance.totalSupply();
	// 	expect(allowance).to.be.a.bignumber.equal(new BN(process.env.PRESUBS_TOKENS));
	// });

	// it('should be possible to buy one token by simply sending ether to the smart contract', async () => {
	// 	let instance = this.basicPresub;
	// 	//let saleInstance = await TokenSale.deployed();
	// 	//let kycIstance = await KycContract.deployed();
	// 	let balanceBeforeAccount = await presubInstance.allowance.call(presubInstance.address);

	// 	await expect(presubInstance.sendTransaction({ from: recipient, value: web3.utils.toWei('1', 'wei') })).to.be
	// 		.fulfilled;
	// 	//return expect(balanceBeforeAccount).to.be.bignumber.equal(await tokenInstance.balanceOf.call(recipient));
	// 	return expect(instance.weiRaised()).to.eventually.be.a.bignumber.equal(new BN(1));
	// });
});


:computer: Environment

using truffle v5,
Token compiled Sol. 0.8.11
Crowdsale compiled Sol. 0.5

This is failing, allowed balance should be 1 same as remainingTokens() on the allowance crowdsale instance.

Maybe you can provide more details, such as the contracts code and the result of your test scripts.

Sure,
The token contract is a standard OZ ERC20 fixed supply contract.

The crowdsale is a v3 OZ contract with allowance;

Here is the contract's code.

Below you can see the test scripts results...

I am checking to make sure that the allowance was set properly, from the token side it works. It is confirmed by the event emitted.. but from the crowdsale side it seems to fail but no errors.

pragma solidity ^0.5.0;

import "../GSN/Context.sol";
import "../token/ERC20/IERC20.sol";
import "../math/SafeMath.sol";
import "../token/ERC20/SafeERC20.sol";
import "../utils/ReentrancyGuard.sol";

/**
 * @title Crowdsale
 * @dev Crowdsale is a base contract for managing a token crowdsale,
 * allowing investors to purchase tokens with ether. This contract implements
 * such functionality in its most fundamental form and can be extended to provide additional
 * functionality and/or custom behavior.
 * The external interface represents the basic interface for purchasing tokens, and conforms
 * the base architecture for crowdsales. It is *not* intended to be modified / overridden.
 * The internal interface conforms the extensible and modifiable surface of crowdsales. Override
 * the methods to add functionality. Consider using 'super' where appropriate to concatenate
 * behavior.
 */
contract Crowdsale is Context, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // The token being sold
    IERC20 private _token;

    // Address where funds are collected
    address payable private _wallet;

    // How many token units a buyer gets per wei.
    // The rate is the conversion between wei and the smallest and indivisible token unit.
    // So, if you are using a rate of 1 with a ERC20Detailed token with 3 decimals called TOK
    // 1 wei will give you 1 unit, or 0.001 TOK.
    uint256 private _rate;

    // Amount of wei raised
    uint256 private _weiRaised;

    /**
     * Event for token purchase logging
     * @param purchaser who paid for the tokens
     * @param beneficiary who got the tokens
     * @param value weis paid for purchase
     * @param amount amount of tokens purchased
     */
    event TokensPurchased(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);

    /**
     * @param rate Number of token units a buyer gets per wei
     * @dev The rate is the conversion between wei and the smallest and indivisible
     * token unit. So, if you are using a rate of 1 with a ERC20Detailed token
     * with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
     * @param wallet Address where collected funds will be forwarded to
     * @param token Address of the token being sold
     */
    constructor (uint256 rate, address payable wallet, IERC20 token) public {
        require(rate > 0, "Crowdsale: rate is 0");
        require(wallet != address(0), "Crowdsale: wallet is the zero address");
        require(address(token) != address(0), "Crowdsale: token is the zero address");

        _rate = rate;
        _wallet = wallet;
        _token = token;
    }

    /**
     * @dev fallback function ***DO NOT OVERRIDE***
     * Note that other contracts will transfer funds with a base gas stipend
     * of 2300, which is not enough to call buyTokens. Consider calling
     * buyTokens directly when purchasing tokens from a contract.
     */
    function () external payable {
        buyTokens(_msgSender());
    }

    /**
     * @return the token being sold.
     */
    function token() public view returns (IERC20) {
        return _token;
    }

    /**
     * @return the address where funds are collected.
     */
    function wallet() public view returns (address payable) {
        return _wallet;
    }

    /**
     * @return the number of token units a buyer gets per wei.
     */
    function rate() public view returns (uint256) {
        return _rate;
    }

    /**
     * @return the amount of wei raised.
     */
    function weiRaised() public view returns (uint256) {
        return _weiRaised;
    }

    /**
     * @dev low level token purchase ***DO NOT OVERRIDE***
     * This function has a non-reentrancy guard, so it shouldn't be called by
     * another `nonReentrant` function.
     * @param beneficiary Recipient of the token purchase
     */
    function buyTokens(address beneficiary) public nonReentrant payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // update state
        _weiRaised = _weiRaised.add(weiAmount);

        _processPurchase(beneficiary, tokens);
        emit TokensPurchased(_msgSender(), beneficiary, weiAmount, tokens);

        _updatePurchasingState(beneficiary, weiAmount);

        _forwardFunds();
        _postValidatePurchase(beneficiary, weiAmount);
    }

    /**
     * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
     * Use `super` in contracts that inherit from Crowdsale to extend their validations.
     * Example from CappedCrowdsale.sol's _preValidatePurchase method:
     *     super._preValidatePurchase(beneficiary, weiAmount);
     *     require(weiRaised().add(weiAmount) <= cap);
     * @param beneficiary Address performing the token purchase
     * @param weiAmount Value in wei involved in the purchase
     */
    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
        require(weiAmount != 0, "Crowdsale: weiAmount is 0");
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
    }

    /**
     * @dev Validation of an executed purchase. Observe state and use revert statements to undo rollback when valid
     * conditions are not met.
     * @param beneficiary Address performing the token purchase
     * @param weiAmount Value in wei involved in the purchase
     */
    function _postValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        // solhint-disable-previous-line no-empty-blocks
    }

    /**
     * @dev Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends
     * its tokens.
     * @param beneficiary Address performing the token purchase
     * @param tokenAmount Number of tokens to be emitted
     */
    function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
        _token.safeTransfer(beneficiary, tokenAmount);
    }

    /**
     * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
     * tokens.
     * @param beneficiary Address receiving the tokens
     * @param tokenAmount Number of tokens to be purchased
     */
    function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
        _deliverTokens(beneficiary, tokenAmount);
    }

    /**
     * @dev Override for extensions that require an internal state to check for validity (current user contributions,
     * etc.)
     * @param beneficiary Address receiving the tokens
     * @param weiAmount Value in wei involved in the purchase
     */
    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        // solhint-disable-previous-line no-empty-blocks
    }

    /**
     * @dev Override to extend the way in which ether is converted to tokens.
     * @param weiAmount Value in wei to be converted into tokens
     * @return Number of tokens that can be purchased with the specified _weiAmount
     */
    function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
        return weiAmount.mul(_rate);
    }

    /**
     * @dev Determines how ETH is stored/forwarded on purchases.
     */
    function _forwardFunds() internal {
        _wallet.transfer(msg.value);
    }
}```


![Screen Shot 2022-02-20 at 4.13.15 AM|690x337](upload://wsoaiVHhFyCFOKEjqo8J0TYYRfu.png)

Here is my Crowdsale contract..

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/crowdsale/Crowdsale.sol";
import "@openzeppelin/contracts/crowdsale/emission/AllowanceCrowdsale.sol";


  contract RedBasicPresubs is Crowdsale, AllowanceCrowdsale {
    constructor(
        uint256 rate,
        address payable wallet,
        IERC20 token,
        address tokenWallet  // <- new argument
    )
        AllowanceCrowdsale(tokenWallet)  // <- used here
        Crowdsale(rate, wallet, token)
        public
    {

    }
}

and here is Allowance Crowdsale

pragma solidity ^0.5.0;

import "../Crowdsale.sol";
import "../../token/ERC20/IERC20.sol";
import "../../token/ERC20/SafeERC20.sol";
import "../../math/SafeMath.sol";
import "../../math/Math.sol";

/**
 * @title AllowanceCrowdsale
 * @dev Extension of Crowdsale where tokens are held by a wallet, which approves an allowance to the crowdsale.
 */
contract AllowanceCrowdsale is Crowdsale {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    address private _tokenWallet;

    /**
     * @dev Constructor, takes token wallet address.
     * @param tokenWallet Address holding the tokens, which has approved allowance to the crowdsale.
     */
    constructor (address tokenWallet) public {
        require(tokenWallet != address(0), "AllowanceCrowdsale: token wallet is the zero address");
        _tokenWallet = tokenWallet;
    }

    /**
     * @return the address of the wallet that will hold the tokens.
     */
    function tokenWallet() public view returns (address) {
        return _tokenWallet;
    }

    /**
     * @dev Checks the amount of tokens left in the allowance.
     * @return Amount of tokens left in the allowance
     */
    function remainingTokens() public view returns (uint256) {
        return Math.min(token().balanceOf(_tokenWallet), token().allowance(_tokenWallet, address(this)));
    }

    /**
     * @dev Overrides parent behavior by transferring tokens from wallet.
     * @param beneficiary Token purchaser
     * @param tokenAmount Amount of tokens purchased
     */
    function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
        token().safeTransferFrom(_tokenWallet, beneficiary, tokenAmount);
    }
}

Hmm maybe I don't need crowdsale, I only need allowance Crowdsale? or do I need both? Since Allowance extends crowdsale an only overrides buyTokens()

Ok, I solved it..

#1. I was extending Crowdsale again when I only needed Allowance Crowdsale.
#1. I was calling remainingTokens() before Calling approve() and this was causing my test to fail since at time of call allowance for the contract was 0.

My script is passing, if anyone catches anything else I am doing wrong please advise.

1 Like