4

In a transaction in which some token (e.g. DGD, DAO) balance is transferred, there is no need to verify that the address has enough tokens before sending the transaction.

But if the sender doesn't have enough token balance, the transaction will be broadcasted but the balances of sender and receiver won't change.

How can I know programatically that this transfer couldn't go through?

1

According to the transfer function in contract, it will return false if the sender balance doesn't have enough balance to send to the receiver. By tracing the transaction with parity, it will return a field output in result.

If the return false, the output will be 0x0000000000000000000000000000000000000000000000000000000000000000 instead of 0x0000000000000000000000000000000000000000000000000000000000000001.

"action": {
      "callType": "call",
      "from": senderAddress,
      "gas": "0x10578",
      "input": inputData,
      "to": contractAddress,
      "value": "0x0"
    },
    "blockHash": "",
    "blockNumber": 00,
    "result": {
      "gasUsed": "0x2d8",
      "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
    },
    "subtraces": 0,
    "traceAddress": [],
    "transactionHash": "txID",
    "transactionPosition": 1,
    "type": "call"
  }

https://github.com/ethcore/parity/wiki/JSONRPC-trace-module#example-2

However, I can't find the way to check with geth.

1

I assume you are working with geth. Now, to track whether your transaction was successful or not, you can debug the transaction.

If you have 100 token in your account and you try to send 110 tokens, the transaction will be send for mining but since you don't have sufficient balance in your account, the transaction will throw and consume all the gas provided with the transaction and throw out of gas. You can debug the transaction by:

var status = debug.traceTransaction("txHAsh");
if (status.structLogs.length > 0) {
    console.log(status.structLogs[status.structLogs.length-1].error)
}

If above code results "invalid jump destination (PUSH1) 2", this means your transaction failed due to low gas. Else your transction is successful.

  • The problem is not related to out of gas. The tx passed when I trying to transfer an amount of token I don't have but it won't affect the real token balance. How can I know that tx didn't affect the balance. – Roy Pun Oct 4 '16 at 5:16
  • Even when you don't have sufficient balance, the transaction will be send to blockchain but the transaction will consume all the provided gas and hence will throw out of gas. – Prashant Prabhakar Singh Oct 4 '16 at 5:21
  • sorry for the confuse but what I mean is the token balance instead ether balance. So the tx will pass without out of gas error – Roy Pun Oct 4 '16 at 5:26
  • updated the answer. Even if you have sufficient ethers in account but if you have less token in your account that what you are trying to transfer, you will get out-of-gas – Prashant Prabhakar Singh Oct 4 '16 at 5:37
  • that's wrong Prashant, they don't appear as out of gas, in etherscan at least – knocte Oct 4 '16 at 7:39
1

If a token conforms to the ERC: Token standard #20, a transfer of tokens will normally invoke a transfer(address _to, uint256 _amount) method.

Using The DAO as an example,

  • [1] contract TokenInterface's transfer(...) defines the following interface:

    function transfer(address _to, uint256 _amount) returns (bool success);
    
  • [2] contract Token is TokenInterface's transfer(...) implements the following method:

    function transfer(address _to, uint256 _amount) noEther returns (bool success) {
        if (balances[msg.sender] >= _amount && _amount > 0) {
            balances[msg.sender] -= _amount;
            balances[_to] += _amount;
            Transfer(msg.sender, _to, _amount);
            return true;
        } else {
           return false;
        }
    }
    
  • [3] contract DAO is DAOInterface, Token, TokenCreation's transfer(...) overrides the method in [2] above:

    function transfer(address _to, uint256 _value) returns (bool success) {
        if (isFueled
            && now > closingTime
            && !isBlocked(msg.sender)
            && transferPaidOut(msg.sender, _to, _value)
            && super.transfer(_to, _value)) {
    
            return true;
        } else {
            throw;
        }
    }
    

When you execute a The DAO transfer, you will be calling [3] above. [3] will call [2] which will perform the check on whether you have enough token balance to transfer. If you don't, then [2] will return false. When [3] receives a return status of false from [2], an error is thrown.

When an error is thrown, the Ethereum VM will consume gas equal to the amount of gas supplied. The only reliable way to check whether the transfer function has executed without any errors is to check the transaction status using the following logic as detailed in How can the transaction status from a thrown error be detected when gas can be exactly the same as the gasUsed for a successful transaction? :

> var status = debug.traceTransaction("{transaction id}")
undefined
> if (status.structLogs.length > 0) {
  console.log(status.structLogs[status.structLogs.length-1].error)
}
"invalid jump destination (PUSH1) 2"

If the result of the check above is "" then no error occurred and your transfer(...) call was successful. If the result is "Out of gas" or "invalid jump destination (PUSH1) 2", then an error occurred and your transfer(...) call failed.



Example Of A Failed The DAO Transfer(...)

From https://etherscan.io/tx/0xde5f71e76dc87bfcc16649c16f512a5b3086efd4cdaa31b8ee848a978b53b8b7

enter image description here

And here is the debug.traceTransaction(...) results run in geth:

> var status = debug.traceTransaction("0xde5f71e76dc87bfcc16649c16f512a5b3086efd4cdaa31b8ee848a978b53b8b7")
undefined
> if (status.structLogs.length > 0) {
...   console.log(status.structLogs[status.structLogs.length-1].error)
... }
invalid jump destination (PUSH1) 2
  • how about contracts that don't follow the standard and return false instead of throwing? how can we get the return value of the transfer function? – knocte Oct 4 '16 at 11:30
  • You can't get the return value of true or false from the function. If you don't want to use the thrown status, you will have to encode the error status into a state change in your contract. And then you provide a constant function to check the state change. The other alternative is to raise an event like TransferSuccessful or TransferFailure. But it's better to use the ERC20 standard and your token contract will operate with many front ends including the block explorers (e.g. etherscan.io/token-search) and wallets (e.g. myetherwallet.com/#send-tokens). – The Officious BokkyPooBah Oct 4 '16 at 11:32
  • mmm, I've found someone providing a way to get the return value: ethereum.stackexchange.com/questions/3285/… , is it wrong? – knocte Oct 5 '16 at 17:14
  • @knocte, the link you provided defines event VoteEvent(string ID, bool returnValue) and then logs VotedEvent(ID, true) if successful. This is similar to my suggested alternative 2 comments up where you can try logging the events TransferSuccessful or TransferFailure. It is not wrong at all. You will just have to create the watch filter to look for the events and match it with your transaction hash. – The Officious BokkyPooBah Oct 5 '16 at 22:29
0

All, I have encountered something similar - a successful transaction but without a change in balance of tokens and when I parity trace, the output is 0x0000000000000000000000000000000000000000000000000000000000000000

I did not get an 'out of gas' error. After thorough investigation it turns out that the reason for this is very simple - I attempted to send (by mistake, not deliberately) more Ethereum than I had. The transaction will consume gas(and therefore will not throw an 'out of gas' error) and fail silently - by returning a 'false' (see function below).

function transfer(address _to, uint _value) returns (bool success) {
    if (balances[msg.sender] >= _value 
        && _value > 0 
        && balances[_to] + _value > balances[_to]
        ) {
      balances[msg.sender] = safeSub(balances[msg.sender],_value);
      balances[_to] = safeAdd(balances[_to],_value);
      Transfer(msg.sender, _to, _value);
      return true;
    }
    else{
      return false;
    }

  }

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.