diff --git a/cmd/cvm/main.go b/cmd/cvm/main.go index 0eef918a4..7248af61d 100644 --- a/cmd/cvm/main.go +++ b/cmd/cvm/main.go @@ -121,6 +121,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", + } CVMInterpreterFlag = cli.StringFlag{ Name: "vm.cvm", Usage: "External CVM configuration (default = built-in interpreter)", @@ -137,6 +145,7 @@ var stateTransitionCommand = cli.Command{ t8ntool.TraceFlag, t8ntool.TraceDisableMemoryFlag, t8ntool.TraceDisableStackFlag, + t8ntool.TraceDisableReturnDataFlag, t8ntool.OutputAllocFlag, t8ntool.OutputResultFlag, t8ntool.InputAllocFlag, @@ -171,6 +180,8 @@ func init() { ReceiverFlag, DisableMemoryFlag, DisableStackFlag, + DisableStorageFlag, + DisableReturnDataFlag, CVMInterpreterFlag, } app.Commands = []cli.Command{ diff --git a/cmd/cvm/runner.go b/cmd/cvm/runner.go index 6b560082e..c5675b448 100644 --- a/cmd/cvm/runner.go +++ b/cmd/cvm/runner.go @@ -108,9 +108,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/cvm/staterunner.go b/cmd/cvm/staterunner.go index 369533017..ad3424432 100644 --- a/cmd/cvm/staterunner.go +++ b/cmd/cvm/staterunner.go @@ -59,8 +59,10 @@ func stateTestCmd(ctx *cli.Context) error { // Configure the CVM 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/cmd/cvm/t8ntool/flags.go b/cmd/cvm/t8ntool/flags.go index 09ff15b51..a2964d557 100644 --- a/cmd/cvm/t8ntool/flags.go +++ b/cmd/cvm/t8ntool/flags.go @@ -33,6 +33,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/cvm/t8ntool/transition.go b/cmd/cvm/t8ntool/transition.go index dd9644683..b9fb407dd 100644 --- a/cmd/cvm/t8ntool/transition.go +++ b/cmd/cvm/t8ntool/transition.go @@ -82,9 +82,10 @@ func Main(ctx *cli.Context) error { if ctx.Bool(TraceFlag.Name) { // Configure the CVM 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/core/state/statedb.go b/core/state/statedb.go index 9d6f69b7b..4076791b8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -506,7 +506,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } // If no live objects are available, attempt to use snapshots var ( - data Account + data *Account err error ) if s.snap != nil { @@ -518,11 +518,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if acc == nil { return nil } - data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash + data = &Account{ + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + Root: common.BytesToHash(acc.Root), + } if len(data.CodeHash) == 0 { data.CodeHash = emptyCodeHash } - data.Root = common.BytesToHash(acc.Root) if data.Root == (common.Hash{}) { data.Root = emptyRoot } @@ -538,13 +542,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { s.setError(err) return nil } - if err := rlp.DecodeBytes(enc, &data); err != nil { + data = new(Account) + if err := rlp.DecodeBytes(enc, data); err != nil { log.Error("Failed to decode state object", "addr", addr, "err", err) return nil } } // Insert into the live set - obj := newObject(s, addr, data) + obj := newObject(s, addr, *data) s.setStateObject(obj) return obj } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 767af2c06..72c11a193 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -56,12 +56,18 @@ var PrecompiledContracts = 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) { - energy := p.RequiredEnergy(input) - if contract.UseEnergy(energy) { - return p.Run(input) +// It returns +// - the returned bytes, +// - the _remaining_ energy, +// - any error that occurred +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedEnergy uint64) (ret []byte, remainingEnergy uint64, err error) { + energyCost := p.RequiredEnergy(input) + if suppliedEnergy < energyCost { + return nil, 0, ErrOutOfEnergy } - return nil, ErrOutOfEnergy + suppliedEnergy -= energyCost + output, err := p.Run(input) + return output, suppliedEnergy, err } // ECRECOVER implemented as a native contract. @@ -136,6 +142,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 3846c99b0..a9ef4a1a6 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -19,7 +19,6 @@ package vm import ( "bytes" "fmt" - "math/big" "reflect" "testing" @@ -405,14 +404,9 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { } p := PrecompiledContracts[address] in := common.Hex2Bytes(test.input) - testAddr, err := common.HexToAddress("cb390000000000000000000000000000000000001337") - if err != nil { - t.Error(err) - } - contract := NewContract(AccountRef(testAddr), - nil, new(big.Int), p.RequiredEnergy(in)) - t.Run(fmt.Sprintf("%s-Energy=%d", test.name, contract.Energy), func(t *testing.T) { - if res, err := RunPrecompiledContract(p, in, contract); err != nil { + energy := p.RequiredEnergy(in) + t.Run(fmt.Sprintf("%s-Energy=%d", test.name, energy), func(t *testing.T) { + if res, _, err := RunPrecompiledContract(p, in, energy); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.expected { t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res)) @@ -432,14 +426,10 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { } p := PrecompiledContracts[address] in := common.Hex2Bytes(test.input) - testAddr, err := common.HexToAddress("cb390000000000000000000000000000000000001337") - if err != nil { - t.Error(err) - } - contract := NewContract(AccountRef(testAddr), - nil, new(big.Int), p.RequiredEnergy(in)-1) - t.Run(fmt.Sprintf("%s-Energy=%d", test.name, contract.Energy), func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + energy := p.RequiredEnergy(in) - 1 + + t.Run(fmt.Sprintf("%s-Energy=%d", test.name, energy), func(t *testing.T) { + _, _, err := RunPrecompiledContract(p, in, energy) if err.Error() != "out of energy" { t.Errorf("Expected error [out of energy], got [%v]", err) } @@ -458,15 +448,9 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing } p := PrecompiledContracts[address] in := common.Hex2Bytes(test.input) - testAddr, err := common.HexToAddress("cb860000000000000000000000000000000000031337") - if err != nil { - t.Error(err) - } - contract := NewContract(AccountRef(testAddr), - nil, new(big.Int), p.RequiredEnergy(in)) - + energy := p.RequiredEnergy(in) t.Run(test.name, func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + _, _, err := RunPrecompiledContract(p, in, energy) if !reflect.DeepEqual(err, test.expectedError) { t.Errorf("Expected error [%v], got [%v]", test.expectedError, err) } @@ -489,24 +473,16 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { p := PrecompiledContracts[address] in := common.Hex2Bytes(test.input) reqEnergy := p.RequiredEnergy(in) - testAddr, err := common.HexToAddress("cb860000000000000000000000000000000000031337") - if err != nil { - bench.Error(err) - } - contract := NewContract(AccountRef(testAddr), - nil, new(big.Int), reqEnergy) - var ( res []byte data = make([]byte, len(in)) ) - bench.Run(fmt.Sprintf("%s-Energy=%d", test.name, contract.Energy), func(bench *testing.B) { + bench.Run(fmt.Sprintf("%s-Energy=%d", test.name, reqEnergy), func(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { - contract.Energy = reqEnergy copy(data, in) - res, err = RunPrecompiledContract(p, data, contract) + res, _, err = RunPrecompiledContract(p, data, reqEnergy) } bench.StopTimer() //Check if it is correct diff --git a/core/vm/cvm.go b/core/vm/cvm.go index 1ed6aba41..5655bccce 100644 --- a/core/vm/cvm.go +++ b/core/vm/cvm.go @@ -18,6 +18,7 @@ package vm import ( "errors" + "github.com/core-coin/uint256" "math/big" "sync/atomic" "time" @@ -41,14 +42,15 @@ type ( GetHashFunc func(uint64) common.Hash ) +func (cvm *CVM) precompile(addr common.Address) (PrecompiledContract, bool) { + var precompiles map[common.Address]PrecompiledContract + precompiles = PrecompiledContracts + 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(cvm *CVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { - if contract.CodeAddr != nil { - precompiles := PrecompiledContracts - if p := precompiles[*contract.CodeAddr]; p != nil { - return RunPrecompiledContract(p, input, contract) - } - } for _, interpreter := range cvm.interpreters { if interpreter.CanRun(contract.Code) { if cvm.interpreter != interpreter { @@ -191,17 +193,15 @@ func (cvm *CVM) Call(caller ContractRef, addr common.Address, input []byte, ener return nil, energy, ErrDepth } // Fail if we're trying to transfer more than the available balance - if !cvm.Context.CanTransfer(cvm.StateDB, caller.Address(), value) { + if value.Sign() != 0 && !cvm.Context.CanTransfer(cvm.StateDB, caller.Address(), value) { return nil, energy, ErrInsufficientBalance } - var ( - to = AccountRef(addr) - snapshot = cvm.StateDB.Snapshot() - ) + snapshot := cvm.StateDB.Snapshot() + p, isPrecompile := cvm.precompile(addr) + if !cvm.StateDB.Exist(addr) { - precompiles := PrecompiledContracts - if precompiles[addr] == nil && value.Sign() == 0 { + if !isPrecompile && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if cvm.vmConfig.Debug && cvm.depth == 0 { cvm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, energy, value) @@ -211,34 +211,47 @@ func (cvm *CVM) Call(caller ContractRef, addr common.Address, input []byte, ener } cvm.StateDB.CreateAccount(addr) } - cvm.Transfer(cvm.StateDB, caller.Address(), to.Address(), value) - // Initialise a new contract and set the code that is to be used by the CVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, energy) - contract.SetCallCode(&addr, cvm.StateDB.GetCodeHash(addr), cvm.StateDB.GetCode(addr)) - - // Even if the account has no code, we need to continue because it might be a precompile - start := time.Now() + cvm.Transfer(cvm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if cvm.vmConfig.Debug && cvm.depth == 0 { cvm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, energy, value) - defer func() { // Lazy evaluation of the parameters - cvm.vmConfig.Tracer.CaptureEnd(ret, energy-contract.Energy, time.Since(start), err) - }() + defer func(startEnergy uint64, startTime time.Time) { // Lazy evaluation of the parameters + cvm.vmConfig.Tracer.CaptureEnd(ret, startEnergy-energy, time.Since(startTime), err) + }(energy, time.Now()) + } + if isPrecompile { + ret, energy, err = RunPrecompiledContract(p, input, energy) + } else { + // Initialise a new contract and set the code that is to be used by the CVM. + // The contract is a scoped environment for this execution context only. + code := cvm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // energy 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, energy) + contract.SetCallCode(&addrCopy, cvm.StateDB.GetCodeHash(addrCopy), code) + ret, err = run(cvm, contract, input, false) + energy = contract.Energy + } } - ret, err = run(cvm, contract, input, false) // When an error was returned by the CVM or when setting the creation code // above we revert to the snapshot and consume any energy remaining. if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseEnergy(contract.Energy) + energy = 0 } + // TODO: consider clearing up unused snapshots: + //} else { + // cvm.StateDB.DiscardSnapshot(snapshot) } - return ret, contract.Energy, err + return ret, energy, err } // CallCode executes the contract associated with the addr with the given input @@ -265,23 +278,27 @@ func (cvm *CVM) CallCode(caller ContractRef, addr common.Address, input []byte, return nil, energy, ErrInsufficientBalance } - var ( - snapshot = cvm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - // Initialise a new contract and set the code that is to be used by the CVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, value, energy) - contract.SetCallCode(&addr, cvm.StateDB.GetCodeHash(addr), cvm.StateDB.GetCode(addr)) - - ret, err = run(cvm, contract, input, false) + var snapshot = cvm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := cvm.precompile(addr); isPrecompile { + ret, energy, err = RunPrecompiledContract(p, input, energy) + } else { + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the CVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(caller.Address()), value, energy) + contract.SetCallCode(&addrCopy, cvm.StateDB.GetCodeHash(addrCopy), cvm.StateDB.GetCode(addrCopy)) + ret, err = run(cvm, contract, input, false) + energy = contract.Energy + } if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseEnergy(contract.Energy) + energy = 0 } } - return ret, contract.Energy, err + return ret, energy, err } // DelegateCall executes the contract associated with the addr with the given input @@ -298,23 +315,26 @@ func (cvm *CVM) DelegateCall(caller ContractRef, addr common.Address, input []by return nil, energy, ErrDepth } - var ( - snapshot = cvm.StateDB.Snapshot() - to = AccountRef(caller.Address()) - ) - - // Initialise a new contract and make initialise the delegate values - contract := NewContract(caller, to, nil, energy).AsDelegate() - contract.SetCallCode(&addr, cvm.StateDB.GetCodeHash(addr), cvm.StateDB.GetCode(addr)) - - ret, err = run(cvm, contract, input, false) + var snapshot = cvm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := cvm.precompile(addr); isPrecompile { + ret, energy, err = RunPrecompiledContract(p, input, energy) + } else { + addrCopy := addr + // Initialise a new contract and make initialise the delegate values + contract := NewContract(caller, AccountRef(caller.Address()), nil, energy).AsDelegate() + contract.SetCallCode(&addrCopy, cvm.StateDB.GetCodeHash(addrCopy), cvm.StateDB.GetCode(addrCopy)) + ret, err = run(cvm, contract, input, false) + energy = contract.Energy + } if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseEnergy(contract.Energy) + energy = 0 } } - return ret, contract.Energy, err + return ret, energy, err } // StaticCall executes the contract associated with the addr with the given input @@ -329,31 +349,41 @@ func (cvm *CVM) StaticCall(caller ContractRef, addr common.Address, input []byte if cvm.depth > int(params.CallCreateDepth) { return nil, energy, ErrDepth } - - var ( - to = AccountRef(addr) - snapshot = cvm.StateDB.Snapshot() - ) - // Initialise a new contract and set the code that is to be used by the CVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, to, new(big.Int), energy) - contract.SetCallCode(&addr, cvm.StateDB.GetCodeHash(addr), cvm.StateDB.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 = cvm.StateDB.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - cvm.StateDB.AddBalance(addr, big.NewInt(0)) - - // When an error was returned by the CVM or when setting the creation code - // above we revert to the snapshot and consume any energy remaining. - ret, err = run(cvm, contract, input, true) + cvm.StateDB.AddBalance(addr, big0) + + if p, isPrecompile := cvm.precompile(addr); isPrecompile { + ret, energy, err = RunPrecompiledContract(p, input, energy) + } 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 CVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), energy) + contract.SetCallCode(&addrCopy, cvm.StateDB.GetCodeHash(addrCopy), cvm.StateDB.GetCode(addrCopy)) + // When an error was returned by the CVM or when setting the creation code + // above we revert to the snapshot and consume any energy remaining. + ret, err = run(cvm, contract, input, true) + energy = contract.Energy + } if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseEnergy(contract.Energy) + energy = 0 } } - return ret, contract.Energy, err + return ret, energy, err } type codeAndHash struct { @@ -452,9 +482,9 @@ func (cvm *CVM) Create(caller ContractRef, code []byte, energy uint64, value *bi // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (cvm *CVM) Create2(caller ContractRef, code []byte, energy uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverEnergy uint64, err error) { +func (cvm *CVM) Create2(caller ContractRef, code []byte, energy uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverEnergy 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 cvm.create(caller, codeAndHash, energy, endowment, contractAddr) } diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index f6992609f..2d145819d 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 0d099ea23..3ca0f5119 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -571,7 +571,7 @@ func opJumpSub(pc *uint64, interpreter *CVMInterpreter, 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 } @@ -583,7 +583,7 @@ func opReturnSub(pc *uint64, interpreter *CVMInterpreter, 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 } @@ -615,7 +615,13 @@ func opCreate(pc *uint64, interpreter *CVMInterpreter, callContext *callCtx) ([] stackvalue := size callContext.contract.UseEnergy(energy) - res, addr, returnEnergy, suberr := interpreter.cvm.Create(callContext.contract, input, energy, value.ToBig()) + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { + bigVal = value.ToBig() + } + + res, addr, returnEnergy, suberr := interpreter.cvm.Create(callContext.contract, input, energy, bigVal) // Push item on the callContext.stack based on the returned error. We must // ignore this error and pretend the operation was successful. if suberr == ErrCodeStoreOutOfEnergy { @@ -648,8 +654,13 @@ func opCreate2(pc *uint64, interpreter *CVMInterpreter, callContext *callCtx) ([ callContext.contract.UseEnergy(energy) // reuse size int for callContext.stackvalue stackvalue := size + //TODO: use uint256.Int instead of converting with toBig() + bigEndowment := big0 + if !endowment.IsZero() { + bigEndowment = endowment.ToBig() + } res, addr, returnEnergy, suberr := interpreter.cvm.Create2(callContext.contract, input, energy, - endowment.ToBig(), salt.ToBig()) + bigEndowment, &salt) // Push item on the callContext.stack based on the returned error. if suberr != nil { stackvalue.Clear() @@ -676,10 +687,16 @@ func opCall(pc *uint64, interpreter *CVMInterpreter, callContext *callCtx) ([]by // Get the arguments from the callContext.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() { energy += params.CallStipend + bigVal = value.ToBig() } - ret, returnEnergy, err := interpreter.cvm.Call(callContext.contract, toAddr, args, energy, value.ToBig()) + + ret, returnEnergy, err := interpreter.cvm.Call(callContext.contract, toAddr, args, energy, bigVal) if err != nil { temp.Clear() } else { @@ -704,10 +721,14 @@ func opCallCode(pc *uint64, interpreter *CVMInterpreter, callContext *callCtx) ( // Get arguments from the callContext.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() { energy += params.CallStipend + bigVal = value.ToBig() } - ret, returnEnergy, err := interpreter.cvm.CallCode(callContext.contract, toAddr, args, energy, value.ToBig()) + + ret, returnEnergy, err := interpreter.cvm.CallCode(callContext.contract, toAddr, args, energy, bigVal) if err != nil { temp.Clear() } else { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 2f744b649..bfe782ea2 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -159,13 +159,20 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps res []byte // result of the opcode execution function ) + // 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.cvm, pcCopy, op, energyCopy, cost, mem, stack, returns, contract, in.cvm.depth, err) + in.cfg.Tracer.CaptureState(in.cvm, pcCopy, op, energyCopy, cost, mem, stack, returns, in.returnData, contract, in.cvm.depth, err) } else { in.cfg.Tracer.CaptureFault(in.cvm, pcCopy, op, energyCopy, cost, mem, stack, returns, contract, in.cvm.depth, err) } @@ -249,7 +256,7 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { - in.cfg.Tracer.CaptureState(in.cvm, pc, op, energyCopy, cost, mem, stack, returns, contract, in.cvm.depth, err) + in.cfg.Tracer.CaptureState(in.cvm, pc, op, energyCopy, cost, mem, stack, returns, in.returnData, contract, in.cvm.depth, err) logged = true } @@ -258,7 +265,7 @@ func (in *CVMInterpreter) 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 0a745ba58..866294390 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 CVM 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"` @@ -105,7 +107,7 @@ func (s *StructLog) ErrorString() string { // if you need to retain them beyond the current call. type Tracer interface { CaptureStart(from common.Address, to common.Address, create bool, input []byte, energy uint64, value *big.Int) error - CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error + CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error CaptureFault(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureEnd(output []byte, energyUsed uint64, t time.Duration, err error) error } @@ -143,7 +145,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SSTORE ops to track dirty values. -func (l *StructLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, 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 @@ -183,13 +185,18 @@ func (l *StructLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost if !l.cfg.DisableStorage { storage = l.changedValues[contract.Address()].Copy() } - 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) } + 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, energy, cost, mem, memory.Len(), stck, rstack, storage, depth, env.StateDB.GetRefund(), err} + log := StructLog{pc, op, energy, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) return nil } @@ -253,6 +260,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) } } @@ -304,7 +315,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b return nil } -func (t *mdLogger) CaptureState(env *CVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (t *mdLogger) CaptureState(env *CVM, 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 cc194646b..af22b1b0d 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { log := StructLog{ Pc: pc, Op: op, @@ -69,6 +69,9 @@ func (l *JSONLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost u } log.Stack = logstack } + if !l.cfg.DisableReturnData { + log.ReturnData = rData + } return l.encoder.Encode(log) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 702dc0a4d..e04a296ee 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) { stack.push(uint256.NewInt(1)) stack.push(new(uint256.Int)) var index common.Hash - logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, contract, 0, nil) + logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil) if len(logger.changedValues[contract.Address()]) == 0 { t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 96675816c..f4b657d27 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -321,32 +321,402 @@ func TestBlockhash(t *testing.T) { } } -// BenchmarkSimpleLoop test a pretty simple loop which loops -// 1M (1 048 575) times. -// Takes about 200 ms +type stepCounter struct { + inner *vm.JSONLogger + steps int +} + +func (s *stepCounter) CaptureStart(from common.Address, to common.Address, create bool, input []byte, energy uint64, value *big.Int) error { + return nil +} + +func (s *stepCounter) CaptureState(env *vm.CVM, pc uint64, op vm.OpCode, energy, 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, energy, cost, memory, stack, rStack, contract, depth, err) + return nil +} + +func (s *stepCounter) CaptureFault(env *vm.CVM, pc uint64, op vm.OpCode, energy, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { + return nil +} + +func (s *stepCounter) CaptureEnd(output []byte, energyUsed uint64, t time.Duration, err error) error { + return nil +} + +func TestJumpSub1024Limit(t *testing.T) { + state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + address, _ := common.HexToAddress("cb270000000000000000000000000000000000000001") + // Code is + // 0 beginsub + // 1 push 0 + // 3 jumpsub + // + // The code recursively calls itself. It should error when the returns-stack + // grows above 1023 + state.SetCode(address, []byte{ + byte(vm.PUSH1), 3, + byte(vm.JUMPSUB), + byte(vm.BEGINSUB), + byte(vm.PUSH1), 3, + byte(vm.JUMPSUB), + }) + tracer := stepCounter{inner: vm.NewJSONLogger(nil, os.Stdout)} + // Enable 2315 + _, _, err := Call(address, nil, &Config{State: state, + EnergyLimit: 20000, + ChainConfig: params.AllCryptoreProtocolChanges, + CVMConfig: vm.Config{ + Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + Tracer: &tracer, + }}) + exp := "return stack limit reached" + if err.Error() != exp { + t.Fatalf("expected %v, got %v", exp, err) + } + if exp, got := 2048, tracer.steps; exp != got { + t.Fatalf("expected %d steps, got %d", exp, got) + } +} + +func TestReturnSubShallow(t *testing.T) { + state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + address, _ := common.HexToAddress("cb270000000000000000000000000000000000000001") + // The code does returnsub without having anything on the returnstack. + // It should not panic, but just fail after one step + state.SetCode(address, []byte{ + byte(vm.PUSH1), 5, + byte(vm.JUMPSUB), + byte(vm.RETURNSUB), + byte(vm.PC), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + byte(vm.PC), + }) + tracer := stepCounter{} + + // Enable 2315 + _, _, err := Call(address, nil, &Config{State: state, + EnergyLimit: 10000, + ChainConfig: params.AllCryptoreProtocolChanges, + CVMConfig: vm.Config{ + Debug: true, + Tracer: &tracer, + }}) + + exp := "invalid retsub" + if err.Error() != exp { + t.Fatalf("expected %v, got %v", exp, err) + } + if exp, got := 4, tracer.steps; exp != got { + t.Fatalf("expected %d steps, got %d", exp, got) + } +} + +// disabled -- only used for generating markdown +func DisabledTestReturnCases(t *testing.T) { + cfg := &Config{ + CVMConfig: vm.Config{ + Debug: true, + Tracer: vm.NewMarkdownLogger(nil, os.Stdout), + }, + } + // This should fail at first opcode + Execute([]byte{ + byte(vm.RETURNSUB), + byte(vm.PC), + byte(vm.PC), + }, nil, cfg) + + // Should also fail + Execute([]byte{ + byte(vm.PUSH1), 5, + byte(vm.JUMPSUB), + byte(vm.RETURNSUB), + byte(vm.PC), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + byte(vm.PC), + }, nil, cfg) + + // This should complete + Execute([]byte{ + byte(vm.PUSH1), 0x4, + byte(vm.JUMPSUB), + byte(vm.STOP), + byte(vm.BEGINSUB), + byte(vm.PUSH1), 0x9, + byte(vm.JUMPSUB), + byte(vm.RETURNSUB), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + }, nil, cfg) +} + +// DisabledTestEipExampleCases contains various testcases that are used for the +// EIP examples +// This test is disabled, as it's only used for generating markdown +func DisabledTestEipExampleCases(t *testing.T) { + cfg := &Config{ + CVMConfig: vm.Config{ + Debug: true, + Tracer: vm.NewMarkdownLogger(nil, os.Stdout), + }, + } + prettyPrint := func(comment string, code []byte) { + instrs := make([]string, 0) + it := asm.NewInstructionIterator(code) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg())) + } else { + instrs = append(instrs, fmt.Sprintf("%v", it.Op())) + } + } + ops := strings.Join(instrs, ", ") + + fmt.Printf("%v\nBytecode: `0x%x` (`%v`)\n", + comment, + code, ops) + Execute(code, nil, cfg) + } + + { // First eip testcase + code := []byte{ + byte(vm.PUSH1), 4, + byte(vm.JUMPSUB), + byte(vm.STOP), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + } + prettyPrint("This should jump into a subroutine, back out and stop.", code) + } + + { + code := []byte{ + byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.JUMPSUB), + byte(vm.STOP), + byte(vm.BEGINSUB), + byte(vm.PUSH1), 8 + 9, + byte(vm.JUMPSUB), + byte(vm.RETURNSUB), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + } + prettyPrint("This should execute fine, going into one two depths of subroutines", code) + } + // TODO(@holiman) move this test into an actual test, which not only prints + // out the trace. + { + code := []byte{ + byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.JUMPSUB), + byte(vm.STOP), + byte(vm.BEGINSUB), + byte(vm.PUSH1), 8 + 9, + byte(vm.JUMPSUB), + byte(vm.RETURNSUB), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + } + prettyPrint("This should fail, since the given location is outside of the "+ + "code-range. The code is the same as previous example, except that the "+ + "pushed location is `0x01000000000000000c` instead of `0x0c`.", code) + } + { + // This should fail at first opcode + code := []byte{ + byte(vm.RETURNSUB), + byte(vm.PC), + byte(vm.PC), + } + prettyPrint("This should fail at first opcode, due to shallow `return_stack`", code) + + } + { + code := []byte{ + byte(vm.PUSH1), 5, // Jump past the subroutine + byte(vm.JUMP), + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + byte(vm.JUMPDEST), + byte(vm.PUSH1), 3, // Now invoke the subroutine + byte(vm.JUMPSUB), + } + prettyPrint("In this example. the JUMPSUB is on the last byte of code. When the "+ + "subroutine returns, it should hit the 'virtual stop' _after_ the bytecode, "+ + "and not exit with error", code) + } + + { + code := []byte{ + byte(vm.BEGINSUB), + byte(vm.RETURNSUB), + byte(vm.STOP), + } + prettyPrint("In this example, the code 'walks' into a subroutine, which is not "+ + "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(energy 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.EnergyLimit = energy + var ( + destination = common.BytesToAddress([]byte("contract")) + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + ) + cfg.State.CreateAccount(destination) + eoa, _ := common.HexToAddress("cb270000000000000000000000000000000000000001") + { + cfg.State.CreateAccount(eoa) + cfg.State.SetNonce(eoa, 100) + } + reverting, _ := common.HexToAddress("cb970000000000000000000000000000000000000002") + { + 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, energy, cfg.Value) + + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + vmenv.Call(sender, destination, nil, energy, cfg.Value) + } + }) +} + +// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG +// 55 ms func BenchmarkSimpleLoop(b *testing.B) { - // 0xfffff = 1048575 loops - code := []byte{ - byte(vm.PUSH3), 0x0f, 0xff, 0xff, + + staticCallIdentity := []byte{ 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), + // 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.ENERGY), // energy + 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.ENERGY), // energy + 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.ENERGY), // energy + 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.ENERGY), // energy + 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.ENERGY), // energy + + 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.ENERGY), // energy + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), } + //tracer := vm.NewJSONLogger(nil, os.Stdout) - //Execute(code, nil, &Config{ - // CVMConfig: vm.Config{ + //Execute(loopingCode, nil, &Config{ + // EVMConfig: vm.Config{ // Debug: true, // Tracer: tracer, // }}) - - for i := 0; i < b.N; i++ { - Execute(code, nil, nil) - } + // 100M energy + 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) } type stepCounter struct { diff --git a/core/vm/stack.go b/core/vm/stack.go index e52f1fa91..487e94437 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -19,8 +19,15 @@ package vm import ( "fmt" "github.com/core-coin/uint256" + "sync" ) +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. @@ -29,7 +36,12 @@ type Stack struct { } func newstack() *Stack { - return &Stack{data: make([]uint256.Int, 0, 16)} + return stackPool.Get().(*Stack) +} + +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) } // Data returns the underlying uint256 array. @@ -86,20 +98,32 @@ 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 &ReturnStack{data: make([]uint64, 0, 1024)} + return rStackPool.Get().(*ReturnStack) +} + +func returnRStack(rs *ReturnStack) { + rs.data = rs.data[:0] + rStackPool.Put(rs) } -func (st *ReturnStack) push(d uint64) { +func (st *ReturnStack) push(d uint32) { st.data = append(st.data, d) } -func (st *ReturnStack) pop() (ret uint64) { +// A uint32 is sufficient as for code below 4.2G +func (st *ReturnStack) pop() (ret uint32) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return diff --git a/go.mod b/go.mod index 517a31d70..f50af3f90 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 + golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d golang.org/x/text v0.3.7 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index 1e7fe3ff7..2891ecd29 100644 --- a/go.sum +++ b/go.sum @@ -438,8 +438,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/signer/fourbyte/4byte.go b/signer/fourbyte/4byte.go index 3b741c30f..a48ea182b 100644 --- a/signer/fourbyte/4byte.go +++ b/signer/fourbyte/4byte.go @@ -146899,8 +146899,8 @@ func _4byteJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "4byte.json", size: 5954391, mode: os.FileMode(0664), modTime: time.Unix(1657275512, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xca, 0x20, 0x33, 0xe4, 0xd0, 0xaf, 0x28, 0x3f, 0xd0, 0xe8, 0x24, 0xb9, 0x5a, 0xd9, 0x95, 0x9c, 0x8a, 0x7e, 0x98, 0x18, 0xba, 0x8b, 0xd2, 0x8c, 0x82, 0x6b, 0x59, 0x11, 0x36, 0xd, 0x8a, 0x66}} + info := bindataFileInfo{name: "4byte.json", size: 5954391, mode: os.FileMode(0664), modTime: time.Unix(1657276926, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x78, 0xa2, 0xbc, 0xfb, 0x95, 0x5a, 0x5f, 0x6c, 0xdd, 0xc4, 0x5, 0x2c, 0xd0, 0x37, 0x7a, 0x8f, 0x25, 0xd2, 0x9a, 0x27, 0x3c, 0xb2, 0x91, 0x82, 0x97, 0x5e, 0xcf, 0x86, 0x46, 0x9c, 0x83, 0x13}} return a, nil } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 4bbbfa7dd..0b26c4330 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -178,9 +178,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte { h := newHasher(false) h.sha.Reset() h.sha.Write(key) - buf := h.sha.Sum(t.hashKeyBuf[:0]) + h.sha.Read(t.hashKeyBuf[:]) returnHasherToPool(h) - return buf + return t.hashKeyBuf[:] } // getSecKeyCache returns the current secure key cache, creating a new one if diff --git a/xcb/api_tracer.go b/xcb/api_tracer.go index 6008b9892..893041b25 100644 --- a/xcb/api_tracer.go +++ b/xcb/api_tracer.go @@ -721,7 +721,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha return api.traceTx(ctx, msg, vmctx, statedb, config) } -// TraceCall lets you trace a given xcb_call. It collects the structured logs created during the execution of EVM +// TraceCall lets you trace a given xcb_call. It collects the structured logs created during the execution of CVM // if the given transaction was added on top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args xcbapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { diff --git a/xcb/tracers/tracer.go b/xcb/tracers/tracer.go index 10c2fb129..bf09a5001 100644 --- a/xcb/tracers/tracer.go +++ b/xcb/tracers/tracer.go @@ -554,7 +554,7 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *Tracer) CaptureState(env *vm.CVM, pc uint64, op vm.OpCode, energy, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { +func (jst *Tracer) CaptureState(env *vm.CVM, pc uint64, op vm.OpCode, energy, 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/xcb/tracers/tracer_test.go b/xcb/tracers/tracer_test.go index d26f760d1..ad7c66bd6 100644 --- a/xcb/tracers/tracer_test.go +++ b/xcb/tracers/tracer_test.go @@ -169,10 +169,10 @@ func TestHaltBetweenSteps(t *testing.T) { env := vm.NewCVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) + 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) + 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)