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

The Nonesense ETH Balance:

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

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:

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?
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
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?
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:

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

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?
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’m also unfamiliar with ethers-plugin... must be their custom thing)
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 😉
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
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.
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?
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));
})();

@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...
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?
@gitpusha I have been able to reproduce it, but have simplified it even further to the point where the only different is whether a
fromis 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.
I do not think this is an ethers issue, but there is only one
CALLERopcode in your contract (and it's unreachable), so I don't know how your contract could be causing this... Unless theDELEGATECALLis usingCALLERorORIGINor the static call is usingORIGIN.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.
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.
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.
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?
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 👍
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.
@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.
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?
I'm going to report it upstream to the go-ethereum team and see what they say.
@egalano did you report upstream?
Thanks for the ping ligi: https://github.com/ethereum/go-ethereum/issues/20685
@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.
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! :)
Ok @ricmoo , will let you know should I encounter the same problem again.
Most helpful comment
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.