1

This may look duplicate but I can't find a proper answer. I aim to develop a contract where contract owner can set recipients and the number of tokens they can withdraw. Once they are set, the recipient can call a function with metamask bearing the gas price and redeem the tokens.

  • Have deployed the below contract via Remix (Token owner is not same the contract owner)
  • When tried to execute the method setBalances with only 2 items in each array, that resulted in error transact to WithDraw.setBalances errored: Error: gas required exceeds allowance or always failing transaction
  • Have changed the gas limit to 900000000000 but still the same result.
  • Just for check, called redeem which also yield the same error transact to WithDraw.redeem errored: Error: gas required exceeds allowance or always failing transaction

Smart Contract Code

pragma solidity ^0.4.18;

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {

  address public owner;
  event OwnershipTransferred (address indexed _from, address indexed _to);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public{
    owner = msg.sender;
    OwnershipTransferred(address(0), owner);
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    owner = newOwner;
    OwnershipTransferred(owner,newOwner);
  }
}

/**
 * @title Token
 * @dev API interface for interacting with the Token contract 
 */
interface Token {
  function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
  function balanceOf(address _owner) constant external returns (uint256 balance);
}

contract WithDraw is Ownable {

  Token token;
  mapping(address => uint256) public redeemBalanceOf;
  event BalanceSet(address indexed beneficiary, uint256 value);
  event Redeemed(address indexed beneficiary, uint256 value);

  function WithDraw() public {
      address _tokenAddr = MY_TOKEN_ADDR;
      token = Token(_tokenAddr);
  }

  function setBalances(address[] dests, uint256[] values) onlyOwner public {
    uint256 i = 0;
    while (i < dests.length) {
        if(dests[i] == address(0)) continue;
        uint256 toSend = values[i] * 10**18;
        redeemBalanceOf[dests[i]] += toSend;
        i++;
        BalanceSet(dests[i],values[i]);
    }
  }

  function redeem(uint256 quantity) external{
      uint256 baseUnits = quantity * 10**18;
      require(redeemBalanceOf[msg.sender]>=baseUnits);
      redeemBalanceOf[msg.sender] -= baseUnits;
      token.transferFrom(owner,msg.sender,baseUnits);
      Redeemed(msg.sender,quantity);
  }

}

What is the cause of the error? Should the contract owner same as token contract owner?

EDIT1

Based on @smarx reply, have changed the setBalances method like below.

  function setBalances(address[] dests, uint256[] values) onlyOwner public {
    for (uint256 i = 0; i < dests.length; i++) {
        if(dests[i] != address(0)) 
        {
            uint256 toSend = values[i] * 10**18;
            redeemBalanceOf[dests[i]] += toSend;
            BalanceSet(dests[i],values[i]);
        }
        i++;
    }
  }

Now, when I pass two items in the array to set value, am seeing event log for only the first item, and the second one got missed. Confused!. Whats wrong with the for loop?

Apart from the loop issue, calling the redeem function also throws the same error transact to WithDraw.redeem errored: Error: gas required exceeds allowance or always failing transaction even when passing less quantity than what is already assigned. Is it needed to call the approve method before token transfer in that method?

EDIT2

Couldn't the issue with for loop. Modified it back to while back with approve function as below.This time setBalances worked properly, the balances were showing properly.

function setBalances(address[] dests, uint256[] values) onlyOwner public {
    uint256 i = 0;
    while (i < dests.length){
        if(dests[i] != address(0)) 
        {
            uint256 toSend = values[i] * 10**18;
            redeemBalanceOf[dests[i]] += toSend;
            token.approve(dests[i], toSend);
            BalanceSet(dests[i],values[i]);
        }
        i++;
    }
  }

But still, the redeem is throwing that same error. Any help is highly appreciated.

EDIT 3

Have made the changes like below. And this time have transferred enough number of tokens to this contract address

  function setBalances(address[] dests, uint256[] values) onlyOwner public {
    uint256 i = 0; 
    while (i < dests.length){
        if(dests[i] != address(0)) 
        {
            uint256 toSend = values[i] * 10**18;
            redeemBalanceOf[dests[i]] += toSend;
            BalanceSet(dests[i],values[i]);
        }
        i++;
    }
  }

  function redeem(uint256 quantity) external{
      uint256 baseUnits = quantity * 10**18;
      uint256 tokensAvailable = token.balanceOf(this);
      require(redeemBalanceOf[msg.sender]>=baseUnits);
      require( tokensAvailable >= baseUnits);
      redeemBalanceOf[msg.sender] -= baseUnits;
      token.transfer(msg.sender,baseUnits);
      Redeemed(msg.sender,quantity);
  }

