diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go
index 2e57d725891b5d17380a3ff3de956bc0f8c04819..d110af2c30aeccb30c09e2efe37d0277ea266aaa 100644
--- a/cmd/evm/internal/t8ntool/flags.go
+++ b/cmd/evm/internal/t8ntool/flags.go
@@ -38,6 +38,10 @@ var (
 		Name:  "trace.nostack",
 		Usage: "Disable stack output in traces",
 	}
+	TraceDisableReturnDataFlag = cli.BoolFlag{
+		Name:  "trace.noreturndata",
+		Usage: "Disable return data output in traces",
+	}
 	OutputAllocFlag = cli.StringFlag{
 		Name: "output.alloc",
 		Usage: "Determines where to put the `alloc` of the post-state.\n" +
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index a4908d763ea0efdf90ad244c5c6496311f49364a..079307b97502763194320239d278bf8e974a2b6a 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -83,9 +83,10 @@ func Main(ctx *cli.Context) error {
 	if ctx.Bool(TraceFlag.Name) {
 		// Configure the EVM logger
 		logConfig := &vm.LogConfig{
-			DisableStack:  ctx.Bool(TraceDisableStackFlag.Name),
-			DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
-			Debug:         true,
+			DisableStack:      ctx.Bool(TraceDisableStackFlag.Name),
+			DisableMemory:     ctx.Bool(TraceDisableMemoryFlag.Name),
+			DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name),
+			Debug:             true,
 		}
 		var prevFile *os.File
 		// This one closes the last file
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 1394dc9ae724726616f1d46a06c209c0a4cb2e20..1bcd4db489efb4f8ce170ce8185d07004630e7c6 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -119,6 +119,14 @@ var (
 		Name:  "nostack",
 		Usage: "disable stack output",
 	}
+	DisableStorageFlag = cli.BoolFlag{
+		Name:  "nostorage",
+		Usage: "disable storage output",
+	}
+	DisableReturnDataFlag = cli.BoolFlag{
+		Name:  "noreturndata",
+		Usage: "disable return data output",
+	}
 	EVMInterpreterFlag = cli.StringFlag{
 		Name:  "vm.evm",
 		Usage: "External EVM configuration (default = built-in interpreter)",
@@ -135,6 +143,7 @@ var stateTransitionCommand = cli.Command{
 		t8ntool.TraceFlag,
 		t8ntool.TraceDisableMemoryFlag,
 		t8ntool.TraceDisableStackFlag,
+		t8ntool.TraceDisableReturnDataFlag,
 		t8ntool.OutputAllocFlag,
 		t8ntool.OutputResultFlag,
 		t8ntool.InputAllocFlag,
@@ -170,6 +179,8 @@ func init() {
 		ReceiverFlag,
 		DisableMemoryFlag,
 		DisableStackFlag,
+		DisableStorageFlag,
+		DisableReturnDataFlag,
 		EVMInterpreterFlag,
 	}
 	app.Commands = []cli.Command{
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index 0779706763f313f0766a42a27323ecaa30985ab0..a82427eec00e8bde96bb4a80b6ab65505f1307e2 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -111,9 +111,11 @@ func runCmd(ctx *cli.Context) error {
 	glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
 	log.Root().SetHandler(glogger)
 	logconfig := &vm.LogConfig{
-		DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
-		DisableStack:  ctx.GlobalBool(DisableStackFlag.Name),
-		Debug:         ctx.GlobalBool(DebugFlag.Name),
+		DisableMemory:     ctx.GlobalBool(DisableMemoryFlag.Name),
+		DisableStack:      ctx.GlobalBool(DisableStackFlag.Name),
+		DisableStorage:    ctx.GlobalBool(DisableStorageFlag.Name),
+		DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
+		Debug:             ctx.GlobalBool(DebugFlag.Name),
 	}
 
 	var (
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index 60eded7a63c212fc45c43df4d9191dfc059f90b6..78bd7a93ea6099c6e4a8e994c6100384bcd73046 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -61,8 +61,10 @@ func stateTestCmd(ctx *cli.Context) error {
 
 	// Configure the EVM logger
 	config := &vm.LogConfig{
-		DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
-		DisableStack:  ctx.GlobalBool(DisableStackFlag.Name),
+		DisableMemory:     ctx.GlobalBool(DisableMemoryFlag.Name),
+		DisableStack:      ctx.GlobalBool(DisableStackFlag.Name),
+		DisableStorage:    ctx.GlobalBool(DisableStorageFlag.Name),
+		DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
 	}
 	var (
 		tracer   vm.Tracer
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index 25166efe724dca2e9242034d3e09aa49e0506cef..1668f797e95dd99fcce010adfceb945ae2627672 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -104,12 +104,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
 }
 
 // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
-func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
-	gas := p.RequiredGas(input)
-	if contract.UseGas(gas) {
-		return p.Run(input)
+// It returns
+// - the returned bytes,
+// - the _remaining_ gas,
+// - any error that occurred
+func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
+	gasCost := p.RequiredGas(input)
+	if suppliedGas < gasCost {
+		return nil, 0, ErrOutOfGas
 	}
-	return nil, ErrOutOfGas
+	suppliedGas -= gasCost
+	output, err := p.Run(input)
+	return output, suppliedGas, err
 }
 
 // ECRECOVER implemented as a native contract.
@@ -199,6 +205,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
 type bigModExp struct{}
 
 var (
+	big0      = big.NewInt(0)
 	big1      = big.NewInt(1)
 	big4      = big.NewInt(4)
 	big8      = big.NewInt(8)
diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go
index 1957514dc3d05f248df01630daa654559e527a0e..8923047328d40f9c3aa3382d9f30348cc4a64411 100644
--- a/core/vm/contracts_test.go
+++ b/core/vm/contracts_test.go
@@ -73,10 +73,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{
 func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
 	p := allPrecompiles[common.HexToAddress(addr)]
 	in := common.Hex2Bytes(test.Input)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(uint256.Int), p.RequiredGas(in), false /* skipAnalysis */)
-	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
-		if res, err := RunPrecompiledContract(p, in, contract); err != nil {
+	gas := p.RequiredGas(in)
+	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
+		if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
 			t.Error(err)
 		} else if common.Bytes2Hex(res) != test.Expected {
 			t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@@ -92,10 +91,10 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
 func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
 	p := allPrecompiles[common.HexToAddress(addr)]
 	in := common.Hex2Bytes(test.Input)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(uint256.Int), p.RequiredGas(in)-1, false /* skipAnalysis */)
-	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
-		_, err := RunPrecompiledContract(p, in, contract)
+	gas := p.RequiredGas(in) - 1
+
+	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
+		_, _, err := RunPrecompiledContract(p, in, gas)
 		if err.Error() != "out of gas" {
 			t.Errorf("Expected error [out of gas], got [%v]", err)
 		}
@@ -110,11 +109,9 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
 func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
 	p := allPrecompiles[common.HexToAddress(addr)]
 	in := common.Hex2Bytes(test.Input)
-	contract := NewContract(AccountRef(common.HexToAddress("31337")),
-		nil, new(uint256.Int), p.RequiredGas(in), false /* skipAnalysis */)
-
+	gas := p.RequiredGas(in)
 	t.Run(test.Name, func(t *testing.T) {
-		_, err := RunPrecompiledContract(p, in, contract)
+		_, _, err := RunPrecompiledContract(p, in, gas)
 		if err.Error() != test.ExpectedError {
 			t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
 		}
@@ -133,8 +130,6 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
 	p := allPrecompiles[common.HexToAddress(addr)]
 	in := common.Hex2Bytes(test.Input)
 	reqGas := p.RequiredGas(in)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(uint256.Int), reqGas, false /* skipAnalysis */)
 
 	var (
 		res  []byte
@@ -142,14 +137,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
 		data = make([]byte, len(in))
 	)
 
-	bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(bench *testing.B) {
+	bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) {
 		bench.ReportAllocs()
 		start := time.Now().Nanosecond()
 		bench.ResetTimer()
 		for i := 0; i < bench.N; i++ {
-			contract.Gas = reqGas
 			copy(data, in)
-			res, err = RunPrecompiledContract(p, data, contract)
+			res, _, err = RunPrecompiledContract(p, data, reqGas)
 		}
 		bench.StopTimer()
 		elapsed := float64(time.Now().Nanosecond() - start)
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 51e6aa7a7ec93c40dba90c4b554bf9ade78e90c1..5648c2a9070e53aa29a27eb4efc8438e97e7ab90 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -45,23 +45,24 @@ type (
 	GetHashFunc func(uint64) common.Hash
 )
 
+func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
+	var precompiles map[common.Address]PrecompiledContract
+	switch {
+	case evm.chainRules.IsYoloV1:
+		precompiles = PrecompiledContractsYoloV1
+	case evm.chainRules.IsIstanbul:
+		precompiles = PrecompiledContractsIstanbul
+	case evm.chainRules.IsByzantium:
+		precompiles = PrecompiledContractsByzantium
+	default:
+		precompiles = PrecompiledContractsHomestead
+	}
+	p, ok := precompiles[addr]
+	return p, ok
+}
+
 // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
 func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
-	if contract.CodeAddr != nil {
-		precompiles := PrecompiledContractsHomestead
-		if evm.chainRules.IsByzantium {
-			precompiles = PrecompiledContractsByzantium
-		}
-		if evm.chainRules.IsIstanbul {
-			precompiles = PrecompiledContractsIstanbul
-		}
-		if evm.chainRules.IsYoloV1 {
-			precompiles = PrecompiledContractsYoloV1
-		}
-		if p := precompiles[*contract.CodeAddr]; p != nil {
-			return RunPrecompiledContract(p, input, contract)
-		}
-	}
 	for _, interpreter := range evm.interpreters {
 		if interpreter.CanRun(contract.Code) {
 			if evm.interpreter != interpreter {
@@ -208,22 +209,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
 		return nil, gas, ErrDepth
 	}
 	// Fail if we're trying to transfer more than the available balance
-	if !evm.Context.CanTransfer(evm.IntraBlockState, caller.Address(), value) {
+	if value.Sign() != 0 && !evm.Context.CanTransfer(evm.IntraBlockState, caller.Address(), value) {
 		return nil, gas, ErrInsufficientBalance
 	}
+	p, isPrecompile := evm.precompile(addr)
+
 	var (
 		to       = AccountRef(addr)
 		snapshot = evm.IntraBlockState.Snapshot()
 	)
-	if !evm.IntraBlockState.Exist(addr) {
-		precompiles := PrecompiledContractsHomestead
-		if evm.chainRules.IsByzantium {
-			precompiles = PrecompiledContractsByzantium
-		}
-		if evm.chainRules.IsIstanbul {
-			precompiles = PrecompiledContractsIstanbul
-		}
-		if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
+	if !evm.IntraBlockState.Exist(addr) {		if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
 			// Calling a non existing account, don't do anything, but ping the tracer
 			if evm.vmConfig.Debug {
 				_ = evm.vmConfig.Tracer.CaptureStart(evm.depth, caller.Address(), addr, false, input, gas, value.ToBig())
@@ -237,34 +232,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
 		evm.IntraBlockState.CreateAccount(addr, false)
 	}
 	evm.Transfer(evm.IntraBlockState, caller.Address(), to.Address(), value)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, value, gas, evm.vmConfig.SkipAnalysis)
-	contract.SetCallCode(&addr, evm.IntraBlockState.GetCodeHash(addr), evm.IntraBlockState.GetCode(addr))
 
-	// Even if the account has no code, we need to continue because it might be a precompile
-	start := time.Now()
 
 	// Capture the tracer start/end events in debug mode
 	if evm.vmConfig.Debug {
 		_ = evm.vmConfig.Tracer.CaptureStart(evm.depth, caller.Address(), addr, false, input, gas, value.ToBig())
-
-		defer func() { // Lazy evaluation of the parameters
-			_ = evm.vmConfig.Tracer.CaptureEnd(evm.depth, ret, gas-contract.Gas, time.Since(start), err)
-		}()
+		defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
+			evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
+		}(gas, time.Now())
 	}
-	ret, err = run(evm, contract, input, false)
 
+	if isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		code := evm.StateDB.GetCode(addr)
+		if len(code) == 0 {
+			ret, err = nil, nil // gas is unchanged
+		} else {
+			addrCopy := addr
+			// If the account has no code, we can abort here
+			// The depth-check is already done, and precompiles handled above
+			contract := NewContract(caller, AccountRef(addrCopy), value, gas)
+			contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
+			ret, err = run(evm, contract, input, false)
+			gas = contract.Gas
+		}
+	}
 	// When an error was returned by the EVM or when setting the creation code
 	// above we revert to the snapshot and consume any gas remaining. Additionally
 	// when we're in homestead this also counts for code storage gas errors.
 	if err != nil {
 		evm.IntraBlockState.RevertToSnapshot(snapshot)
 		if err != ErrExecutionReverted {
-			contract.UseGas(contract.Gas)
+			gas = 0
 		}
+		// TODO: consider clearing up unused snapshots:
+		//} else {
+		//	evm.StateDB.DiscardSnapshot(snapshot)
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // CallCode executes the contract associated with the addr with the given input
@@ -291,21 +299,26 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
 	}
 	var (
 		snapshot = evm.IntraBlockState.Snapshot()
-		to       = AccountRef(caller.Address())
-	)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, value, gas, evm.vmConfig.SkipAnalysis)
-	contract.SetCallCode(&addr, evm.IntraBlockState.GetCodeHash(addr), evm.IntraBlockState.GetCode(addr))
 
-	ret, err = run(evm, contract, input, false)
+	// It is allowed to call precompiles, even via delegatecall
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		addrCopy := addr
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		ret, err = run(evm, contract, input, false)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.IntraBlockState.RevertToSnapshot(snapshot)
 		if err != ErrExecutionReverted {
-			contract.UseGas(contract.Gas)
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // DelegateCall executes the contract associated with the addr with the given input
@@ -321,22 +334,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
-	var (
 		snapshot = evm.IntraBlockState.Snapshot()
-		to       = AccountRef(caller.Address())
-	)
-	// Initialise a new contract and make initialise the delegate values
-	contract := NewContract(caller, to, nil, gas, evm.vmConfig.SkipAnalysis).AsDelegate()
-	contract.SetCallCode(&addr, evm.IntraBlockState.GetCodeHash(addr), evm.IntraBlockState.GetCode(addr))
 
-	ret, err = run(evm, contract, input, false)
+	// It is allowed to call precompiles, even via delegatecall
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		addrCopy := addr
+		// Initialise a new contract and make initialise the delegate values
+		contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		ret, err = run(evm, contract, input, false)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.IntraBlockState.RevertToSnapshot(snapshot)
 		if err != ErrExecutionReverted {
-			contract.UseGas(contract.Gas)
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // StaticCall executes the contract associated with the addr with the given input
@@ -351,14 +368,12 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
-	var (
-		to       = AccountRef(addr)
-		snapshot = evm.IntraBlockState.Snapshot()
-	)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, new(uint256.Int), gas, evm.vmConfig.SkipAnalysis)
-	contract.SetCallCode(&addr, evm.IntraBlockState.GetCodeHash(addr), evm.IntraBlockState.GetCode(addr))
+	// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
+	// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
+	// after all empty accounts were deleted, so this is not required. However, if we omit this,
+	// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
+	// We could change this, but for now it's left for legacy reasons
+	var snapshot = evm.StateDB.Snapshot()
 
 	// We do an AddBalance of zero here, just in order to trigger a touch.
 	// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
@@ -366,17 +381,30 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
 	// future scenarios
 	evm.IntraBlockState.AddBalance(addr, u256.Num0)
 
-	// When an error was returned by the EVM or when setting the creation code
-	// above we revert to the snapshot and consume any gas remaining. Additionally
-	// when we're in Homestead this also counts for code storage gas errors.
-	ret, err = run(evm, contract, input, true)
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		// At this point, we use a copy of address. If we don't, the go compiler will
+		// leak the 'contract' to the outer scope, and make allocation for 'contract'
+		// even if the actual execution ends on RunPrecompiled above.
+		addrCopy := addr
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		// When an error was returned by the EVM or when setting the creation code
+		// above we revert to the snapshot and consume any gas remaining. Additionally
+		// when we're in Homestead this also counts for code storage gas errors.
+		ret, err = run(evm, contract, input, true)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.IntraBlockState.RevertToSnapshot(snapshot)
 		if err != ErrExecutionReverted {
-			contract.UseGas(contract.Gas)
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 type codeAndHash struct {
@@ -485,7 +513,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2
 // DESCRIBED: docs/programmers_guide/guide.md#nonce
 func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
 	codeAndHash := &codeAndHash{code: code}
-	contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
+	contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes())
 	return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
 }
 
diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go
index 9f3e3e2c5934ce8168ebe230a99d138197bf6b24..e1a79a303e38211b4355bbbbc4518ba2378a72a3 100644
--- a/core/vm/gen_structlog.go
+++ b/core/vm/gen_structlog.go
@@ -24,6 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
 		MemorySize    int                         `json:"memSize"`
 		Stack         []*math.HexOrDecimal256     `json:"stack"`
 		ReturnStack   []math.HexOrDecimal64       `json:"returnStack"`
+		ReturnData    []byte                      `json:"returnData"`
 		Storage       map[common.Hash]common.Hash `json:"-"`
 		Depth         int                         `json:"depth"`
 		RefundCounter uint64                      `json:"refund"`
@@ -50,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
 			enc.ReturnStack[k] = math.HexOrDecimal64(v)
 		}
 	}
+	enc.ReturnData = s.ReturnData
 	enc.Storage = s.Storage
 	enc.Depth = s.Depth
 	enc.RefundCounter = s.RefundCounter
@@ -70,6 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
 		MemorySize    *int                        `json:"memSize"`
 		Stack         []*math.HexOrDecimal256     `json:"stack"`
 		ReturnStack   []math.HexOrDecimal64       `json:"returnStack"`
+		ReturnData    []byte                      `json:"returnData"`
 		Storage       map[common.Hash]common.Hash `json:"-"`
 		Depth         *int                        `json:"depth"`
 		RefundCounter *uint64                     `json:"refund"`
@@ -104,11 +107,14 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
 		}
 	}
 	if dec.ReturnStack != nil {
-		s.ReturnStack = make([]uint64, len(dec.ReturnStack))
+		s.ReturnStack = make([]uint32, len(dec.ReturnStack))
 		for k, v := range dec.ReturnStack {
-			s.ReturnStack[k] = uint64(v)
+			s.ReturnStack[k] = uint32(v)
 		}
 	}
+	if dec.ReturnData != nil {
+		s.ReturnData = dec.ReturnData
+	}
 	if dec.Storage != nil {
 		s.Storage = dec.Storage
 	}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index d35d640a82a8a5c2fe084a3bdc8c39486b5c16fc..00001deba20cba3ecd1a8da97822d4682fa1a290 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -606,7 +606,7 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
 	if !callContext.contract.validJumpSubdest(posU64) {
 		return nil, ErrInvalidJump
 	}
-	callContext.rstack.Push(*pc)
+	callContext.rstack.push(uint32(*pc))
 	*pc = posU64 + 1
 	return nil, nil
 }
@@ -618,7 +618,7 @@ func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx)
 	// Other than the check that the return stack is not empty, there is no
 	// need to validate the pc from 'returns', since we only ever push valid
 	//values onto it via jumpsub.
-	*pc = callContext.rstack.Pop() + 1
+	*pc = uint64(callContext.rstack.pop()) + 1
 	return nil, nil
 }
 
@@ -652,9 +652,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
 	stackvalue := size
 
 	callContext.contract.UseGas(gas)
-	res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig())
+	//TODO: use uint256.Int instead of converting with toBig()
+	var bigVal = big0
+	if !value.IsZero() {
+		bigVal = value.ToBig()
+	}
 
-	stackValue := size
+	res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal)
 
 	// Push item on the stack based on the returned error. If the ruleset is
 	// homestead we must check for CodeStoreOutOfGasError (homestead only
@@ -688,9 +692,16 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
 	// Apply EIP150
 	gas -= gas / 64
 	callContext.contract.UseGas(gas)
+	// reuse size int for stackvalue
+	stackvalue := size
+	//TODO: use uint256.Int instead of converting with toBig()
+	bigEndowment := big0
+	if !endowment.IsZero() {
+		bigEndowment = endowment.ToBig()
+	}
 	res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, &endowment, salt.ToBig())
 
-	stackValue := salt
+		bigEndowment, &salt)
 
 	// Push item on the stack based on the returned error.
 	if suberr != nil {
@@ -720,10 +731,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
 	// Get the arguments from the memory.
 	args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
+	var bigVal = big0
+	//TODO: use uint256.Int instead of converting with toBig()
+	// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
+	// but it would make more sense to extend the usage of uint256.Int
 	if !value.IsZero() {
 		gas += params.CallStipend
+		bigVal = value.ToBig()
 	}
-	ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value.ToBig())
+
+	ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal)
+
 	if err != nil {
 		temp.Clear()
 	} else {
@@ -756,10 +774,14 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
 	// Get arguments from the memory.
 	args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
+	//TODO: use uint256.Int instead of converting with toBig()
+	var bigVal = big0
 	if !value.IsZero() {
 		gas += params.CallStipend
+		bigVal = value.ToBig()
 	}
-	ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value.ToBig())
+
+	ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal)
 	if err != nil {
 		temp.Clear()
 	} else {
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 59ab8081b2aae2bdc7259715225521431daa3ee4..53c83a9f8749938ecb4d9c30737e731ec4d28941 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -215,14 +215,20 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		logged  bool   // deferred Tracer should ignore already logged steps
 		res     []byte // result of the opcode execution function
 	)
