Skip to content

Gas cost disaccords between ethereumjs/evm and py-evm/geth #3236

@Alleysira

Description

@Alleysira

what happened

Hey, I'm running some contracts to test ethereumjs/evm's security with other evms. I used evm.runCall to run solc generated bytecodes. Then I found that when dealing with specific contract, the gas costs of ethereumjs/evm differ from geth and py-evm(while these two vms' results are the same).
I'd like to know whether this is sort of bug or I should change my script to test jsevm better.
Thinks in advance.

Environment

  • OS: Ubuntu 20.04
  • solc 0.8.23
  • node-v20.9.0-linux-x64
  • @ethereumjs/evm@2.1.0
  • @ethereumjs/util@9.0.1
  • @ethereumjs/common@4.1.0
  • @ethereumjs/blockchain@7.0.1
  • @ethereumjs/statemanager@2.1.0
  • memory-level@1.0.0

Here is my contract in solidity and the corresponding bytecode: src and bin.zip

My test script

runcode.js is the script I used to run bytecodes on ethereumjs

const { Account,Address,hexToBytes,bytesToHex } =require('@ethereumjs/util')
const { EVM } =require('@ethereumjs/evm')
const { Chain, Common, Hardfork,ConsensusType,ConsensusAlgorithm  } =require('@ethereumjs/common')
const {Blockchain} = require('@ethereumjs/blockchain')
const { DefaultStateManager } =require('@ethereumjs/statemanager')
const { MemoryLevel } = require( 'memory-level')
const fs = require('fs');
const yargs = require('yargs')
yargs.option('code',{type:"string",demandOption:true})
     .option('sig',{type:"string",demandOption:true})
var argv = yargs.argv
var code = '0x00'
var sig=argv.sig
// note that code has 0x prefix but sig doesn't
if (argv.code!=true){
    code='0x'+argv.code
}

function uint8ArrayToHexString(uint8Array) {

    let hexString = Array.from(uint8Array)
      .map(byte => byte.toString(16).padStart(2, '0'))
      .join('');

    return hexString;
}

async function runEvmRunCall (){
    const common = Common.custom({
        chainId: 1234,
        networkId: 1234,
        defaultHardfork: Hardfork.Shanghai,
        consensus: {
          type: ConsensusType.ProofOfStake,
          algorithm: ConsensusAlgorithm.Casper,
        },
        genesis: {
          gasLimit: 10000000000,
          difficulty: 1,
          nonce: '0x0000000000000000',
          extraData: '0x0',
        },
        comment: 'Custom empty chain for benchmarks',
        bootstrapNodes: [],
        dnsNetworks: [],
      })
    const db = new MemoryLevel() 
    const blockchain = await Blockchain.create(common,db)
    const stateManager = new DefaultStateManager()
    const evm = new EVM({ common,stateManager,blockchain })
    evm.DEBUG=true
    const contractCode = hexToBytes(code) 
    const contractAddress = Address.fromString('0x000000000000000000000000636F6E7472616374')
    await evm.stateManager.putContractCode(contractAddress, contractCode)
    evm.events.on('step', function (data) {

        let hexStack = []
        hexStack = data.stack.map(item => {
            return '0x' + item.toString(16)
        })

        let hexMemory = '0x'
        for (let i=0;i<data.memory.length;i++){
            hexMemory += data.memory[i]
        }
        var opTrace = {
            'pc': data.pc,
            'gas': '0x' + data.gasLeft.toString(16),
            'gasCost': '0x' + data.opcode.fee.toString(16),
            'memory': hexMemory,
            'memsize': data.memoryWordCount.toString(16),
            'stack': hexStack,
            'depth': data.depth,
            'opName': data.opcode.name
        }
        opTrace_json = JSON.stringify(opTrace)
        console.log(opTrace_json)
      })
      if(sig==undefined){
        try{
        const results = await evm.runCall({
            code: hexToBytes(code),
            gasLimit: BigInt('0x'+'ffff'),
            to: contractAddress
        }).catch(console.error);
        var ret = {
            'output':uint8ArrayToHexString(results.execResult.returnValue),
            'gasUsed':'0x'+results.execResult.executionGasUsed.toString(16),
            'error':results.execResult.exceptionError
        }
        ret_json = JSON.stringify(ret)
        console.log(ret_json)
        }
        catch(err){
            console.log(err)
        }
    }
    // sig in defined
        else {
            sig=sig.toString(16)
            if(sig.charAt(0)!= "0" && sig.charAt(1)!= "x"){
                sig='0x'+sig
            }
            const results = await evm.runCall({
                gasLimit: BigInt('0x'+'ffffff'),
                data: hexToBytes(sig),
                to: contractAddress,
                caller: new Address(hexToBytes("0x1c7cd2d37ffd63856a5bd56a9af1643f2bcf545f"))
            });
            var ret = {
                'output':uint8ArrayToHexString(results.execResult.returnValue),
                'gasUsed':'0x'+results.execResult.executionGasUsed.toString(16),
                'error':results.execResult.exceptionError
            }
            ret_json = JSON.stringify(ret)
            console.log(ret_json)
        }
}

runEvmRunCall()

Then I use this python script to call runcode.js, the command is python3 poc.py --sig 0x22ea223100000000000000000000000042a39d51fc07bb9c181a0b62df834575cb3b1aa40000000000000000000000000000000000000000000000000000000054c1f8e0 --code poc/D223ICO.bin-runtime

import subprocess
import argparse

def parse_args():
    """
    Parse input arguments
    """
    parser = argparse.ArgumentParser(description='Test a transaction')

    parser.add_argument('--code', dest='code', default='./poc/xxx.bin-runtime', type=str)
    # function signature bytecode
    parser.add_argument('--sig', dest='signature', default='0x22ea223100000000000000000000000042a39d51fc07bb9c181a0b62df834575cb3b1aa40000000000000000000000000000000000000000000000000000000054c1f8e0', type=str)

    args = parser.parse_args()
    return args

PROJECT_DIR = "/home/alleysira/project" 
args = parse_args()
codefile = open(args.code, "r")
bincode = codefile.read()
codefile.close()
sigName = args.signature
retcode = subprocess.call("node " + PROJECT_DIR + "/poc/runcode.js --code " + bincode + " --sig " + sigName + " > " + PROJECT_DIR + "/poc/jsout.json",shell=True)

The result will be added in the json file. In a similar way, I collected the results from geth and py-evm, then I found the gas cost of jsevm is 0x4a3a, which is different from 0x4076 of both geth and py-evm.
Here is the corresponding json file of 3 evms. gethout.json, jsout.json, pyout.json

Please enlighten me, thanks.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions