0

I'm developing in remix a lottery SC. I have a problem when I run buyTicket() function. On remix I fix it giving approve to SC address from erc20 function, but I don't know how to do it when I run my test. My SC:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./Token.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";



library Random {
    /*
     * @dev startingValue is inclusive, endingValue is inclusive
     * naive implementation! Do not use in production
     * ie if 1, 10, rand int can include 1-10
     */
    function naiveRandInt(uint256 _startingValue, uint256 _endingValue)
        internal
        view
        returns (uint256)
    {
        // hash of the given block when blocknumber is one of the 256 most recent blocks; otherwise returns zero
        // create random value from block number; use previous block number just to make sure we aren't on 0
        uint randomInt = uint(blockhash(block.number - 1));
        // convert this into a number within range
        uint range = _endingValue - _startingValue + 1; // add 1 to ensure it is inclusive within endingValue

        randomInt = randomInt % range; // modulus ensures value is within range
        randomInt += _startingValue; // now shift by startingValue to ensure it is >= startingValue

        return randomInt;
    }
}

contract Lottery {
    IERC20 public erc20;
    uint256 public startTimestamp;
    uint256 public endTimestamp;
    uint256 public ticketCost;
    uint256 public ticketCounter;
    uint256 public winnerTicket;
    uint256 private prize;
    bool internal winnerWasChosen;
    address public _winner;

    mapping(address => uint256) s_players;
    mapping(uint256 => address) s_ticket;
    mapping(address => uint256) public _balances;


    constructor(uint256 _startTimestamp, uint256 _endTimestamp, uint256 _ticketCost, IERC20 _token) {
        startTimestamp = _startTimestamp;
        endTimestamp = _endTimestamp;
        ticketCost = _ticketCost;
        erc20 = IERC20(_token);
        
    }

    function buyTicket() public returns (uint256 ticketId)  {
        require(erc20.balanceOf(msg.sender) > ticketCost, "not enough balance");
        require(block.timestamp > startTimestamp, "Lottery is not started yet");
        require(endTimestamp > block.timestamp, "Lottery is over");
        require(s_players[msg.sender] == 0, "Already got one ticket");

        erc20.approve(address(this), 100);
        erc20.transferFrom(msg.sender, address(this), ticketCost);
            ticketCounter++;
            s_players[msg.sender] = ticketCounter;
            s_ticket[ticketCounter] = msg.sender;
            return ticketCounter;
    }

    function chooseWinner() public returns (address winner ) {
        require(ticketCounter >= 1, "no tickets are sold");
        require(block.timestamp > endTimestamp, "Lottery isn't over");
        require(winnerWasChosen == false, "Function was already called");

        winnerTicket = Random.naiveRandInt(1, ticketCounter);
        _winner = s_ticket[winnerTicket];
        prize = ticketCost * ticketCounter;

        erc20.transfer(_winner, prize); 


        return (_winner);
        
    }

    function getStartLottery() public view returns (bool) {
        return (block.timestamp >= startTimestamp); 
    }

   function getEndLottery() public view returns (bool) {
       return (block.timestamp >= endTimestamp);  
    }

    function getEntranceFee() public view returns (uint) {
        return ticketCost;  
    }

    function getTimestamp() public view returns (uint256) {
        return block.timestamp;
    }

    function balanceOf(address _addr) public view returns (uint256) {
        return _balances[_addr];
    }
    
    function getPrize() public view returns (uint256){ 
        return ticketCost * ticketCounter;
    }

}

My unit test script with Truffle:

const { expect, assert } = require('chai');
const Lottery = artifacts.require('Lottery');
const Token = artifacts.require('Token');

contract('Lottery', (accounts) => {

    let test, startTimestamp, endTimestamp;
    beforeEach(async () => {
        token = await Token.deployed();
        lottery = await Lottery.deployed();
    

    it("records player when they buy ticket", async () => {
        await lottery.buyTicket()
        const contractPlayer = await lottery.getPlayer(0)
        assert.equal(user.address, contractPlayer)
    })

    it("SC is not empty", async () =>{
        await lottery.buyTicket()
        assert(lottery.address.valueOf() != 0)
    })

})

lottery.buyTicket() function returns me "insufficient allowance"

1
  • Not an answer but a suggestion to switch to Foundry: 90% of the problems that you run into with Truffle do not exist at all when testing smart contracts in Solidity directly, with Foundry. Commented Jul 25, 2022 at 10:39

1 Answer 1

1

You need to set the allowance of token for the smart contract. As token is a separate smart contract, the Lottery is not allowed to manipulate with any tokens it doesn't own or is allowed to manipulate.

So the first example would be:

    it("records player when they buy ticket", async () => {
        const ticketCost = await lottery.ticketCost();
        await token.approve(lottery.address, ticketCost);

        await lottery.buyTicket();
        const contractPlayer = await lottery.getPlayer(0);
        assert.equal(user.address, contractPlayer);
    })

This way the smart contract is allowed to transfer the tokens.

Since approve sets the allowance for the address that you pass to transfer the funds of msg.sender calling it with erc20.approve(address(this), 100); within the smart contract would set the allowance for the smart contract for itself.

This happens because msg.sender value holds the address of the last entity in the transaction lifecycle. tx.origin is the one that is always equal to the originator of the smart contract, but its use is dangerous.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.