Safemath Functions in Solidity – Using Safemath Sub and Add

contract-developmenterc-20openzeppelinsolidity

Well, I'm creating a contract to be my ICO, but I have a "simple" problem using the SafeMath lib.

What is the problem?
I need to limit the purchase of my token to a maximum of "5 ether(bnb)", so I created a variable called "investorBalances" where I save the amount that was purchased.

That done, I put a check on my code where I confirm that the amount purchased by the investor is not higher than the limit. But then the error happens, it returns my "revert" message saying that it has already reached the limit, however, this is the first purchase…

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

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

contract SALES is Ownable {
  using SafeMath for uint256;
  
  IERC20 token;

  uint256 private rate = 3000; // Number of tokens per BNB  
  uint256 private start = 1636914760; // 16/11/2021
  uint256 private dayMax = 45; // 45 Day
  uint256 private buyMax = 5; //5 bnb
  
  uint256 public initialTokens; // Initial number of tokens available
  bool public initialized = false;
  uint256 public raisedAmount = 0;

  mapping(address => uint256) investorBalances;

  /**
   * BoughtTokens
   * @dev Log tokens bought onto the blockchain
   */
  event BoughtTokens(address indexed to, uint256 value);

  constructor(address _token){
    setAddrERC20(_token);
  }

  /**
   * @dev Fallback function if ether is sent to address insted of buyTokens function
   **/
  receive() external payable {
    buyTokens();
  }

   /**
   * buyTokens
   * @dev function that sells available tokens
   **/
  function buyTokens() public payable whenSaleIsActive {
    uint256 weiAmount = msg.value; // Calculate tokens to sell
    uint256 tokens = weiAmount.mul(rate);
    uint256 amountMax = investorBalances[msg.sender].add(weiAmount);

    if(amountMax >= buyMax){
      revert("You have reached the purchase limit");
    }

    emit BoughtTokens(msg.sender, tokens); // log event onto the blockchain
    raisedAmount = raisedAmount.add(msg.value); // Increment raised amount
    token.transfer(msg.sender, tokens); // Send tokens to buyer
    
    investorBalances[msg.sender] = amountMax;

    payable(owner()).transfer(msg.value);// Send money to owner
  }

  /**
   * whenSaleIsActive
   * @dev ensures that the contract is still active
   **/
  modifier whenSaleIsActive() {
    // Check if sale is active
    assert(isActive());
    _;
  }

  function isActive() public view returns (bool) {
    return (
        initialized == true &&
        block.timestamp >= start && // Must be after the START date
        block.timestamp <= start.add(dayMax * 1 days)// Must be before the end date        
    );
  }

  /**
   * terminate
   * @notice Terminate contract and refund to owner
   **/
  function terminateSales() public onlyOwner  {
    // Transfer tokens back to owner    
    uint256 balance = token.balanceOf(address(this));
    assert(balance > 0);
    token.transfer(owner(), balance);    
  }

  /**
   * tokensAvailable
   * @dev returns the number of tokens allocated to this contract
   **/
  function tokensAvailable() public view returns (uint256) {
    return token.balanceOf(address(this));
  }

  /**
   * investorBalanceAvailable
   * @dev returns the number of tokens the investor bought
   **/
  function investorBalanceAvailable(address _investor) public view returns (uint256) {
    return investorBalances[_investor];
  }

  /**
   * initialize
   * @dev Initialize the contract
   **/
  function initialize() public onlyOwner {
      require(initialized == false); // Can only be initialized once      
      initialized = true;
  }

  function setRate(uint _rate) public onlyOwner{
    rate = _rate;
  }

  function setBuyMax(uint _buyMax) public onlyOwner{
    buyMax = _buyMax;
  }

  function setStart(uint _start) public onlyOwner{
    start = _start;
  }

  function setDays(uint _days) public onlyOwner{
    dayMax = _days;
  }

  function setAddrERC20(address _tokenAddr) public onlyOwner{
    require(_tokenAddr != address(0));
    token = IERC20(_tokenAddr);
  }
}

My function in test.js

it(`Buy with account Investor1 4 ether = 12000 tokens`, async () => {
            await contractInstance.initialize();
            await contractInstance.buyTokens({
                from: investor1,
                value: web3.utils.toWei("4", "ether"),
                gas: 200000
            })
            let total = await coinInstance.balanceOf(investor1);
            expect(web3.utils.fromWei(total)).to.be.equal("12000");
        });

Output:

Validate pre-sales from account privateSales
         Buy with account Investor1 4 ether = 12000 tokens:
     Error: Returned error: VM Exception while processing transaction: revert You have reached the purchase limit -- Reason given: You have reached the purchase limit.  ```

Best Answer

You compare wei (msg.value) with something which doesnt look like wei (but rather ethers/bnb), uint256 buyMax = 5;

To compare and have the behavior you're describing in comment, you need to multiply buyMax by 10^18 (or use the "ether" unit, uint256 buyMax = 5 ether;, which is a shortcut for 10^18)

edit for clarity: if you replace your variables by their values, your test is failing on : if(amountMax >= buyMax) because you're testing if(5000000000000000000 >= 5)

Related Topic