4

I've made two functions to interact with a local ERC20 token. Here they are:

pragma solidity ^0.5.0;

contract GardenTestContract {

    function getBalanceOf(address _address) public view returns (bool, uint, uint, address) {
        address sender = msg.sender;
        (bool _success, bytes memory data) = _address.staticcall(abi.encode(bytes4(keccak256('balanceOf(address)')), (sender)));
        (uint amount) = abi.decode(data, (uint));
        return (_success, data.length, amount, sender);
    }

    function getTotalSupply(address _address) public view returns (uint) {
        (bool _success2, bytes memory data) = _address.staticcall(abi.encode(bytes4(keccak256("totalSupply()"))));
        (uint amount) = abi.decode(data, (uint));
        return amount;
    }

}

The address I'm passing to these functions is the address of the ERC20 token which is implemented like this:

pragma solidity ^0.5.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20Detailed.sol";

/**
 * @title GardenTestToken
 * @dev Simple ERC20 Token example yielding 10000 tokens pre-assigned to its creator.
 */
contract GardenTestToken is ERC20, ERC20Detailed {
    // modify token name
    string public constant NAME = "GardenTestToken";
    // modify token symbol
    string public constant SYMBOL = "GTT";
    // modify token decimals
    uint8 public constant DECIMALS = 18;
    // modify initial token supply
    uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** uint256(DECIMALS)); // 10000 tokens

    /**
     * @dev Constructor that gives msg.sender all of existing tokens.
     */
    constructor () public ERC20Detailed(NAME, SYMBOL, DECIMALS) {
        _mint(msg.sender, INITIAL_SUPPLY);
    }
}

I'm working in remix, which is why the github import works as far as i know.

When i call getTotalSupply i get back the correct number of total supply (the INITIAL_SUPPLY value from the token contract). When i call getBalanceOf, things get confusing. I dont get the balance of the address that I'm calling from, but i just get 0. The decoded output from the remix console looks like this:

{
    "0": "bool: true",
    "1": "uint256: 32",
    "2": "uint256: 0",
    "3": "address: 0xf348328786984162e70f08d54a272427D810124b"
}

If i copy the address in this output into the remix GUI belonging the my deployed ERC20 contract, I get back the correct balance, and i do not know why or how this could be.

My remix is running against a local ganache chain through metamask and I'm compiling with 5.13. It also doesn't with the JavaScript VM.

I'm at a loss, any suggestions are welcome.

8
  • Side note: I'm pretty sure that you should do uint256(10) ** DECIMALS instead of 10 ** uint256(DECIMALS). Also, instead of multiplying by 10000, you could use (DECIMALS + 4). Lastly, you should use small letters for those 3 constants (name, symbol and decimals), otherwise you contract is not conforming to the ERC20 standards, and applications will not display your contract details (including details of transactions executed on your contract) correctly. Commented Nov 22, 2019 at 6:27
  • Also, I believe that you should change abi.encode(bytes4(keccak256("balanceOf(address)")) to abi.encodeWithSelector(bytes4(uint256(keccak256("balanceOf(address)") >> (256 - 4 * 8))), though I'm not entirely sure about this. Commented Nov 22, 2019 at 7:01
  • Also, I'm not sure what yaya means in your code, but I believe that you should be using sender there. Commented Nov 22, 2019 at 7:11
  • You are not passing _address to balancOf in your static call. Commented Nov 22, 2019 at 7:32
  • @MikhailVladimirov no, im calling balanceOf on _address with a static call.
    – Johan
    Commented Nov 22, 2019 at 8:35

2 Answers 2

1
abi.encode(bytes4(keccak256('balanceOf(address)')), (sender))

Should be:

abi.encodeWithSelector(bytes4(keccak256(bytes("balanceOf(address)"))), sender)

or, which is the same

abi.encodeWithSignature("balanceOf(address)", sender)

Function abi.encode pads all arguments, including the first one, to 32-bytes. In your case 4-bytes function selector was padded to 32-bytes effectively shifting the next argument to the right by 28 bytes. Thus method balanceOf didn't find its argument.

Method totalSupply does not have arguments, that's why it worked.

Expression abi.encodeWithSignature("balanceOf(address)", 0x0123456789012345678901234567890123456789) evaluates to

0x70a08231
  0000000000000000000000000123456789012345678901234567890123456789

while abi.encode(bytes4(keccak256(bytes("balanceOf(address)"))), 0x0123456789012345678901234567890123456789) evaluates to

0x70a0823100000000000000000000000000000000000000000000000000000000
  0000000000000000000000000123456789012345678901234567890123456789

See documentation for details:

0

I believe that you should change this:

abi.encode(bytes4(keccak256("balanceOf(address)"))

To this:

abi.encodeWithSelector(bytes4(uint256(keccak256("balanceOf(address)") >> (256 - 4 * 8)))

The shift-right operation is in order to extract the (32-bit) function selector out of the (256-bit) hash generated by the keccak256 built-in function.

You can find more details about Function Selector in the official documenation.

3
  • I cant seem to get the bitshifting thing to work, but just changing from abi.encode() to abi.encodeWithSelector worked for me
    – Johan
    Commented Nov 22, 2019 at 11:33
  • @Johan: What do you mean "can't get it to work"? Do you get a compilation error? Have you followed my description precisely (for example, have you added the cast to uint256, which is not present in your code)? Commented Nov 22, 2019 at 11:37
  • I get a "TypeError: Explicit type conversion not allowed from "uint256" to "bytes4"."
    – Johan
    Commented Nov 22, 2019 at 11:51

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.