But still getting the same issue. Am surely missing something, don't know what is it. Please help.

  • What's the purpose of if (dest[i] == address(0)) continue;? That will cause an infinite loop if it ever happens. – user19510 Mar 1 '18 at 11:28
  • Hello @smarx, Updated the question, please review and suggest. – Rajesh Mar 1 '18 at 12:26
  • redeem calls token.transferFrom(owner, ...). Perhaps owner hasn't approved a sufficient number of tokens for that to work? (Try commenting out that line to see if it's the source of the revert.) – user19510 Mar 1 '18 at 19:52
  • I see that setBalances also calls token.approve. I think there's some confusion over where the tokens are. It can't make sense to have both of those lines. – user19510 Mar 1 '18 at 19:54
  • The tokens are with contract's owner that would approve the tokens to be withdrawn on the redeem function. Is there something missing? – Rajesh Mar 2 '18 at 5:03
2

I think the cause of your error is that you're reading past the end of your arrays:

while (i < dests.length) {
    // ...
    i++;
    // On the last iteration, i is dests.length, which is out of bounds.
    BalanceSet(dests[i],values[i]);
}

Move the i++ to after the BalanceSet line, or better yet, use a for loop:

for (uint256 i = 0; i < dests.length; i++) {
    // ...
    BalanceSet(dests[i], values[i]);
}

This will also avoid the infinite loop you currently have if someone passes the address 0.

  • Thanks smarx. Have changed it to for loop and retained this line too if(dests[i] == address(0)) continue; just to preventive passing of 0 address. This time the error has not appeared, but redeemBalanceOf not showing value to one of the item that was passed in the array. What is missing again? – Rajesh Mar 1 '18 at 11:41
0

Though I do not want to answer my own question, thought it would help to others. The entire working copy is pasted below.

  • Create this contract
  • Have enough tokens transferred to it (if you are using token contract's transfer method, ensure proper decimals followed, and ensure in etherscan if you see enough number of tokens)

This can be useful for airdrop companies that can set the eligible tokens and ask the participants to withdraw on their own.

pragma solidity ^0.4.18;

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {

  address public owner;
  event OwnershipTransferred (address indexed _from, address indexed _to);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public{
    owner = msg.sender;
    OwnershipTransferred(address(0), owner);
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    owner = newOwner;
    OwnershipTransferred(owner,newOwner);
  }
}

/**
 * @title Token
 * @dev API interface for interacting with the Token contract 
 */
interface Token {
  function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
  function balanceOf(address _owner) constant external returns (uint256 balance);
}

contract WithDraw is Ownable {

  Token token;
  mapping(address => uint256) public redeemBalanceOf;
  event BalanceSet(address indexed beneficiary, uint256 value);
  event Redeemed(address indexed beneficiary, uint256 value);

  function WithDraw() public {
      address _tokenAddr = MY_TOKEN_ADDR;
      token = Token(_tokenAddr);
  }

function setBalances(address[] dests, uint256[] values) onlyOwner public {
    uint256 i = 0; 
    while (i < dests.length){
        if(dests[i] != address(0)) 
        {
            uint256 toSend = values[i] * 10**18;
            redeemBalanceOf[dests[i]] += toSend;
            BalanceSet(dests[i],values[i]);
        }
        i++;
    }
  }

  function redeem(uint256 quantity) external{
      uint256 baseUnits = quantity * 10**18;
      uint256 senderEligibility = redeemBalanceOf[msg.sender];
      uint256 tokensAvailable = token.balanceOf(this);
      require(senderEligibility >= baseUnits);
      require( tokensAvailable >= baseUnits);
      if(token.transfer(msg.sender,baseUnits)){
        redeemBalanceOf[msg.sender] -= baseUnits;
        Redeemed(msg.sender,quantity);
      }
  }

  function removeBalances(address[] dests, uint256[] values) onlyOwner public {
    uint256 i = 0; 
    while (i < dests.length){
        if(dests[i] != address(0)) 
        {
            uint256 toRevoke = values[i] * 10**18;
            if(redeemBalanceOf[dests[i]]>=toRevoke)
            {
                redeemBalanceOf[dests[i]] -= toRevoke;
                BalanceCleared(dests[i],values[i]);
            }
        }
        i++;
    }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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