-	defer stackPool.Put(locStack)
+	// Don't move this deferrred function, it's placed before the capturestate-deferred method,
+	// so that it get's executed _after_: the capturestate needs the stacks before
+	// they are returned to the pools
+	defer func() {
+		returnStack(stack)
+		returnRStack(returns)
+	}()
 	contract.Input = input
 
 	if in.cfg.Debug {
 		defer func() {
 			if err != nil {
 				if !logged {
-					_ = in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, locStack, returns, contract, in.evm.depth, err)
+					in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
 				} else {
 					_ = in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, locStack, returns, contract, in.evm.depth, err)
 				}
@@ -307,7 +313,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		}
 
 		if in.cfg.Debug {
-			_ = in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, locStack, returns, contract, in.evm.depth, err)
+			in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
 			logged = true
 		}
 
@@ -316,7 +322,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		// if the operation clears the return data (e.g. it has returning data)
 		// set the last return to the result of the operation.
 		if operation.returns {
-			in.returnData = res
+			in.returnData = common.CopyBytes(res)
 		}
 
 		switch {
diff --git a/core/vm/logger.go b/core/vm/logger.go
index d31848d40e18eac3491e3878e260a4d75cf998e7..18efafccf8e447d85dd3b160cb061331db4a78e0 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -48,11 +48,12 @@ func (s Storage) Copy() Storage {
 
 // LogConfig are the configuration options for structured logger the EVM
 type LogConfig struct {
-	DisableMemory  bool // disable memory capture
-	DisableStack   bool // disable stack capture
-	DisableStorage bool // disable storage capture
-	Debug          bool // print output during capture end
-	Limit          int  // maximum length of output, but zero means unlimited
+	DisableMemory     bool // disable memory capture
+	DisableStack      bool // disable stack capture
+	DisableStorage    bool // disable storage capture
+	DisableReturnData bool // disable return data capture
+	Debug             bool // print output during capture end
+	Limit             int  // maximum length of output, but zero means unlimited
 }
 
 //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
@@ -67,7 +68,8 @@ type StructLog struct {
 	Memory        []byte                      `json:"memory"`
 	MemorySize    int                         `json:"memSize"`
 	Stack         []*big.Int                  `json:"stack"`
-	ReturnStack   []uint64                    `json:"returnStack"`
+	ReturnStack   []uint32                    `json:"returnStack"`
+	ReturnData    []byte                      `json:"returnData"`
 	Storage       map[common.Hash]common.Hash `json:"-"`
 	Depth         int                         `json:"depth"`
 	RefundCounter uint64                      `json:"refund"`
@@ -104,10 +106,10 @@ func (s *StructLog) ErrorString() string {
 // Note that reference types are actual VM data structures; make copies
 // if you need to retain them beyond the current call.
 type Tracer interface {
-	CaptureStart(depth int, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error
-	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *Contract, depth int, err error) error
-	CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *Contract, depth int, err error) error
-	CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error
+	CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error
+	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error
+	CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error
+	CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
 	CaptureCreate(creator common.Address, creation common.Address) error
 	CaptureAccountRead(account common.Address) error
 	CaptureAccountWrite(account common.Address) error
@@ -146,7 +148,7 @@ func (l *StructLogger) CaptureStart(depth int, from common.Address, to common.Ad
 // CaptureState logs a new structured log message and pushes it out to the environment
 //
 // CaptureState also tracks SLOAD/SSTORE ops to track storage change.
-func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *Contract, depth int, err error) error {
+func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
 	// check if already accumulated the specified number of logs
 	if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
 		return errTraceLimitReached
@@ -181,9 +183,9 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
 			stck[i] = new(big.Int).Set(item.ToBig())
 		}
 	}
-	var rstack []uint64
+	var rstack []uint32
 	if !l.cfg.DisableStack && rStack != nil {
-		rstck := make([]uint64, len(rStack.data))
+		rstck := make([]uint32, len(rStack.data))
 		copy(rstck, rStack.data)
 	}
 	// Copy a snapshot of the current storage to a new container
@@ -212,10 +214,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
 		}
 		storage = l.storage[contract.Address()].Copy()
 	}
-	var rstack []uint64
-	if !l.cfg.DisableStack && rStack != nil {
-		rstck := make([]uint64, len(rStack.Data()))
-		copy(rstck, rStack.Data())
+	var rdata []byte
+	if !l.cfg.DisableReturnData {
+		rdata = make([]byte, len(rData))
+		copy(rdata, rData)
 	}
 	// create a new snapshot of the EVM.
 	log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, storage, depth, env.IntraBlockState.GetRefund(), err}
@@ -297,6 +299,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
 				fmt.Fprintf(writer, "%x: %x\n", h, item)
 			}
 		}
