Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Including “from” in call returns incorrect data [fyi: infura/geth issue] #705

Closed
gitpusha opened this issue Jan 15, 2020 · 25 comments
Closed
Labels
discussion Questions, feedback and general information.

Comments

@gitpusha
Copy link

gitpusha commented Jan 15, 2020

Hi,
I am sorry for this vague issue description, but this bug I have been seeing recently is really confusing to me:

Basically, I have one solidity contract function that should return my current ETH balance or ERC20 balance using "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" for ETH.

    function getTriggerValue(address _account, address _coin, uint256, bool)
        external
        view
        returns(uint256)
    {
        if (_coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
            return _account.balance;
        IERC20 erc20 = IERC20(_coin);
        return erc20.balanceOf(_account);
    } 

Now the bug only happens if I call this function with coin="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" to query my own ETH balance.

So it does work for my account if I use any ERC20 address , but not our hardcoded ETH signal.

My Account: 0x203AdbbA2402a36C202F207caA8ce81f1A4c7a72

The BUG:
I always get a nonsense value returned if I use my account address and make a call to the function from ethers.js using the following code

image

The Nonesense ETH Balance:

image

I also cross-checked this on Remix for my account and the result is the same nonsense:

image

What confuses me about this bug:

As soon as I use the exactly same code in ethers but just a different account address (I tried my friend's) everything works as expected and I got the correct result for his account balance.

I also cross-checked on Remix and the result is correct:

image

This looks too weird for me to dig further, so I wanted to bring it to your attention. I believe Remix uses ethers.js too, which might explain why my results match 1:1 between the two environments.

Maybe you can help?

If you wanna give it a try, here is the stuff you need:

Solidity Interface to function I get the bug for:

pragma solidity ^0.6.0;

interface TriggerBalance {
    function getTriggerValue(address _account, address _coin, uint256, bool)
        external
        view
        returns(uint256);
}

Deployment address of TriggerBalance on ropsten: 0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030

My EOA address where I find the bug: 0x203AdbbA2402a36C202F207caA8ce81f1A4c7a72

Try maybe with your own EOA address and see if you get the bug or not.

Otherwise here is my friend's address where the bug does not occur, oddly: 0xe2A8950bC498e19457BE5bBe2C25bC1f535C743e

@ricmoo ricmoo added the investigate Under investigation and may be a bug. label Jan 16, 2020
@ricmoo
Copy link
Member

ricmoo commented Jan 16, 2020

What version are you using? I've tried your above example in both v4 and v5 and seem to get the correct result. Here is my code:

const { ethers } = require("ethers");

const provider = ethers.getDefaultProvider("ropsten");

const abi = [ "function getTriggerValue(address _account, address _coin, uint256, bool) view returns(uint256)" ]

// A provided bad address that causes problems
const bad = "0x203AdbbA2402a36C202F207caA8ce81f1A4c7a72"

// A provided good address that behaves 
const good = "0xe2A8950bC498e19457BE5bBe2C25bC1f535C743e"

// The trigger contract (although, the zero address would prolly make more
//  sense, for the sake of testing and using the existing contract, we use
// this trigger; for future reference, ethers.constants.AddressZero can be
// used)
const trigger = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"

const c = new ethers.Contract("0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030", abi, provider);

(async function() {
    let result;

    result = await c.getTriggerValue(good, trigger, 0, 0)
    console.log("GOOD", result.toString(), ethers.utils.formatUnits(result, 18));

    result = await c.getTriggerValue(bad, trigger, 0, 0)
    console.log("BAD", result.toString(), ethers.utils.formatUnits(result, 18));
})();

The output (which looks fairly sane) I get is:

GOOD 1336115633645982815 1.336115633645982815
BAD 23163511897856121193 23.163511897856121193

Could it be that in an earlier version of your code it was pointing to another contract address?

@gitpusha
Copy link
Author

Hi @ricmoo - thanks for looking into this and thanks for your example code.

I noticed the following for me. When I run my buidler task, which wraps my ethers.js script (buidler-ethers plugin to be precise) , I get GOOD results on Kovan:

image

and for the exact same code (solidity and Javascript) on --network ropsten I get the nonsense result:

image

HOWEVER: when I use your ethers example script, I also get the GOOD result on ropsten.

So I assume this must have something to do with the way buidler runs their ethers-plugin?

@ricmoo
Copy link
Member

ricmoo commented Jan 22, 2020

Oh. Maybe? I don’t know buidler. What does it do? Maybe they are wrapping the Contract API and doing something a bit odd with it?

@ricmoo ricmoo added discussion Questions, feedback and general information. and removed investigate Under investigation and may be a bug. labels Jan 22, 2020
@ricmoo
Copy link
Member

ricmoo commented Jan 22, 2020

(I’m also unfamiliar with ethers-plugin... must be their custom thing)

@gitpusha
Copy link
Author

gitpusha commented Jan 22, 2020

Hi @ricmoo I have informed the buidler team about this issue. I will keep on investigating with them. There is no need for you to help any further, since the pure ethers.js script works. I just opened a new issue with regards to a different bug I found in one of my ethers scripts. Would appreciate your help there too. It has to do with a bug I got with ethers parsing logs and UTF-8 stuff 😉

@gitpusha
Copy link
Author

Oh. Maybe? I don’t know buidler. What does it do? Maybe they are wrapping the Contract API and doing something a bit odd with it?

I believe the buidler-ethers plugin does wrap the ethers contract API.
https://github.com/nomiclabs/buidler/tree/master/packages/buidler-ethers

@gitpusha
Copy link
Author

gitpusha commented Jan 30, 2020

Hi @ricmoo . I am reopening this because we have seen this bug reoccur on multiple occasions for different wallet addresses and on different networks. We have a guess as to what be the source of the bug:

The problem seems to occur (sometimes) when querying the ETH balance of an address from a contract, like in the example I included above, AND you instantiated the ethers Contract with a signer-provider, whose wallet.address (in my case the wallet is generated fromMnemonic), is THE SAME ADDRESS as the one that you are querying the ETH balance for.

We tested this against the buggy case. As soon as we switched to ethers Default Provider , we got the correct result back, all other code unchanged. Just the provider with which we instantiated the contract changed between the tests.

I am not sure if you can reproduce this. But you might wanna try the test script you posted above and instantiate the Contract with a signer-provider and then query your own ETH balance of your signer's wallet.address.

@gitpusha gitpusha reopened this Jan 30, 2020
@ricmoo ricmoo added the investigate Under investigation and may be a bug. label Feb 4, 2020
@ricmoo
Copy link
Member

ricmoo commented Feb 4, 2020

What do you mean by a signer-provider? I'll look into this again, but wasn't able to reproduce it last time. Have you had any luck reproducing this reliably?

@gitpusha
Copy link
Author

gitpusha commented Feb 6, 2020

Hi @ricmoo - this is how you can reproduce:

const ethers = require("ethers");

require("dotenv").config();
const INFURA_ID = process.env.INFURA_ID;
const DEV_MNEMONIC = process.env.DEV_MNEMONIC;

const provider = new ethers.providers.InfuraProvider("ropsten", INFURA_ID);
const signer = ethers.Wallet.fromMnemonic(DEV_MNEMONIC);
const signerProvider = signer.connect(provider);

const abi = [
  "function getTriggerValue(address _account, address _coin, uint256, bool) view returns(uint256)"
];

// A bug occurs if the address we query for is also our signerProvider
const bug = signerProvider.address;

// If the address we query is any but our signerProvider, it should work
const good = "0xe2A8950bC498e19457BE5bBe2C25bC1f535C743e";

// The trigger contract (although, the zero address would prolly make more
//  sense, for the sake of testing and using the existing contract, we use
// this trigger; for future reference, ethers.constants.AddressZero can be
// used)
const coin = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";

const c = new ethers.Contract(
  "0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030",
  abi,
  signerProvider
);

(async function() {
  let result;

  result = await c.getTriggerValue(good, coin, 0, 0);
  console.log("GOOD", result.toString(), ethers.utils.formatUnits(result, 18));

  result = await c.getTriggerValue(bug, coin, 0, 0);
  console.log("BUG", result.toString(), ethers.utils.formatUnits(result, 18));
})();

image

@ricmoo
Copy link
Member

ricmoo commented Feb 7, 2020

@gitpusha I have been able to reproduce it, but have simplified it even further to the point where the only different is whether a from is included in the call.

Can you include the Contract source? It is incredibly weird... The decompiled code I have been able to pull apart and reading the byte code a few things don't really add up; the selector that matches the function doesn't seem to match the code that is expected to be running...

@ricmoo
Copy link
Member

ricmoo commented Feb 7, 2020

I do not think this is an ethers issue, but there is only one CALLER opcode in your contract (and it's unreachable), so I don't know how your contract could be causing this... Unless the DELEGATECALL is using CALLER or ORIGIN or the static call is using ORIGIN.

The only different in these two requests (on the command line) is that I pasted a from into the second.

Without a from: (works)

/home/ethers> curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://ropsten.infura.io/v3/7d0d81d0919f4f05b9ab6634be01ee73

{"jsonrpc":"2.0","id":43,"result":"0x000000000000000000000000000000000000000000000000128ad616fa50945f"

With a from: (explodes)

/home/ethers> curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"from":"0xe2a8950bc498e19457be5bbe2c25bc1f535c743e","to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://ropsten.infura.io/v3/7d0d81d0919f4f05b9ab6634be01ee73

{"jsonrpc":"2.0","id":43,"result":"0xfffffffffffffffffffffffffffffffffffffffffffffffffee3c828fc9f2bff"}

It looks like it is sign extending -80000290000000001... Or slicing something in memory wrong?

@ricmoo ricmoo removed the investigate Under investigation and may be a bug. label Feb 7, 2020
@gitpusha
Copy link
Author

gitpusha commented Feb 7, 2020

@gitpusha I have been able to reproduce it, but have simplified it even further to the point where the only different is whether a from is included in the call.

Can you include the Contract source? It is incredibly weird... The decompiled code I have been able to pull apart and reading the byte code a few things don't really add up; the selector that matches the function doesn't seem to match the code that is expected to be running...

Here is the source

UPDATE: the current source has a different function signature:

    function reached(
        address _account,
        address _coin,
        uint256 _refBalance,
        bool _greaterElseSmaller
    )
        external
        view
        returns(bool, uint8)  // executable?, reason

But the error is pretty much the as before.

@gitpusha
Copy link
Author

gitpusha commented Feb 7, 2020

I do not think this is an ethers issue, but there is only one CALLER opcode in your contract (and it's unreachable), so I don't know how your contract could be causing this... Unless the DELEGATECALL is using CALLER or ORIGIN or the static call is using ORIGIN.

The only different in these two requests (on the command line) is that I pasted a from into the second.

Without a from: (works)

/home/ethers> curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://ropsten.infura.io/v3/7d0d81d0919f4f05b9ab6634be01ee73

{"jsonrpc":"2.0","id":43,"result":"0x000000000000000000000000000000000000000000000000128ad616fa50945f"

With a from: (explodes)

/home/ethers> curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"from":"0xe2a8950bc498e19457be5bbe2c25bc1f535c743e","to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://ropsten.infura.io/v3/7d0d81d0919f4f05b9ab6634be01ee73

{"jsonrpc":"2.0","id":43,"result":"0xfffffffffffffffffffffffffffffffffffffffffffffffffee3c828fc9f2bff"}

It looks like it is sign extending -80000290000000001... Or slicing something in memory wrong?

I am sorry, I am a bit out of my depth here.

@ricmoo
Copy link
Member

ricmoo commented Feb 7, 2020

Basically, if I send you contract a from address (not in ethers, just in general to an normal INFURA node) your contract goes a little crazy. :)

But I've examined the bytecode, and it doesn't seem like it should go haywire. But there are some very strange things happening in the code. Is there any assembly?

The source you shared I think is for the wrong file, there is no getTriggerValue(address, address,uint256,bool) in it.

@gitpusha
Copy link
Author

gitpusha commented Feb 7, 2020

Basically, if I send you contract a from address (not in ethers, just in general to an normal INFURA node) your contract goes a little crazy. :)

But I've examined the bytecode, and it doesn't seem like it should go haywire. But there are some very strange things happening in the code. Is there any assembly?

The source you shared I think is for the wrong file, there is no getTriggerValue(address, address,uint256,bool) in it.

god you are fast. yes the source was wrong. I just edited the reply. but we also updated the source in the meantime. the same error occured for the new source.

There is no assembly.

We also noticed that the bug does not occur in solidity.

It only occurs when we call it from Javascript API.

@ricmoo
Copy link
Member

ricmoo commented Feb 7, 2020

If you could verify your code on Etherscan (rooster is fine) it would go a long way to helping debug this.

My current thought is that there is some use of DELEGATECALL between incompatible contracts...

If you paste this into your terminal, you will see it fail the same way. No JavaScript API. Just pure vanilla calling an Ethereum node directly and asking for a result:

curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"from":"0xe2a8950bc498e19457be5bbe2c25bc1f535c743e","to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://ropsten.infura.io/v3/7d0d81d0919f4f05b9ab6634be01ee73

I had that written, but tried one more thing... I didn't use INFURA, and instead queried Alchemy. It worked!

curl -H "Accept: application/json" -X POST --data-raw '{"method":"eth_call","params":[{"from":"0xe2a8950bc498e19457be5bbe2c25bc1f535c743e","to":"0xaf4c11A90e98D0C5ecFb403C62Cc8Dfe8DF11030","data":"0xb7a56985000000000000000000000000e2a8950bc498e19457be5bbe2c25bc1f535c743e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":43,"jsonrpc":"2.0"}' https://eth-ropsten.alchemyapi.io/jsonrpc/_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC

So, it seems there is something wrong on INFURA's end. I'll reach out to them and see what they think. :)

In the meantime, I guess use the AlchemyProvider instead of the InfuraProvider?

@gitpusha
Copy link
Author

gitpusha commented Feb 7, 2020

thank you for your effort and for working your magic.

I didnt spot the "AlchemyProvider" in the docs yet?

Also, we use Growth Tier with Infura for play.gelato.finance

Shameless plug: try it out! it is IFTTT for Ethereum 👍

@egalano
Copy link

egalano commented Feb 7, 2020

Hey there @gitpusha . Thanks for using Infura. Richard reached out to us and after looking into this with our engineering team it appears to be a bug in the client version we run which is geth-1.9. I'm going to report it upstream to the go-ethereum team and see what they say.

@ricmoo ricmoo changed the title Bug - maybe something to do with how addresses are encoded? Including “from” in call returns incorrect data [fyi: infura/geth issue] Feb 8, 2020
@ryanschneider
Copy link

@gitpusha any chance your contract is also deployed to goerli? I'd like to debug what's going on in geth but don't have a ropsten environment setup.

@gitpusha
Copy link
Author

Hi @egalano thanks for stepping in.

@ryanschneider here is the mainnet address and verified contract:
https://etherscan.io/address/0x60621bf3f7132838b27972084eaa56e87395d44b#code

Since this is a view only function I assume mainnet is good for your tests?

@ligi
Copy link

ligi commented Feb 17, 2020

I'm going to report it upstream to the go-ethereum team and see what they say.

@egalano did you report upstream?

@egalano
Copy link

egalano commented Feb 17, 2020

Thanks for the ping ligi: ethereum/go-ethereum#20685

@gitpusha
Copy link
Author

gitpusha commented Feb 18, 2020

@ryanschneider I forgot to mention that on the deployed mainnet instance the function sig changed to:

function getConditionValue(address _account, address _coin, uint256, bool)
   external
   view
   returns(uint256)

But it does the same.

The bug is also not specific to this function only. It has to do with querying the account balance as returned by a smart contract returning account.balance from ethers.js with an Infura Provider and Signer setup.

@ricmoo
Copy link
Member

ricmoo commented Apr 22, 2020

It looks like this has been resolved on both INFURA and Geth's side, according to their related tickets, so I'm closing this here too.

If it is still a problem, please let me know and we can ping whomever needs pinging. :)

Thanks! :)

@ricmoo ricmoo closed this as completed Apr 22, 2020
@gitpusha
Copy link
Author

Ok @ricmoo , will let you know should I encounter the same problem again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information.
Projects
None yet
Development

No branches or pull requests

5 participants