bnb-chain / bsc

A BNB Smart Chain client based on the go-ethereum fork
GNU Lesser General Public License v3.0
2.73k stars 1.56k forks source link

Performing eth_call with override state set #656

Closed toriqahmads closed 2 years ago

toriqahmads commented 2 years ago

Hi, I want performing an eth_call with overriding state such as balance, code, nonce, state and stateDiff. I was called with overriding balance of some account and it works fine. But, if I override the code of some account/contract it always return an error execution reverted. Does BSC doesn't support this perform?

Here is my testing parameter

keefel commented 2 years ago

From the code, I think bsc can support this. As you rewrite the code, so the method in the data field may have changed, I will suggest you to rewrite code first, and then send transaction after.

RumeelHussainbnb commented 2 years ago

We have responded to the question and will proceed to close the case as we didn't get any additional question after 3days. Please proceed to join our Discord channel for more discussion at https://discord.com/invite/buildnbuild

alex88 commented 2 years ago

Just to add more info, I have the same problem and this is what I do from ether.js:

import { hexValue, parseEther } from "ethers/lib/utils";
import { ethers } from "hardhat";

const PLACEHOLDER_ADDRESS = "0xae1A331eaAF7b9a44005035588C2542B0Cfe9FCf";

async function main() {
  const provider = new ethers.providers.JsonRpcProvider(
    "http://localhost:10000"
  );
  const MyContract = await ethers.getContractFactory("MyContract");
  const data = MyContract.interface.encodeFunctionData("tryCall");

  const params = [
    {
      from: "0xCC4B9142788B8D41FF908EE19940cbc3fD9fDfC4", // Source wallet
      to: PLACEHOLDER_ADDRESS, // Fake contract address
      value: hexValue(parseEther("0.01")),
      data,
    },
    "latest",
    {
      [PLACEHOLDER_ADDRESS]: {
        balance: hexValue(parseEther("1.0")),
        code: MyContract.bytecode,
      },
    },
  ];

  const result = await provider.send("eth_call", params);

  console.log(result);
  console.log(MyContract.interface.decodeFunctionResult("tryCall", result));
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

so the data should be correct. The function code is simply

    function tryCall() public view returns (uint256) {
        uint256 x = 1;
        return x; // x has value 2
    }

logs don't help either:

Served eth_call                          conn=127.0.0.1:39912 reqid=43 t=1.759014ms err="execution reverted"

Update: as pointed out in discord I've also tried without setting a value and with a 0 value, same error or CALL_EXCEPTION

alex88 commented 2 years ago

Just to give a full example, create a new hardhat project, have a node running at localhost:1000

this is the contract

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

contract TestContract {
    function tryCall() public pure returns (uint256 value) {
        uint256 x = 1;
        return x;
    }
}

this is the script code

import { ethers } from "hardhat";

const PLACEHOLDER_ADDRESS = "0xae1A331eaAF7b9a44005035588C2542B0Cfe9FCf";

async function main() {
  // We get the contract to deploy
  const provider = new ethers.providers.JsonRpcProvider(
    "http://localhost:10000"
  );
  const TestContract = await ethers.getContractFactory("TestContract");
  const data = TestContract.interface.encodeFunctionData("tryCall");
  const params = [
    {
      to: PLACEHOLDER_ADDRESS,
      data,
    },
    "latest",
    {
      [PLACEHOLDER_ADDRESS]: {
        code: TestContract.bytecode,
      },
    },
  ];

  console.log(params);

  const result = await provider.send("eth_call", params);

  console.log(result);
  console.log(TestContract.interface.decodeFunctionResult("tryCall", result));
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

this is the output:

[
  {
    to: '0xae1A331eaAF7b9a44005035588C2542B0Cfe9FCf',
    data: '0x118dfb0e'
  },
  'latest',
  {
    '0xae1A331eaAF7b9a44005035588C2542B0Cfe9FCf': {
      code: '0x608060405234801561001057600080fd5b5060bb8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063118dfb0e14602d575b600080fd5b60336047565b604051603e91906062565b60405180910390f35b600080600190508091505090565b605c81607b565b82525050565b6000602082019050607560008301846055565b92915050565b600081905091905056fea26469706673582212206697fcd9141a9315a2c909d6245d783781cf57770fdb8fdd356e879ad0a0980b64736f6c63430008040033'
    }
  }
]
0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063118dfb0e14602d575b600080fd5b60336047565b604051603e91906062565b60405180910390f35b600080600190508091505090565b605c81607b565b82525050565b6000602082019050607560008301846055565b92915050565b600081905091905056fea26469706673582212206697fcd9141a9315a2c909d6245d783781cf57770fdb8fdd356e879ad0a0980b64736f6c63430008040033
Error: call revert exception (method="tryCall()", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.5.0)
    at Logger.makeError (/home/alex/Projects/personal/node_modules/@ethersproject/logger/src.ts/index.ts:225:28)
    at Logger.throwError (/home/alex/Projects/personal/node_modules/@ethersproject/logger/src.ts/index.ts:237:20)
    at Interface.decodeFunctionResult (/home/alex/Projects/personal/node_modules/@ethersproject/abi/src.ts/interface.ts:425:23)
    at main (/home/alex/Projects/personal/scripts/test.ts:37:38)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  reason: null,
  code: 'CALL_EXCEPTION',
  method: 'tryCall()',
  errorArgs: null,
  errorName: null,
  errorSignature: null
}

it's interesting how the returned value seems to be very close to the contract bytecode

Update: that's the init bytecode, after using the runtime bytecode it seems to work, I'll update this in a few

Update: it seems to work but as soon there's an error somewhere I get execution reverted and no error other than that :(

Arinerron commented 2 years ago

@alex88 how'd you go about fixing this? I'm having the same trouble where it looks very similar to the contract bytecode.

alex88 commented 2 years ago

@alex88 how'd you go about fixing this? I'm having the same trouble where it looks very similar to the contract bytecode.

you should use the runtime bytecode, I'm not sure how to take it from ethers.js but it's definitely in the json files

IronSight87 commented 2 years ago

@alex88 Did you find a solution? Below my example. May you help me? Cheers

contract

interface IERC20 {
    function balanceOf(address) external view returns (uint);
    function approve(address, uint) external returns (bool);
}

interface IUniswapRouter {
    function WETH() external view returns(address);
    function swapExactETHForTokens(uint, address[] calldata, address, uint) external payable returns (uint[] memory);
    function swapExactTokensForETH(uint, uint, address[] calldata, address, uint) external returns (uint[] memory);
}

contract Audit {

    address constant public ROUTER_ADDRESS = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    uint256 constant public MAX_BUY_TAX = 10;
    uint256 constant public MAX_SELL_TAX = 10;

    function isHoneypot(address token) external returns(bool) {

        uint256 amount = address(this).balance;

        address[] memory path = new address[](2);
        path[0] = IUniswapRouter(ROUTER_ADDRESS).WETH();
        path[1] = token;

        // save token balance before swap to ignore any scrap balance
        uint256 preBalance0 = IERC20(path[1]).balanceOf(address(this));
        uint256[] memory amounts0 = IUniswapRouter(ROUTER_ADDRESS).swapExactETHForTokens{value: amount}(0, path, address(this), block.timestamp * 15);
        uint256 postBalance0 = IERC20(path[1]).balanceOf(address(this)) - preBalance0;

        if ((amounts0[1] - postBalance0) > (amounts0[1] * MAX_BUY_TAX / 100)) {
            return true;
        }

        path[1] = path[0];
        path[0] = token;

        IERC20(path[0]).approve(router, postBalance0);
        uint256[] memory amounts1 = IUniswapRouter(ROUTER_ADDRESS).swapExactTokensForETH(postBalance0, 0, path, address(this), block.timestamp * 15);
        uint256 balance1 = address(this).balance;

        if ((amounts1[1] - balance1) > (amounts1[1] * MAX_SELL_TAX / 100)) {
            return true;
        }

        return false;
    }
}

script

const provider = new ethers.providers.JsonRpcProvider("URL");
const AuditContract = await ethers.getContractFactory("Audit");
const data = AuditContract.interface.encodeFunctionData("isHoneypot", [token]);
const result = await provider.send("eth_call", [
    {
        data: data,
        from: '0x0000000000000000000000000000000000000124',
        to: '0x0000000000000000000000000000000000000125',
        gas: 1000000
    },
    'latest',
    {
        '0x0000000000000000000000000000000000000124': {
            balance: ethers.parseEther('1').toHexString()
        },
        '0x0000000000000000000000000000000000000125': {
            code: AuditContract.bytecode,
            balance: ethers.parseEther('1').toHexString()
        },

    }
]);
const value = AuditContract.interface.decodeFunctionData("isHoneypot", result);
console.log(value);
alex88 commented 2 years ago

@IronSight87 AuditContract.bytecode is probably the deploy bytecode not the runtime one, check for that

IronSight87 commented 2 years ago

@IronSight87 AuditContract.bytecode is probably the deploy bytecode not the runtime one, check for that

Thanks mate. Fixed it!