+		if len(log.ReturnData) > 0 {
+			fmt.Fprintln(writer, "ReturnData:")
+			fmt.Fprint(writer, hex.Dump(log.ReturnData))
+		}
 		fmt.Fprintln(writer)
 	}
 }
@@ -348,7 +354,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
 	return nil
 }
 
-func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *Contract, depth int, err error) error {
+func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
 	fmt.Fprintf(t.out, "| %4d  | %10v  |  %3d |", pc, op, cost)
 
 	if !t.cfg.DisableStack { // format stack
diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go
index f6bfc9a0ed268d157c25107a2ff1fa616f3b8e6c..9826c6433841831331300cc0744beebc405d9d08 100644
--- a/core/vm/logger_json.go
+++ b/core/vm/logger_json.go
@@ -47,7 +47,7 @@ func (l *JSONLogger) CaptureStart(depth int, from common.Address, to common.Addr
 }
 
 // CaptureState outputs state information on the logger.
-func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *Contract, depth int, err error) error {
+func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
 	log := StructLog{
 		Pc:            pc,
 		Op:            op,
@@ -71,6 +71,9 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
 		log.Stack = logstack
 		log.ReturnStack = rStack.data
 	}
+	if !l.cfg.DisableReturnData {
+		log.ReturnData = rData
+	}
 	return l.encoder.Encode(log)
 }
 
diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go
index be0ee8242210ed0c268bd6e23d32d420e6528b00..4575de8769bf328804bdbd9d560294a192fd4b4f 100644
--- a/core/vm/runtime/runtime.go
+++ b/core/vm/runtime/runtime.go
@@ -55,13 +55,20 @@ type Config struct {
 func setDefaults(cfg *Config) {
 	if cfg.ChainConfig == nil {
 		cfg.ChainConfig = &params.ChainConfig{
-			ChainID:        big.NewInt(1),
-			HomesteadBlock: new(big.Int),
-			DAOForkBlock:   new(big.Int),
-			DAOForkSupport: false,
-			EIP150Block:    new(big.Int),
-			EIP155Block:    new(big.Int),
-			EIP158Block:    new(big.Int),
+			ChainID:             big.NewInt(1),
+			HomesteadBlock:      new(big.Int),
+			DAOForkBlock:        new(big.Int),
+			DAOForkSupport:      false,
+			EIP150Block:         new(big.Int),
+			EIP150Hash:          common.Hash{},
+			EIP155Block:         new(big.Int),
+			EIP158Block:         new(big.Int),
+			ByzantiumBlock:      new(big.Int),
+			ConstantinopleBlock: new(big.Int),
+			PetersburgBlock:     new(big.Int),
+			IstanbulBlock:       new(big.Int),
+			MuirGlacierBlock:    new(big.Int),
+			YoloV1Block:         nil,
 		}
 	}
 
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index d12186d22329787b3a76dd6b42bda4c0cd415aae..30cb02a4e10ad1398bffed76815970c98d9d8533 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -328,34 +328,6 @@ func TestBlockhash(t *testing.T) {
 	}
 }
 
-// BenchmarkSimpleLoop test a pretty simple loop which loops
-// 1M (1 048 575) times.
-// Takes about 200 ms
-func BenchmarkSimpleLoop(b *testing.B) {
-	// 0xfffff = 1048575 loops
-	code := []byte{
-		byte(vm.PUSH3), 0x0f, 0xff, 0xff,
-		byte(vm.JUMPDEST), //  [ count ]
-		byte(vm.PUSH1), 1, // [count, 1]
-		byte(vm.SWAP1),    // [1, count]
-		byte(vm.SUB),      // [ count -1 ]
-		byte(vm.DUP1),     //  [ count -1 , count-1]
-		byte(vm.PUSH1), 4, // [count-1, count -1, label]
-		byte(vm.JUMPI), // [ 0 ]
-		byte(vm.STOP),
-	}
-	//tracer := vm.NewJSONLogger(nil, os.Stdout)
-	//Execute(code, nil, &Config{
-	//	EVMConfig: vm.Config{
-	//		Debug:  true,
-	//		Tracer: tracer,
-	//	}})
-
-	for i := 0; i < b.N; i++ {
-		_, _, _ = Execute(code, nil, nil, 0)
-	}
-}
-
 type stepCounter struct {
 	inner *vm.JSONLogger
 	steps int
@@ -365,7 +337,7 @@ func (s *stepCounter) CaptureStart(_ int, from common.Address, to common.Address
 	return nil
 }
 
-func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *vm.Contract, depth int, err error) error {
+func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rData []byte, contract *vm.Contract, depth int, err error) error {
 	s.steps++
 	// Enable this for more output
 	//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
@@ -625,3 +597,160 @@ func DisabledTestEipExampleCases(t *testing.T) {
 			"allowed, and causes an error", code)
 	}
 }
+
+// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
+// state, this should not be used, since it does not reset the state between runs.
+func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) {
+	cfg := new(Config)
+	setDefaults(cfg)
+	cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+	cfg.GasLimit = gas
+	var (
+		destination = common.BytesToAddress([]byte("contract"))
+		vmenv       = NewEnv(cfg)
+		sender      = vm.AccountRef(cfg.Origin)
+	)
+	cfg.State.CreateAccount(destination)
+	eoa := common.HexToAddress("E0")
+	{
+		cfg.State.CreateAccount(eoa)
+		cfg.State.SetNonce(eoa, 100)
+	}
+	reverting := common.HexToAddress("EE")
+	{
+		cfg.State.CreateAccount(reverting)
+		cfg.State.SetCode(reverting, []byte{
+			byte(vm.PUSH1), 0x00,
+			byte(vm.PUSH1), 0x00,
+			byte(vm.REVERT),
+		})
+	}
+
+	//cfg.State.CreateAccount(cfg.Origin)
+	// set the receiver's (the executing contract) code for execution.
+	cfg.State.SetCode(destination, code)
+	vmenv.Call(sender, destination, nil, gas, cfg.Value)
+
+	b.Run(name, func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			vmenv.Call(sender, destination, nil, gas, cfg.Value)
+		}
+	})
+}
+
+// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG
+// 55 ms
+func BenchmarkSimpleLoop(b *testing.B) {
+
+	staticCallIdentity := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+		byte(vm.STATICCALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callIdentity := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.DUP1),       // value
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callInexistant := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.DUP1),        // out insize
+		byte(vm.DUP1),        // in offset
+		byte(vm.DUP1),        // value
+		byte(vm.PUSH1), 0xff, // address of existing contract
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callEOA := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.DUP1),        // out insize
+		byte(vm.DUP1),        // in offset
+		byte(vm.DUP1),        // value
+		byte(vm.PUSH1), 0xE0, // address of EOA
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	loopingCode := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+
+		byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP),
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	calllRevertingContractWithInput := []byte{
+		byte(vm.JUMPDEST), //
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.PUSH1), 0x20, // in size
+		byte(vm.PUSH1), 0x00, // in offset
+		byte(vm.PUSH1), 0x00, // value
+		byte(vm.PUSH1), 0xEE, // address of reverting contract
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	//tracer := vm.NewJSONLogger(nil, os.Stdout)
+	//Execute(loopingCode, nil, &Config{
+	//	EVMConfig: vm.Config{
+	//		Debug:  true,
+	//		Tracer: tracer,
+	//	}})
+	// 100M gas
+	benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b)
+	benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b)
+	benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)
+	benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b)
+	benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b)
+	benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b)
+
+	//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
+	//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
+}
diff --git a/core/vm/stack/stack.go b/core/vm/stack/stack.go
index c6a8aaf0f477f09af49af37f1e9f79f8ecdc7baa..c15ef568631e9fd4981f3809dcf9e83314796f4f 100644
--- a/core/vm/stack/stack.go
+++ b/core/vm/stack/stack.go
@@ -18,10 +18,17 @@ package stack
 
 import (
 	"fmt"
+	"sync"
 
 	"github.com/holiman/uint256"
 )
 
+var stackPool = sync.Pool{
+	New: func() interface{} {
+		return &Stack{data: make([]uint256.Int, 0, 16)}
+	},
+}
+
 // Stack is an object for basic stack operations. Items popped to the stack are
 // expected to be changed and modified. stack does not take care of adding newly
 // initialised objects.
@@ -30,6 +37,9 @@ type Stack struct {
 }
 
 func New(n ...int) *Stack {
+	return stackPool.Get().(*Stack)
+}
+
 	if len(n) > 0 {
 	return &Stack{data: make([]uint256.Int, 0, 16)}
 	}
@@ -96,19 +106,31 @@ func (st *Stack) Print() {
 	fmt.Println("#############")
 }
 
+var rStackPool = sync.Pool{
+	New: func() interface{} {
+		return &ReturnStack{data: make([]uint32, 0, 10)}
+	},
+}
+
 // ReturnStack is an object for basic return stack operations.
 type ReturnStack struct {
-	data []uint64
+	data []uint32
+}
+
+func newReturnStack() *ReturnStack {
+	return rStackPool.Get().(*ReturnStack)
 }
 
 func NewReturnStack() *ReturnStack {
 	return &ReturnStack{data: make([]uint64, 0, 1024)}
+	rStackPool.Put(rs)
 }
 
 func (st *ReturnStack) Push(d uint64) {
 	st.data = append(st.data, d)
 }
 
+// A uint32 is sufficient as for code below 4.2G
 func (st *ReturnStack) Pop() (ret uint64) {
 	ret = st.data[len(st.data)-1]
 	st.data = st.data[:len(st.data)-1]
diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go
index 0827d793882ffbde6c33283b36344f59093a7dfa..676de898126fc632cb794c5d6d7fbd5c494bf5b8 100644
--- a/eth/tracers/tracer.go
+++ b/eth/tracers/tracer.go
@@ -549,7 +549,7 @@ func (jst *Tracer) CaptureStart(depth int, from common.Address, to common.Addres
 }
 
 // CaptureState implements the Tracer interface to trace a single step of VM execution.
-func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *stack.Stack, rStack *stack.ReturnStack, contract *vm.Contract, depth int, err error) error {
+func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rdata []byte, contract *vm.Contract, depth int, err error) error {
 	if jst.err == nil {
 		// Initialize the context if it wasn't done yet
 		if !jst.inited {
diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go
index ca656e76abe58b0d3a7ccd4a3d8304b1178f5afd..4b6d5de009efd8bc5e67201dadeb9fe1be0ceec6 100644
--- a/eth/tracers/tracer_test.go
+++ b/eth/tracers/tracer_test.go
@@ -171,10 +171,10 @@ func TestHaltBetweenSteps(t *testing.T) {
 	env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
 	contract := vm.NewContract(&account{}, &account{}, uint256.NewInt(), 0, false /* skipAnalysis */)
 
-	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) //nolint:errcheck
+	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
 	timeout := errors.New("stahp")
 	tracer.Stop(timeout)
-	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) //nolint:errcheck
+	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
 
 	if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
 		t.Errorf("Expected timeout error, got %v", err)