From 459542c4611c7ad5361207f5e034e7db48dcad07 Mon Sep 17 00:00:00 2001 From: AnkushinDaniil Date: Wed, 26 Feb 2025 19:43:45 +0300 Subject: [PATCH] fix: format error trace for rpc v0.8 --- mocks/mock_vm.go | 16 ++-- node/throttled_vm.go | 8 +- rpc/v6/estimate_fee.go | 10 +-- rpc/v6/estimate_fee_test.go | 4 +- rpc/v6/simulation.go | 9 +- rpc/v6/simulation_test.go | 18 ++-- rpc/v6/trace.go | 9 +- rpc/v6/trace_test.go | 14 +-- rpc/v7/estimate_fee.go | 9 +- rpc/v7/estimate_fee_test.go | 11 ++- rpc/v7/simulation.go | 9 +- rpc/v7/simulation_test.go | 12 +-- rpc/v7/trace.go | 9 +- rpc/v7/trace_test.go | 14 +-- rpc/v8/estimate_fee.go | 10 +-- rpc/v8/estimate_fee_test.go | 11 ++- rpc/v8/simulation.go | 9 +- rpc/v8/simulation_pkg_test.go | 3 +- rpc/v8/simulation_test.go | 13 +-- rpc/v8/trace.go | 9 +- rpc/v8/trace_test.go | 12 +-- vm/errors.go | 11 ++- vm/rust/src/error.rs | 114 ++++++++++++++++++++++++ vm/rust/src/error_stack.rs | 104 ++++++++++++++++++++++ vm/rust/src/execution.rs | 163 ++++++++++++++++------------------ vm/rust/src/lib.rs | 128 ++++++++++++++++---------- vm/vm.go | 21 +++-- vm/vm_ffi.h | 4 +- vm/vm_test.go | 14 +-- 29 files changed, 517 insertions(+), 261 deletions(-) create mode 100644 vm/rust/src/error.rs create mode 100644 vm/rust/src/error_stack.rs diff --git a/mocks/mock_vm.go b/mocks/mock_vm.go index 81ed7ca151..036110da4d 100644 --- a/mocks/mock_vm.go +++ b/mocks/mock_vm.go @@ -44,31 +44,31 @@ func (m *MockVM) EXPECT() *MockVMMockRecorder { } // Call mocks base method. -func (m *MockVM) Call(callInfo *vm.CallInfo, blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, maxSteps uint64, sierraVersion string) (vm.CallResult, error) { +func (m *MockVM) Call(callInfo *vm.CallInfo, blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, maxSteps uint64, sierraVersion string, errStack bool) (vm.CallResult, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Call", callInfo, blockInfo, state, network, maxSteps, sierraVersion) + ret := m.ctrl.Call(m, "Call", callInfo, blockInfo, state, network, maxSteps, sierraVersion, errStack) ret0, _ := ret[0].(vm.CallResult) ret1, _ := ret[1].(error) return ret0, ret1 } // Call indicates an expected call of Call. -func (mr *MockVMMockRecorder) Call(callInfo, blockInfo, state, network, maxSteps, sierraVersion any) *gomock.Call { +func (mr *MockVMMockRecorder) Call(callInfo, blockInfo, state, network, maxSteps, sierraVersion, errStack any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockVM)(nil).Call), callInfo, blockInfo, state, network, maxSteps, sierraVersion) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockVM)(nil).Call), callInfo, blockInfo, state, network, maxSteps, sierraVersion, errStack) } // Execute mocks base method. -func (m *MockVM) Execute(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert bool) (vm.ExecutionResults, error) { +func (m *MockVM) Execute(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert, errStack bool) (vm.ExecutionResults, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert) + ret := m.ctrl.Call(m, "Execute", txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert, errStack) ret0, _ := ret[0].(vm.ExecutionResults) ret1, _ := ret[1].(error) return ret0, ret1 } // Execute indicates an expected call of Execute. -func (mr *MockVMMockRecorder) Execute(txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert any) *gomock.Call { +func (mr *MockVMMockRecorder) Execute(txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert, errStack any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockVM)(nil).Execute), txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockVM)(nil).Execute), txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, skipChargeFee, skipValidate, errOnRevert, errStack) } diff --git a/node/throttled_vm.go b/node/throttled_vm.go index 042cffb02f..57477bbfea 100644 --- a/node/throttled_vm.go +++ b/node/throttled_vm.go @@ -20,24 +20,24 @@ func NewThrottledVM(res vm.VM, concurrenyBudget uint, maxQueueLen int32) *Thrott } func (tvm *ThrottledVM) Call(callInfo *vm.CallInfo, blockInfo *vm.BlockInfo, state core.StateReader, - network *utils.Network, maxSteps uint64, sierraVersion string, + network *utils.Network, maxSteps uint64, sierraVersion string, errStack bool, ) (vm.CallResult, error) { ret := vm.CallResult{} return ret, tvm.Do(func(vm *vm.VM) error { var err error - ret, err = (*vm).Call(callInfo, blockInfo, state, network, maxSteps, sierraVersion) + ret, err = (*vm).Call(callInfo, blockInfo, state, network, maxSteps, sierraVersion, errStack) return err }) } func (tvm *ThrottledVM) Execute(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, - blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert bool, + blockInfo *vm.BlockInfo, state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert, errStack bool, ) (vm.ExecutionResults, error) { var executionResult vm.ExecutionResults return executionResult, tvm.Do(func(vm *vm.VM) error { var err error executionResult, err = (*vm).Execute(txns, declaredClasses, paidFeesOnL1, blockInfo, state, network, - skipChargeFee, skipValidate, errOnRevert) + skipChargeFee, skipValidate, errOnRevert, errStack) return err }) } diff --git a/rpc/v6/estimate_fee.go b/rpc/v6/estimate_fee.go index 2f0dab97de..982fdb9147 100644 --- a/rpc/v6/estimate_fee.go +++ b/rpc/v6/estimate_fee.go @@ -1,7 +1,7 @@ package rpcv6 import ( - "errors" + "encoding/json" "fmt" "github.com/NethermindEth/juno/core/felt" @@ -88,7 +88,7 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan if rpcErr != nil { if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code { data := rpcErr.Data.(TransactionExecutionErrorData) - return nil, MakeContractError(errors.New(data.ExecutionError)) + return nil, MakeContractError(data.ExecutionError) } return nil, rpcErr } @@ -96,11 +96,11 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan } type ContractErrorData struct { - RevertError string `json:"revert_error"` + RevertError json.RawMessage `json:"revert_error"` } -func MakeContractError(err error) *jsonrpc.Error { +func MakeContractError(err json.RawMessage) *jsonrpc.Error { return rpccore.ErrContractError.CloneWithData(ContractErrorData{ - RevertError: err.Error(), + RevertError: err, }) } diff --git a/rpc/v6/estimate_fee_test.go b/rpc/v6/estimate_fee_test.go index 178c26186a..af0ba29a84 100644 --- a/rpc/v6/estimate_fee_test.go +++ b/rpc/v6/estimate_fee_test.go @@ -54,9 +54,9 @@ func TestEstimateMessageFee(t *testing.T) { expectedGasConsumed := new(felt.Felt).SetUint64(37) mockVM.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any(), &vm.BlockInfo{ Header: latestHeader, - }, gomock.Any(), &utils.Mainnet, gomock.Any(), false, true).DoAndReturn( + }, gomock.Any(), &utils.Mainnet, gomock.Any(), false, true, false).DoAndReturn( func(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, blockInfo *vm.BlockInfo, - state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert bool, + state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert, errStack bool, ) (vm.ExecutionResults, error) { require.Len(t, txns, 1) assert.NotNil(t, txns[0].(*core.L1HandlerTransaction)) diff --git a/rpc/v6/simulation.go b/rpc/v6/simulation.go index f229e3cdd7..b33dad23db 100644 --- a/rpc/v6/simulation.go +++ b/rpc/v6/simulation.go @@ -1,6 +1,7 @@ package rpcv6 import ( + "encoding/json" "errors" "fmt" "slices" @@ -101,7 +102,7 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra BlockHashToBeRevealed: blockHashToBeRevealed, } executionResults, err := h.vm.Execute(txns, classes, paidFeesOnL1, &blockInfo, - state, h.bcReader.Network(), skipFeeCharge, skipValidate, errOnRevert) + state, h.bcReader.Network(), skipFeeCharge, skipValidate, errOnRevert, false) if err != nil { if errors.Is(err, utils.ErrResourceBusy) { return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr) @@ -157,13 +158,13 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra } type TransactionExecutionErrorData struct { - TransactionIndex uint64 `json:"transaction_index"` - ExecutionError string `json:"execution_error"` + TransactionIndex uint64 `json:"transaction_index"` + ExecutionError json.RawMessage `json:"execution_error"` } func makeTransactionExecutionError(err *vm.TransactionExecutionError) *jsonrpc.Error { return rpccore.ErrTransactionExecutionError.CloneWithData(TransactionExecutionErrorData{ TransactionIndex: err.Index, - ExecutionError: err.Cause.Error(), + ExecutionError: err.Cause, }) } diff --git a/rpc/v6/simulation_test.go b/rpc/v6/simulation_test.go index 3dfab407d6..3dfd03e947 100644 --- a/rpc/v6/simulation_test.go +++ b/rpc/v6/simulation_test.go @@ -1,7 +1,7 @@ package rpcv6_test import ( - "errors" + "encoding/json" "testing" "github.com/NethermindEth/juno/core" @@ -38,7 +38,7 @@ func TestSimulateTransactions(t *testing.T) { stepsUsed := uint64(123) mockVM.EXPECT().Execute(nilTxns, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, true, false, false). + }, mockState, n, true, false, false, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -54,7 +54,7 @@ func TestSimulateTransactions(t *testing.T) { stepsUsed := uint64(123) mockVM.EXPECT().Execute(nilTxns, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -69,30 +69,30 @@ func TestSimulateTransactions(t *testing.T) { t.Run("transaction execution error", func(t *testing.T) { mockVM.EXPECT().Execute(nilTxns, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, false). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) _, err := handler.SimulateTransactions(rpc.BlockID{Latest: true}, []rpc.BroadcastedTransaction{}, []rpc.SimulationFlag{rpc.SkipValidateFlag}) require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(rpc.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), err) mockVM.EXPECT().Execute(nilTxns, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, false). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) _, err = handler.SimulateTransactions(rpc.BlockID{Latest: true}, []rpc.BroadcastedTransaction{}, []rpc.SimulationFlag{rpc.SkipValidateFlag}) require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(rpc.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), err) }) } diff --git a/rpc/v6/trace.go b/rpc/v6/trace.go index 68eb58167e..798ce64128 100644 --- a/rpc/v6/trace.go +++ b/rpc/v6/trace.go @@ -2,6 +2,7 @@ package rpcv6 import ( "context" + "encoding/json" "errors" "net/http" "slices" @@ -256,7 +257,7 @@ func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block, } executionResults, err := h.vm.Execute(block.Transactions, classes, paidFeesOnL1, &blockInfo, state, network, false, - false, false) + false, false, false) if err != nil { if errors.Is(err, utils.ErrResourceBusy) { return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr) @@ -379,15 +380,15 @@ func (h *Handler) call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp }, &vm.BlockInfo{ Header: header, BlockHashToBeRevealed: blockHashToBeRevealed, - }, state, h.bcReader.Network(), h.callMaxSteps, "") + }, state, h.bcReader.Network(), h.callMaxSteps, "", false) if err != nil { if errors.Is(err, utils.ErrResourceBusy) { return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr) } - return nil, MakeContractError(err) + return nil, MakeContractError(json.RawMessage(err.Error())) } if res.ExecutionFailed { - return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result))) + return nil, MakeContractError(json.RawMessage(utils.FeltArrToString(res.Result))) } return res.Result, nil } diff --git a/rpc/v6/trace_test.go b/rpc/v6/trace_test.go index a37bf3ddfd..84162a028a 100644 --- a/rpc/v6/trace_test.go +++ b/rpc/v6/trace_test.go @@ -139,7 +139,7 @@ func TestTraceTransactionV0_6(t *testing.T) { vmTrace := new(vm.TransactionTrace) require.NoError(t, json.Unmarshal(vmTraceJSON, vmTrace)) mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, - &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false). + &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false, false). Return(vm.ExecutionResults{ DataAvailability: []core.DataAvailability{{L1DataGas: 0}}, Traces: []vm.TransactionTrace{*vmTrace}, @@ -199,7 +199,7 @@ func TestTraceTransactionV0_6(t *testing.T) { vmTrace := new(vm.TransactionTrace) require.NoError(t, json.Unmarshal(vmTraceJSON, vmTrace)) mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, - &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false). + &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false, false). Return(vm.ExecutionResults{ Traces: []vm.TransactionTrace{*vmTrace}, NumSteps: 0, @@ -295,7 +295,7 @@ func TestTraceBlockTransactions(t *testing.T) { vmTrace := vm.TransactionTrace{} require.NoError(t, json.Unmarshal(vmTraceJSON, &vmTrace)) mockVM.EXPECT().Execute(block.Transactions, []core.Class{declaredClass.Class}, paidL1Fees, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, false). Return(vm.ExecutionResults{ DataAvailability: []core.DataAvailability{}, Traces: []vm.TransactionTrace{vmTrace, vmTrace}, @@ -366,7 +366,7 @@ func TestTraceBlockTransactions(t *testing.T) { vmTrace := vm.TransactionTrace{} require.NoError(t, json.Unmarshal(vmTraceJSON, &vmTrace)) mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, false). Return(vm.ExecutionResults{ DataAvailability: []core.DataAvailability{}, Traces: []vm.TransactionTrace{vmTrace}, @@ -458,7 +458,7 @@ func TestCall(t *testing.T) { ClassHash: classHash, Selector: selector, Calldata: calldata, - }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil) + }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "", false).Return(expectedRes, nil) res, rpcErr := handler.Call(rpc.FunctionCall{ ContractAddress: *contractAddr, @@ -480,7 +480,7 @@ func TestCall(t *testing.T) { Result: []*felt.Felt{utils.HexToFelt(t, rpccore.EntrypointNotFoundFelt)}, ExecutionFailed: true, } - expectedErr := rpc.MakeContractError(errors.New(rpccore.EntrypointNotFoundFelt)) + expectedErr := rpc.MakeContractError(json.RawMessage(rpccore.EntrypointNotFoundFelt)) headsHeader := &core.Header{ Number: 9, @@ -490,7 +490,7 @@ func TestCall(t *testing.T) { mockReader.EXPECT().HeadsHeader().Return(headsHeader, nil) mockState.EXPECT().ContractClassHash(contractAddr).Return(classHash, nil) mockReader.EXPECT().Network().Return(n) - mockVM.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedRes, nil) + mockVM.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedRes, nil) res, rpcErr := handler.Call(rpc.FunctionCall{ ContractAddress: *contractAddr, diff --git a/rpc/v7/estimate_fee.go b/rpc/v7/estimate_fee.go index 55136306ca..ee0d647298 100644 --- a/rpc/v7/estimate_fee.go +++ b/rpc/v7/estimate_fee.go @@ -2,7 +2,6 @@ package rpcv7 import ( "encoding/json" - "errors" "fmt" "net/http" @@ -112,7 +111,7 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan if rpcErr != nil { if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code { data := rpcErr.Data.(TransactionExecutionErrorData) - return nil, httpHeader, MakeContractError(errors.New(data.ExecutionError)) + return nil, httpHeader, MakeContractError(data.ExecutionError) } return nil, httpHeader, rpcErr } @@ -120,11 +119,11 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan } type ContractErrorData struct { - RevertError string `json:"revert_error"` + RevertError json.RawMessage `json:"revert_error"` } -func MakeContractError(err error) *jsonrpc.Error { +func MakeContractError(err json.RawMessage) *jsonrpc.Error { return rpccore.ErrContractError.CloneWithData(ContractErrorData{ - RevertError: err.Error(), + RevertError: err, }) } diff --git a/rpc/v7/estimate_fee_test.go b/rpc/v7/estimate_fee_test.go index 2b6236643d..03b8751ed2 100644 --- a/rpc/v7/estimate_fee_test.go +++ b/rpc/v7/estimate_fee_test.go @@ -2,7 +2,6 @@ package rpcv7_test import ( "encoding/json" - "errors" "testing" "github.com/NethermindEth/juno/core" @@ -36,7 +35,7 @@ func TestEstimateFee(t *testing.T) { blockInfo := vm.BlockInfo{Header: &core.Header{}} t.Run("ok with zero values", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, false, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, false, true, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -50,7 +49,7 @@ func TestEstimateFee(t *testing.T) { }) t.Run("ok with zero values, skip validate", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -64,16 +63,16 @@ func TestEstimateFee(t *testing.T) { }) t.Run("transaction execution error", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true, false). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) _, httpHeader, err := handler.EstimateFee([]rpcv7.BroadcastedTransaction{}, []rpcv7.SimulationFlag{rpcv7.SkipValidateFlag}, rpcv7.BlockID{Latest: true}) require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(rpcv7.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), err) require.Equal(t, httpHeader.Get(rpcv7.ExecutionStepsHeader), "0") }) diff --git a/rpc/v7/simulation.go b/rpc/v7/simulation.go index cb4b9c0021..3c6980a3e4 100644 --- a/rpc/v7/simulation.go +++ b/rpc/v7/simulation.go @@ -1,6 +1,7 @@ package rpcv7 import ( + "encoding/json" "errors" "fmt" "net/http" @@ -107,7 +108,7 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra BlockHashToBeRevealed: blockHashToBeRevealed, } executionResults, err := h.vm.Execute(txns, classes, paidFeesOnL1, &blockInfo, - state, h.bcReader.Network(), skipFeeCharge, skipValidate, errOnRevert) + state, h.bcReader.Network(), skipFeeCharge, skipValidate, errOnRevert, false) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResults.NumSteps, 10)) overallFees := executionResults.OverallFees @@ -183,13 +184,13 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra } type TransactionExecutionErrorData struct { - TransactionIndex uint64 `json:"transaction_index"` - ExecutionError string `json:"execution_error"` + TransactionIndex uint64 `json:"transaction_index"` + ExecutionError json.RawMessage `json:"execution_error"` } func makeTransactionExecutionError(err *vm.TransactionExecutionError) *jsonrpc.Error { return rpccore.ErrTransactionExecutionError.CloneWithData(TransactionExecutionErrorData{ TransactionIndex: err.Index, - ExecutionError: err.Cause.Error(), + ExecutionError: err.Cause, }) } diff --git a/rpc/v7/simulation_test.go b/rpc/v7/simulation_test.go index b925142f10..0e002c2199 100644 --- a/rpc/v7/simulation_test.go +++ b/rpc/v7/simulation_test.go @@ -1,7 +1,7 @@ package rpcv7_test import ( - "errors" + "encoding/json" "testing" "github.com/NethermindEth/juno/core" @@ -39,7 +39,7 @@ func TestSimulateTransactions(t *testing.T) { stepsUsed := uint64(123) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, true, false, false). + }, mockState, n, true, false, false, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -56,7 +56,7 @@ func TestSimulateTransactions(t *testing.T) { stepsUsed := uint64(123) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, false). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -73,16 +73,16 @@ func TestSimulateTransactions(t *testing.T) { t.Run("v0_7, v0_8", func(t *testing.T) { //nolint:dupl mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, false). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) _, httpHeader, err := handler.SimulateTransactions(rpcv7.BlockID{Latest: true}, []rpcv7.BroadcastedTransaction{}, []rpcv7.SimulationFlag{rpcv7.SkipValidateFlag}) require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(rpcv7.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), err) require.Equal(t, httpHeader.Get(rpcv7.ExecutionStepsHeader), "0") }) diff --git a/rpc/v7/trace.go b/rpc/v7/trace.go index 70d41c12b2..c85aa921b9 100644 --- a/rpc/v7/trace.go +++ b/rpc/v7/trace.go @@ -2,6 +2,7 @@ package rpcv7 import ( "context" + "encoding/json" "errors" "net/http" "slices" @@ -313,7 +314,7 @@ func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block) } executionResult, err := h.vm.Execute(block.Transactions, classes, paidFeesOnL1, - &blockInfo, state, network, false, false, false) + &blockInfo, state, network, false, false, false, false) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResult.NumSteps, 10)) @@ -425,15 +426,15 @@ func (h *Handler) Call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp }, &vm.BlockInfo{ Header: header, BlockHashToBeRevealed: blockHashToBeRevealed, - }, state, h.bcReader.Network(), h.callMaxSteps, sierraVersion) + }, state, h.bcReader.Network(), h.callMaxSteps, sierraVersion, false) if err != nil { if errors.Is(err, utils.ErrResourceBusy) { return nil, rpccore.ErrInternal.CloneWithData(throttledVMErr) } - return nil, MakeContractError(err) + return nil, MakeContractError(json.RawMessage(err.Error())) } if res.ExecutionFailed { - return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result))) + return nil, MakeContractError(json.RawMessage(utils.FeltArrToString(res.Result))) } return res.Result, nil } diff --git a/rpc/v7/trace_test.go b/rpc/v7/trace_test.go index 7bbec4e2ac..cc33c1ce78 100644 --- a/rpc/v7/trace_test.go +++ b/rpc/v7/trace_test.go @@ -181,7 +181,7 @@ func TestTraceTransaction(t *testing.T) { stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, - false). + false, false). Return(vm.ExecutionResults{ OverallFees: overallFee, DataAvailability: dataGas, @@ -277,7 +277,7 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, - &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false). + &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false, false). Return(vm.ExecutionResults{ OverallFees: overallFee, DataAvailability: consumedGas, @@ -394,7 +394,7 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsedStr := "123" require.NoError(t, json.Unmarshal(vmTraceJSON, &vmTrace)) mockVM.EXPECT().Execute(block.Transactions, []core.Class{declaredClass.Class}, paidL1Fees, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, false). Return(vm.ExecutionResults{ DataAvailability: []core.DataAvailability{{}, {}}, Traces: []vm.TransactionTrace{vmTrace, vmTrace}, @@ -474,7 +474,7 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, false). Return(vm.ExecutionResults{ DataAvailability: []core.DataAvailability{{}, {}}, Traces: []vm.TransactionTrace{vmTrace}, @@ -570,7 +570,7 @@ func TestCall(t *testing.T) { ClassHash: classHash, Selector: selector, Calldata: calldata, - }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil) + }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "", false).Return(expectedRes, nil) res, rpcErr := handler.Call(rpcv7.FunctionCall{ ContractAddress: *contractAddr, @@ -592,7 +592,7 @@ func TestCall(t *testing.T) { Result: []*felt.Felt{utils.HexToFelt(t, rpccore.EntrypointNotFoundFelt)}, ExecutionFailed: true, } - expectedErr := rpcv7.MakeContractError(errors.New(rpccore.EntrypointNotFoundFelt)) + expectedErr := rpcv7.MakeContractError(json.RawMessage(rpccore.EntrypointNotFoundFelt)) headsHeader := &core.Header{ Number: 9, @@ -608,7 +608,7 @@ func TestCall(t *testing.T) { ClassHash: classHash, Selector: selector, Calldata: calldata, - }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil) + }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "", false).Return(expectedRes, nil) res, rpcErr := handler.Call(rpcv7.FunctionCall{ ContractAddress: *contractAddr, diff --git a/rpc/v8/estimate_fee.go b/rpc/v8/estimate_fee.go index 9e4509bdb7..6e608a45f2 100644 --- a/rpc/v8/estimate_fee.go +++ b/rpc/v8/estimate_fee.go @@ -1,7 +1,7 @@ package rpcv8 import ( - "errors" + "encoding/json" "fmt" "net/http" @@ -91,7 +91,7 @@ func estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHandler) (*FeeEs if rpcErr != nil { if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code { data := rpcErr.Data.(TransactionExecutionErrorData) - return nil, httpHeader, MakeContractError(errors.New(data.ExecutionError)) + return nil, httpHeader, MakeContractError(data.ExecutionError) } return nil, httpHeader, rpcErr } @@ -99,11 +99,11 @@ func estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHandler) (*FeeEs } type ContractErrorData struct { - RevertError string `json:"revert_error"` + RevertError json.RawMessage `json:"revert_error"` } -func MakeContractError(err error) *jsonrpc.Error { +func MakeContractError(err json.RawMessage) *jsonrpc.Error { return rpccore.ErrContractError.CloneWithData(ContractErrorData{ - RevertError: err.Error(), + RevertError: err, }) } diff --git a/rpc/v8/estimate_fee_test.go b/rpc/v8/estimate_fee_test.go index b57b0dee7d..b06074652a 100644 --- a/rpc/v8/estimate_fee_test.go +++ b/rpc/v8/estimate_fee_test.go @@ -2,7 +2,6 @@ package rpcv8_test import ( "encoding/json" - "errors" "testing" "github.com/NethermindEth/juno/core" @@ -36,7 +35,7 @@ func TestEstimateFee(t *testing.T) { blockInfo := vm.BlockInfo{Header: &core.Header{}} t.Run("ok with zero values", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, false, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, false, true, true). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -51,7 +50,7 @@ func TestEstimateFee(t *testing.T) { }) t.Run("ok with zero values, skip validate", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true, true). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -66,16 +65,16 @@ func TestEstimateFee(t *testing.T) { }) t.Run("transaction execution error", func(t *testing.T) { - mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true). + mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &blockInfo, mockState, n, true, true, true, true). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) _, httpHeader, err := handler.EstimateFee([]rpc.BroadcastedTransaction{}, []rpc.SimulationFlag{rpc.SkipValidateFlag}, rpc.BlockID{Latest: true}) require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(rpc.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), err) require.Equal(t, httpHeader.Get(rpc.ExecutionStepsHeader), "0") }) diff --git a/rpc/v8/simulation.go b/rpc/v8/simulation.go index 69fd339741..d3599b4b63 100644 --- a/rpc/v8/simulation.go +++ b/rpc/v8/simulation.go @@ -1,6 +1,7 @@ package rpcv8 import ( + "encoding/json" "errors" "fmt" "net/http" @@ -93,7 +94,7 @@ func (h *Handler) simulateTransactions(id BlockID, transactions []BroadcastedTra } executionResults, err := h.vm.Execute(txns, classes, paidFeesOnL1, &blockInfo, - state, network, skipFeeCharge, skipValidate, errOnRevert) + state, network, skipFeeCharge, skipValidate, errOnRevert, true) if err != nil { return nil, httpHeader, handleExecutionError(err) } @@ -219,13 +220,13 @@ func createSimulatedTransactions( } type TransactionExecutionErrorData struct { - TransactionIndex uint64 `json:"transaction_index"` - ExecutionError string `json:"execution_error"` + TransactionIndex uint64 `json:"transaction_index"` + ExecutionError json.RawMessage `json:"execution_error"` } func makeTransactionExecutionError(err *vm.TransactionExecutionError) *jsonrpc.Error { return rpccore.ErrTransactionExecutionError.CloneWithData(TransactionExecutionErrorData{ TransactionIndex: err.Index, - ExecutionError: err.Cause.Error(), + ExecutionError: err.Cause, }) } diff --git a/rpc/v8/simulation_pkg_test.go b/rpc/v8/simulation_pkg_test.go index 0609c16711..b867776838 100644 --- a/rpc/v8/simulation_pkg_test.go +++ b/rpc/v8/simulation_pkg_test.go @@ -1,6 +1,7 @@ package rpcv8 import ( + "encoding/json" "errors" "testing" @@ -131,7 +132,7 @@ func TestHandleExecutionError(t *testing.T) { name: "Transaction Execution Error", err: &vm.TransactionExecutionError{ Index: 0, - Cause: errors.New("some error"), + Cause: json.RawMessage("some error"), }, jsonRPCError: &jsonrpc.Error{ Code: rpccore.ErrUnexpectedError.Code, diff --git a/rpc/v8/simulation_test.go b/rpc/v8/simulation_test.go index c6d6bf5a5c..4fe5c78c54 100644 --- a/rpc/v8/simulation_test.go +++ b/rpc/v8/simulation_test.go @@ -1,6 +1,7 @@ package rpcv8_test import ( + "encoding/json" "errors" "strconv" "testing" @@ -54,7 +55,7 @@ func TestSimulateTransactions(t *testing.T) { defaultMockBehavior(mockReader, mockVM, mockState) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, true, false, false). + }, mockState, n, true, false, false, true). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -73,7 +74,7 @@ func TestSimulateTransactions(t *testing.T) { defaultMockBehavior(mockReader, mockVM, mockState) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, true). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{}, DataAvailability: []core.DataAvailability{}, @@ -91,16 +92,16 @@ func TestSimulateTransactions(t *testing.T) { defaultMockBehavior(mockReader, mockVM, mockState) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, true). Return(vm.ExecutionResults{}, vm.TransactionExecutionError{ Index: 44, - Cause: errors.New("oops"), + Cause: json.RawMessage("oops"), }) }, simulationFlags: []rpc.SimulationFlag{rpc.SkipValidateFlag}, err: rpccore.ErrTransactionExecutionError.CloneWithData(rpc.TransactionExecutionErrorData{ TransactionIndex: 44, - ExecutionError: "oops", + ExecutionError: json.RawMessage("oops"), }), }, { @@ -109,7 +110,7 @@ func TestSimulateTransactions(t *testing.T) { defaultMockBehavior(mockReader, mockVM, mockState) mockVM.EXPECT().Execute([]core.Transaction{}, nil, []*felt.Felt{}, &vm.BlockInfo{ Header: headsHeader, - }, mockState, n, false, true, false). + }, mockState, n, false, true, false, true). Return(vm.ExecutionResults{ OverallFees: []*felt.Felt{&felt.Zero}, DataAvailability: []core.DataAvailability{{L1Gas: 0}, {L1Gas: 0}}, diff --git a/rpc/v8/trace.go b/rpc/v8/trace.go index 4456824270..d6cd6d8b97 100644 --- a/rpc/v8/trace.go +++ b/rpc/v8/trace.go @@ -2,6 +2,7 @@ package rpcv8 import ( "context" + "encoding/json" "errors" "net/http" "slices" @@ -304,7 +305,7 @@ func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block) } executionResult, err := h.vm.Execute(block.Transactions, classes, paidFeesOnL1, - &blockInfo, state, network, false, false, false) + &blockInfo, state, network, false, false, false, true) httpHeader.Set(ExecutionStepsHeader, strconv.FormatUint(executionResult.NumSteps, 10)) @@ -410,12 +411,12 @@ func (h *Handler) Call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp }, &vm.BlockInfo{ Header: header, BlockHashToBeRevealed: blockHashToBeRevealed, - }, state, h.bcReader.Network(), h.callMaxSteps, sierraVersion) + }, state, h.bcReader.Network(), h.callMaxSteps, sierraVersion, true) if err != nil { if errors.Is(err, utils.ErrResourceBusy) { return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr) } - return nil, MakeContractError(err) + return nil, MakeContractError(json.RawMessage(err.Error())) } if res.ExecutionFailed { // the blockifier 0.13.4 update requires us to check if the execution failed, @@ -425,7 +426,7 @@ func (h *Handler) Call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp return nil, rpccore.ErrEntrypointNotFound } // Todo: There is currently no standardised way to format these error messages - return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result))) + return nil, MakeContractError(json.RawMessage(utils.FeltArrToString(res.Result))) } return res.Result, nil } diff --git a/rpc/v8/trace_test.go b/rpc/v8/trace_test.go index 86797754e8..fe6e2e45d7 100644 --- a/rpc/v8/trace_test.go +++ b/rpc/v8/trace_test.go @@ -182,7 +182,7 @@ func TestTraceTransaction(t *testing.T) { stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, - false).Return(vm.ExecutionResults{ + false, true).Return(vm.ExecutionResults{ OverallFees: overallFee, DataAvailability: da, GasConsumed: gc, @@ -282,7 +282,7 @@ func TestTraceTransaction(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, - &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false). + &vm.BlockInfo{Header: header}, gomock.Any(), &utils.Mainnet, false, false, false, true). Return(vm.ExecutionResults{ OverallFees: overallFee, DataAvailability: da, @@ -403,7 +403,7 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsedStr := "123" require.NoError(t, json.Unmarshal(vmTraceJSON, &vmTrace)) mockVM.EXPECT().Execute(block.Transactions, []core.Class{declaredClass.Class}, paidL1Fees, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, true). Return(vm.ExecutionResults{ OverallFees: nil, DataAvailability: []core.DataAvailability{{}, {}}, @@ -485,7 +485,7 @@ func TestTraceBlockTransactions(t *testing.T) { stepsUsed := uint64(123) stepsUsedStr := "123" mockVM.EXPECT().Execute([]core.Transaction{tx}, []core.Class{declaredClass.Class}, []*felt.Felt{}, &vm.BlockInfo{Header: header}, - gomock.Any(), n, false, false, false). + gomock.Any(), n, false, false, false, true). Return(vm.ExecutionResults{ OverallFees: nil, DataAvailability: []core.DataAvailability{{}, {}}, @@ -581,7 +581,7 @@ func TestCall(t *testing.T) { ClassHash: classHash, Selector: selector, Calldata: calldata, - }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil) + }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "", true).Return(expectedRes, nil) res, rpcErr := handler.Call(rpc.FunctionCall{ ContractAddress: *contractAddr, @@ -619,7 +619,7 @@ func TestCall(t *testing.T) { ClassHash: classHash, Selector: selector, Calldata: calldata, - }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil) + }, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "", true).Return(expectedRes, nil) res, rpcErr := handler.Call(rpc.FunctionCall{ ContractAddress: *contractAddr, diff --git a/vm/errors.go b/vm/errors.go index df05f12149..1600afb943 100644 --- a/vm/errors.go +++ b/vm/errors.go @@ -1,14 +1,13 @@ package vm -import "fmt" +import ( + "encoding/json" + "fmt" +) type TransactionExecutionError struct { Index uint64 - Cause error -} - -func (e TransactionExecutionError) Unwrap() error { - return e.Cause + Cause json.RawMessage } func (e TransactionExecutionError) Error() string { diff --git a/vm/rust/src/error.rs b/vm/rust/src/error.rs new file mode 100644 index 0000000000..15e88821fe --- /dev/null +++ b/vm/rust/src/error.rs @@ -0,0 +1,114 @@ +use blockifier::execution::errors::{ + ConstructorEntryPointExecutionError, EntryPointExecutionError, +}; +use blockifier::execution::stack_trace::gen_tx_execution_error_trace; +use blockifier::state::errors::StateError; +use blockifier::transaction::errors::TransactionExecutionError; + +use crate::error_stack::ErrorStack; + +#[derive(Debug)] +pub enum CallError { + ContractError(String, ErrorStack), + Internal(String), + Custom(String), +} + +impl From for CallError { + fn from(value: TransactionExecutionError) -> Self { + use TransactionExecutionError::*; + + let error_stack = gen_tx_execution_error_trace(&value); + + match value { + ContractConstructorExecutionFailed( + ConstructorEntryPointExecutionError::ExecutionError { error, .. }, + ) + | ExecutionError { error, .. } + | ValidateTransactionError { error, .. } => { + Self::ContractError(error.to_string(), error_stack.into()) + } + e => Self::ContractError(e.to_string(), error_stack.into()), + } + } +} + +impl CallError { + pub fn from_entry_point_execution_error( + error: EntryPointExecutionError, + contract_address: &starknet_api::core::ContractAddress, + class_hash: &starknet_api::core::ClassHash, + entry_point: &starknet_api::core::EntryPointSelector, + ) -> Self { + let error = TransactionExecutionError::ExecutionError { + error, + class_hash: *class_hash, + storage_address: *contract_address, + selector: *entry_point, + }; + error.into() + } +} + +impl From for CallError { + fn from(e: StateError) -> Self { + match e { + StateError::StateReadError(_) => Self::Internal(e.to_string().into()), + _ => Self::Custom(anyhow::anyhow!("State error: {}", e).to_string()), + } + } +} + +impl From for CallError { + fn from(value: starknet_api::StarknetApiError) -> Self { + Self::Custom(value.to_string()) + } +} + +impl From for CallError { + fn from(value: anyhow::Error) -> Self { + Self::Internal(value.to_string()) + } +} + +#[derive(Debug)] +pub enum ExecutionError { + ExecutionError { + error: String, + error_stack: ErrorStack, + }, + Internal(String), + Custom(String), +} + +impl From for ExecutionError { + fn from(e: StateError) -> Self { + match e { + StateError::StateReadError(_) => Self::Internal(e.to_string()), + _ => Self::Custom(format!("State error: {}", e)), + } + } +} + +impl From for ExecutionError { + fn from(value: starknet_api::StarknetApiError) -> Self { + Self::Custom(value.to_string()) + } +} + +impl From for ExecutionError { + fn from(value: anyhow::Error) -> Self { + Self::Internal(value.to_string()) + } +} + +impl ExecutionError { + pub fn new(error: TransactionExecutionError) -> Self { + let error_stack = gen_tx_execution_error_trace(&error); + + Self::ExecutionError { + error: error.to_string(), + error_stack: error_stack.into(), + } + } +} diff --git a/vm/rust/src/error_stack.rs b/vm/rust/src/error_stack.rs new file mode 100644 index 0000000000..3ed5c396e4 --- /dev/null +++ b/vm/rust/src/error_stack.rs @@ -0,0 +1,104 @@ +use blockifier::execution::stack_trace::{ + gen_tx_execution_error_trace, Cairo1RevertFrame, Cairo1RevertSummary, + ErrorStack as BlockifierErrorStack, ErrorStackSegment, +}; +use blockifier::transaction::errors::TransactionExecutionError; +use blockifier::transaction::objects::RevertError; +use starknet_types_core::felt::Felt; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct ErrorStack(pub Vec); + +impl From for ErrorStack { + fn from(value: BlockifierErrorStack) -> Self { + Self( + value + .stack + .into_iter() + .flat_map(|v| Frames::from(v).0) + .collect(), + ) + } +} + +impl From for ErrorStack { + fn from(value: TransactionExecutionError) -> Self { + gen_tx_execution_error_trace(&value).into() + } +} + +impl From for ErrorStack { + fn from(value: RevertError) -> Self { + match value { + RevertError::Execution(error_stack) => error_stack.into(), + RevertError::PostExecution(fee_check_error) => { + Self(vec![Frame::StringFrame(fee_check_error.to_string())]) + } + } + } +} + +impl From for ErrorStack { + fn from(value: Cairo1RevertSummary) -> Self { + Self(Frames::from(value).0) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Frame { + CallFrame(CallFrame), + StringFrame(String), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CallFrame { + pub storage_address: Felt, + pub class_hash: Felt, + pub selector: Option, +} + +impl From for Frame { + fn from(value: Cairo1RevertFrame) -> Self { + Self::CallFrame(CallFrame { + storage_address: *value.contract_address.0, + class_hash: value.class_hash.unwrap_or_default().0, + selector: Some(value.selector.0), + }) + } +} + +struct Frames(pub Vec); + +impl From for Frames { + fn from(value: ErrorStackSegment) -> Self { + match value { + ErrorStackSegment::EntryPoint(entry_point) => Self(vec![Frame::CallFrame(CallFrame { + storage_address: *entry_point.storage_address.0, + class_hash: entry_point.class_hash.0, + selector: entry_point.selector.map(|s| s.0), + })]), + ErrorStackSegment::Cairo1RevertSummary(revert_summary) => revert_summary.into(), + ErrorStackSegment::Vm(vm_exception) => { + Self(vec![Frame::StringFrame(String::from(&vm_exception))]) + } + ErrorStackSegment::StringFrame(string_frame) => { + Self(vec![Frame::StringFrame(string_frame)]) + } + } + } +} + +impl From for Frames { + fn from(value: Cairo1RevertSummary) -> Self { + let failure_reason = + starknet_api::execution_utils::format_panic_data(&value.last_retdata.0); + Self( + value + .stack + .into_iter() + .map(Into::into) + .chain(std::iter::once(Frame::StringFrame(failure_reason))) + .collect(), + ) + } +} diff --git a/vm/rust/src/execution.rs b/vm/rust/src/execution.rs index d737a6703f..4af3206f0b 100644 --- a/vm/rust/src/execution.rs +++ b/vm/rust/src/execution.rs @@ -1,3 +1,4 @@ +use crate::error::ExecutionError; use crate::juno_state_reader::JunoStateReader; use blockifier::execution::contract_class::TrackedResource; use blockifier::state::state_api::{StateReader, StateResult, UpdatableState}; @@ -13,62 +14,12 @@ use starknet_api::executable_transaction::AccountTransaction; use starknet_api::execution_resources::GasAmount; use starknet_api::transaction::fields::{GasVectorComputationMode, ValidResourceBounds}; use starknet_api::transaction::{DeclareTransaction, DeployAccountTransaction, InvokeTransaction}; -use std::fmt; -#[derive(Debug)] -pub enum ExecutionError { - TransactionExecutionError(TransactionExecutionError), - CustomError(anyhow::Error), -} - -impl fmt::Display for ExecutionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ExecutionError::TransactionExecutionError(err) => { - write!(f, "Transaction execution error: {}", err) - } - ExecutionError::CustomError(err) => write!(f, "Custom error: {}", err), - } - } -} - -impl From for ExecutionError { - fn from(err: TransactionExecutionError) -> Self { - ExecutionError::TransactionExecutionError(err) - } -} - -impl From for ExecutionError { - fn from(err: anyhow::Error) -> Self { - ExecutionError::CustomError(err) - } -} - -#[derive(Debug)] -enum SimulationError { - OutOfGas(GasAmount), - ExecutionError(TransactionExecutionError), -} - -impl fmt::Display for SimulationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SimulationError::OutOfGas(gas) => write!(f, "Out of gas: {}", gas), - SimulationError::ExecutionError(err) => write!(f, "Execution error: {}", err), - } - } -} - -impl From for SimulationError { - fn from(err: TransactionExecutionError) -> Self { - SimulationError::ExecutionError(err) - } -} - -pub fn execute_transaction( +pub fn process_transaction( txn: &mut Transaction, txn_state: &mut TransactionalState<'_, CachedState>, block_context: &BlockContext, + error_on_revert: bool, ) -> Result { match is_l2_gas_accounting_enabled( txn, @@ -76,13 +27,40 @@ pub fn execute_transaction( block_context, &determine_gas_vector_mode(txn), ) { - Ok(true) => get_gas_vector_computation_mode(txn, txn_state, block_context), - Ok(false) => txn - .execute(txn_state, block_context) - .map_err(ExecutionError::from), - Err(error) => Err(ExecutionError::TransactionExecutionError( - TransactionExecutionError::StateError(error), - )), + Ok(true) => { + execute_transaction_with_binary_search(txn, txn_state, block_context, error_on_revert) + } + Ok(false) => execute_transaction(txn, txn_state, block_context, error_on_revert), + Err(error) => Err(ExecutionError::new(TransactionExecutionError::StateError( + error, + ))), + } +} + +pub(crate) fn execute_transaction( + tx: &Transaction, + state: &mut S, + block_context: &blockifier::context::BlockContext, + error_on_revert: bool, +) -> Result +where + S: UpdatableState, +{ + match tx.execute(state, block_context) { + Ok(tx_info) => { + if tx_info.is_reverted() && error_on_revert { + if let Some(revert_error) = tx_info.revert_error { + let revert_string = revert_error.to_string(); + return Err(ExecutionError::ExecutionError { + error: revert_string, + error_stack: revert_error.into(), + }); + } + } + + Ok(tx_info) + } + Err(error) => Err(ExecutionError::new(error)), } } @@ -134,11 +112,16 @@ fn determine_gas_vector_mode(transaction: &Transaction) -> GasVectorComputationM } const L2_GAS_SEARCH_MARGIN: GasAmount = GasAmount(1_000_000); +enum SimulationError { + OutOfGas, + ExecutionError(ExecutionError), +} -fn get_gas_vector_computation_mode( +fn execute_transaction_with_binary_search( transaction: &mut Transaction, state: &mut S, block_context: &blockifier::context::BlockContext, + error_on_revert: bool, ) -> Result where S: UpdatableState, @@ -154,14 +137,11 @@ where let simulation_result = match simulate_execution(transaction, state, block_context) { Ok((tx_info, _)) => tx_info, - Err(SimulationError::ExecutionError(error)) => { - return Err(ExecutionError::TransactionExecutionError(error)) - } - Err(SimulationError::OutOfGas(gas)) => { - return Err(ExecutionError::CustomError(anyhow::anyhow!( - "Transaction ran out of gas during simulation: {}", - gas - ))); + Err(SimulationError::ExecutionError(error)) => return Err(error), + Err(SimulationError::OutOfGas) => { + return Err(ExecutionError::Custom( + "Transaction ran out of gas during simulation".to_string(), + )); } }; @@ -175,7 +155,7 @@ where // as the estimate and skip the binary search. (l2_gas_adjusted, tx_info, tx_state) } - Err(SimulationError::OutOfGas(_)) => { + Err(SimulationError::OutOfGas) => { let mut lower_bound = GasAmount(gas_used); let mut upper_bound = GasAmount::MAX; let mut current_l2_gas_limit = calculate_midpoint(lower_bound, upper_bound); @@ -204,33 +184,28 @@ where upper_bound = current_l2_gas_limit; current_l2_gas_limit = calculate_midpoint(lower_bound, upper_bound); } - Err(SimulationError::OutOfGas(_)) => { + Err(SimulationError::OutOfGas) => { lower_bound = current_l2_gas_limit; current_l2_gas_limit = calculate_midpoint(lower_bound, upper_bound); } - Err(SimulationError::ExecutionError(error)) => { - return Err(ExecutionError::TransactionExecutionError(error)) - } + Err(SimulationError::ExecutionError(error)) => return Err(error), } }; (current_l2_gas_limit, tx_info, tx_state) } - Err(SimulationError::ExecutionError(error)) => { - return Err(ExecutionError::TransactionExecutionError(error)) - } + Err(SimulationError::ExecutionError(error)) => return Err(error), }; tx_state.abort(); if l2_gas_limit > initial_gas_limit { set_l2_gas_limit(&mut original_transaction, GasAmount(0))?; - return original_transaction - .execute(state, block_context) - .map_err(ExecutionError::from); + return execute_transaction(&original_transaction, state, block_context, error_on_revert); } set_l2_gas_limit(&mut original_transaction, initial_gas_limit)?; - let mut exec_info = original_transaction.execute(state, block_context)?; + let mut exec_info = + execute_transaction(&original_transaction, state, block_context, error_on_revert)?; // Execute the transaction with the determined gas limit and update the estimate. exec_info.receipt.gas.l2_gas = l2_gas_limit; @@ -253,18 +228,32 @@ fn is_search_complete(lower: GasAmount, upper: GasAmount, margin: GasAmount) -> } fn simulate_execution<'a, S>( - transaction: &Transaction, + tx: &Transaction, state: &'a mut S, - block_context: &BlockContext, + block_context: &blockifier::context::BlockContext, ) -> Result<(TransactionExecutionInfo, TransactionalState<'a, S>), SimulationError> where S: UpdatableState, { - let mut simulated_state = CachedState::<_>::create_transactional(state); - match transaction.execute(&mut simulated_state, block_context) { - Ok(info) if is_out_of_gas(&info) => Err(SimulationError::OutOfGas(info.receipt.gas.l2_gas)), - Ok(info) => Ok((info, simulated_state)), - Err(error) => Err(SimulationError::ExecutionError(error)), + let mut tx_state = CachedState::<_>::create_transactional(state); + match tx.execute(&mut tx_state, block_context) { + Ok(tx_info) if is_out_of_gas(&tx_info) => Err(SimulationError::OutOfGas), + Ok(tx_info) => { + if tx_info.is_reverted() { + if let Some(revert_error) = tx_info.revert_error { + let revert_string = revert_error.to_string(); + return Err(SimulationError::ExecutionError( + ExecutionError::ExecutionError { + error: revert_string, + error_stack: revert_error.into(), + }, + )); + } + } + + Ok((tx_info, tx_state)) + } + Err(error) => Err(SimulationError::ExecutionError(ExecutionError::new(error))), } } diff --git a/vm/rust/src/lib.rs b/vm/rust/src/lib.rs index ece04a2c80..63246f8f66 100644 --- a/vm/rust/src/lib.rs +++ b/vm/rust/src/lib.rs @@ -1,12 +1,17 @@ +pub mod error; +pub mod error_stack; pub mod execution; pub mod jsonrpc; mod juno_state_reader; #[macro_use] extern crate lazy_static; - -use crate::execution::execute_transaction; use crate::juno_state_reader::{ptr_to_felt, JunoStateReader}; +use error::{CallError, ExecutionError}; +use error_stack::Frame; +use execution::process_transaction; +use serde::Deserialize; +use serde_json::json; use std::{ collections::HashMap, ffi::{c_char, c_longlong, c_uchar, c_ulonglong, c_void, CStr, CString}, @@ -29,16 +34,12 @@ use blockifier::{ execution::entry_point::{CallEntryPoint, CallType, EntryPointExecutionContext}, state::{cached_state::CachedState, state_api::State}, transaction::{ - errors::TransactionExecutionError::{ - ContractConstructorExecutionFailed, ExecutionError, ValidateTransactionError, - }, objects::{DeprecatedTransactionInfo, HasRelatedFeeType, TransactionInfo}, transaction_execution::Transaction, }, versioned_constants::VersionedConstants, }; use juno_state_reader::{class_info_from_json_str, felt_to_byte_array}; -use serde::Deserialize; use starknet_api::{ block::{BlockHash, GasPrice}, contract_class::{ClassInfo, EntryPointType, SierraVersion}, @@ -59,7 +60,7 @@ use starknet_api::{ execution_resources::GasAmount, }; use starknet_api::{ - core::{ChainId, ClassHash, ContractAddress, EntryPointSelector}, + core::{ChainId, ClassHash, ContractAddress}, hash::StarkHash, }; use starknet_types_core::felt::Felt; @@ -123,6 +124,7 @@ pub extern "C" fn cairoVMCall( max_steps: c_ulonglong, concurrency_mode: c_uchar, sierra_version: *const c_char, + err_stack: c_uchar, ) { let block_info = unsafe { *block_info_ptr }; let call_info = unsafe { *call_info_ptr }; @@ -158,10 +160,11 @@ pub extern "C" fn cairoVMCall( }; let contract_address = starknet_api::core::ContractAddress(PatriciaKey::try_from(contract_addr_felt).unwrap()); + let entry_point_selector = starknet_api::core::EntryPointSelector(entry_point_selector_felt); let entry_point = CallEntryPoint { entry_point_type: EntryPointType::External, - entry_point_selector: EntryPointSelector(entry_point_selector_felt), + entry_point_selector, calldata: Calldata(calldata_vec.into()), storage_address: contract_address, call_type: CallType::Call, @@ -171,6 +174,7 @@ pub extern "C" fn cairoVMCall( }; let concurrency_mode = concurrency_mode == 1; + let err_stack = err_stack == 1; let mut state = CachedState::new(reader); let mut context = EntryPointExecutionContext::new_invoke( Arc::new(TransactionContext { @@ -188,17 +192,38 @@ pub extern "C" fn cairoVMCall( SierraGasRevertTracker::new(GasAmount::from(initial_gas)), ); let mut remaining_gas = entry_point.initial_gas; - match entry_point.execute(&mut state, &mut context, &mut remaining_gas) { - Err(e) => report_error(reader_handle, e.to_string().as_str(), -1, 0), + let call_info = entry_point + .execute(&mut state, &mut context, &mut remaining_gas) + .map_err(|e| { + CallError::from_entry_point_execution_error( + e, + &contract_address, + class_hash.as_ref().unwrap_or(&ClassHash::default()), + &entry_point_selector, + ) + }); + + match call_info { + Err(CallError::ContractError(revert_error, error_stack)) => { + if err_stack { + let error_stack_json = error_stack_frames_to_json(&error_stack.0); + report_error(reader_handle, error_stack_json.to_string().as_str(), -1, 0); + } else { + report_error(reader_handle, revert_error.to_string().as_str(), -1, 0); + } + } + Err(CallError::Internal(e)) | Err(CallError::Custom(e)) => { + report_error(reader_handle, &e, -1, 0); + } Ok(call_info) => { if call_info.execution.failed { - report_error(reader_handle, "execution failed", -1, 1); + report_error(reader_handle, "execution failed".into(), -1, 1); return; } for data in call_info.execution.retdata.0 { unsafe { JunoAppendResponse(reader_handle, felt_to_byte_array(&data).as_ptr()); - }; + } } } } @@ -224,6 +249,7 @@ pub extern "C" fn cairoVMExecute( skip_validate: c_uchar, err_on_revert: c_uchar, concurrency_mode: c_uchar, + err_stack: c_uchar, ) { let block_info = unsafe { *block_info_ptr }; let reader = JunoStateReader::new(reader_handle, block_info.block_number); @@ -271,6 +297,8 @@ pub extern "C" fn cairoVMExecute( .unwrap(); let charge_fee = skip_charge_fee == 0; let validate = skip_validate == 0; + let err_stack = err_stack == 1; + let err_on_revert = err_on_revert == 1; let mut trace_buffer = Vec::with_capacity(10_000); @@ -343,42 +371,21 @@ pub extern "C" fn cairoVMExecute( Transaction::L1Handler(t) => (None, t.fee_type()), }; - match execute_transaction(&mut txn, &mut txn_state, &block_context) { - Err(error) => { - let err_string = match &error { - execution::ExecutionError::TransactionExecutionError( - transaction_execution_error, - ) => match transaction_execution_error { - ContractConstructorExecutionFailed(e) => format!("{:?}", e), - ExecutionError { error: e, .. } => format!("{:?}", e), - ValidateTransactionError { error: e, .. } => format!("{:?}", e), - other => other.to_string(), - }, - execution::ExecutionError::CustomError(error) => error.to_string(), - }; - report_error( - reader_handle, - format!( - "failed txn {} reason: {}", - txn_and_query_bit.txn_hash, err_string, - ) - .as_str(), - txn_index as i64, - 0, - ); - return; - } - Ok(mut tx_execution_info) => { - if tx_execution_info.is_reverted() && err_on_revert != 0 { - report_error( - reader_handle, - format!("reverted: {}", tx_execution_info.revert_error.unwrap()).as_str(), - txn_index as i64, - 0, - ); - return; + match process_transaction(&mut txn, &mut txn_state, &block_context, err_on_revert) { + Err(e) => match e { + ExecutionError::ExecutionError { error, error_stack } => { + let err_string = if err_stack { + error_stack_frames_to_json(&error_stack.0).to_string() + } else { + error.to_string() + }; + report_error(reader_handle, &err_string, txn_index as i64, 0); } - + ExecutionError::Internal(e) | ExecutionError::Custom(e) => { + report_error(reader_handle, e.to_string().as_str(), txn_index as i64, 0); + } + }, + Ok(mut tx_execution_info) => { // we are estimating fee, override actual fee calculation if tx_execution_info.receipt.fee.0 == 0 { let minimal_gas_vector = minimal_gas_vector.unwrap_or_default(); @@ -543,6 +550,33 @@ fn append_trace( }; } +fn error_stack_frames_to_json(frames: &[Frame]) -> serde_json::Value { + let last_string_frame_contents = frames + .iter() + .rev() + .find_map(|frame| match frame { + Frame::StringFrame(string) => Some(string.clone()), + _ => None, + }) + .unwrap_or_else(|| "Unknown error, no string frame available.".to_string()); + + frames + .iter() + .filter_map(|frame| match frame { + Frame::CallFrame(call_frame) => Some(call_frame), + _ => None, + }) + .rev() + .fold(json!(last_string_frame_contents), |child, frame| { + json!({ + "contract_address": frame.storage_address, + "class_hash": frame.class_hash, + "selector": frame.selector, + "error": child, + }) + }) +} + fn report_error(reader_handle: usize, msg: &str, txn_index: i64, execution_failed: usize) { let err_msg = CString::new(msg).unwrap(); unsafe { diff --git a/vm/vm.go b/vm/vm.go index 658a3a0ec2..8eb96e8303 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -39,9 +39,9 @@ type CallResult struct { //go:generate mockgen -destination=../mocks/mock_vm.go -package=mocks github.com/NethermindEth/juno/vm VM type VM interface { Call(callInfo *CallInfo, blockInfo *BlockInfo, state core.StateReader, network *utils.Network, - maxSteps uint64, sierraVersion string) (CallResult, error) + maxSteps uint64, sierraVersion string, errStack bool) (CallResult, error) Execute(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, blockInfo *BlockInfo, - state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert bool, + state core.StateReader, network *utils.Network, skipChargeFee, skipValidate, errOnRevert, errStack bool, ) (ExecutionResults, error) } @@ -213,7 +213,7 @@ func makeCBlockInfo(blockInfo *BlockInfo) C.BlockInfo { } func (v *vm) Call(callInfo *CallInfo, blockInfo *BlockInfo, state core.StateReader, - network *utils.Network, maxSteps uint64, sierraVersion string, + network *utils.Network, maxSteps uint64, sierraVersion string, errStack bool, ) (CallResult, error) { context := &callContext{ state: state, @@ -227,6 +227,10 @@ func (v *vm) Call(callInfo *CallInfo, blockInfo *BlockInfo, state core.StateRead if v.concurrencyMode { concurrencyModeByte = 1 } + var errorStackByte byte + if errStack { + errorStackByte = 1 + } C.setVersionedConstants(C.CString("my_json")) cCallInfo, callInfoPinner := makeCCallInfo(callInfo) @@ -241,6 +245,7 @@ func (v *vm) Call(callInfo *CallInfo, blockInfo *BlockInfo, state core.StateRead C.ulonglong(maxSteps), //nolint:gocritic C.uchar(concurrencyModeByte), //nolint:gocritic cSierraVersion, //nolint:gocritic + C.uchar(errorStackByte), //nolint:gocritic ) callInfoPinner.Unpin() C.free(unsafe.Pointer(chainID)) @@ -256,7 +261,7 @@ func (v *vm) Call(callInfo *CallInfo, blockInfo *BlockInfo, state core.StateRead // Execute executes a given transaction set and returns the gas spent per transaction func (v *vm) Execute(txns []core.Transaction, declaredClasses []core.Class, paidFeesOnL1 []*felt.Felt, blockInfo *BlockInfo, state core.StateReader, network *utils.Network, - skipChargeFee, skipValidate, errOnRevert bool, + skipChargeFee, skipValidate, errOnRevert, errorStack bool, ) (ExecutionResults, error) { context := &callContext{ state: state, @@ -294,6 +299,11 @@ func (v *vm) Execute(txns []core.Transaction, declaredClasses []core.Class, paid errOnRevertByte = 1 } + var errorStackByte byte + if errorStack { + errorStackByte = 1 + } + var concurrencyModeByte byte if v.concurrencyMode { concurrencyModeByte = 1 @@ -311,6 +321,7 @@ func (v *vm) Execute(txns []core.Transaction, declaredClasses []core.Class, paid C.uchar(skipValidateByte), C.uchar(errOnRevertByte), //nolint:gocritic C.uchar(concurrencyModeByte), //nolint:gocritic + C.uchar(errorStackByte), //nolint:gocritic ) C.free(unsafe.Pointer(classesJSONCStr)) @@ -323,7 +334,7 @@ func (v *vm) Execute(txns []core.Transaction, declaredClasses []core.Class, paid if context.errTxnIndex >= 0 { return ExecutionResults{}, TransactionExecutionError{ Index: uint64(context.errTxnIndex), - Cause: errors.New(context.err), + Cause: json.RawMessage(context.err), } } return ExecutionResults{}, errors.New(context.err) diff --git a/vm/vm_ffi.h b/vm/vm_ffi.h index df3c8b3b8a..a58ad02670 100644 --- a/vm/vm_ffi.h +++ b/vm/vm_ffi.h @@ -31,12 +31,12 @@ typedef struct BlockInfo { } BlockInfo; extern void cairoVMCall(CallInfo* call_info_ptr, BlockInfo* block_info_ptr, uintptr_t readerHandle, char* chain_id, - unsigned long long max_steps, unsigned char concurrency_mode, char* sierra_version); + unsigned long long max_steps, unsigned char concurrency_mode, char* sierra_version, unsigned char err_stack); extern void cairoVMExecute(char* txns_json, char* classes_json, char* paid_fees_on_l1_json, BlockInfo* block_info_ptr, uintptr_t readerHandle, char* chain_id, unsigned char skip_charge_fee, unsigned char skip_validate, unsigned char err_on_revert, - unsigned char concurrency_mode); + unsigned char concurrency_mode, unsigned char err_stack); extern char* setVersionedConstants(char* json); extern void freeString(char* str); diff --git a/vm/vm_test.go b/vm/vm_test.go index 5697a908df..620749dde2 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -50,7 +50,7 @@ func TestV0Call(t *testing.T) { ContractAddress: contractAddr, ClassHash: classHash, Selector: entryPoint, - }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Mainnet, 1_000_000, "") + }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Mainnet, 1_000_000, "", false) require.NoError(t, err) assert.Equal(t, []*felt.Felt{&felt.Zero}, ret.Result) @@ -70,7 +70,7 @@ func TestV0Call(t *testing.T) { ContractAddress: contractAddr, ClassHash: classHash, Selector: entryPoint, - }, &BlockInfo{Header: &core.Header{Number: 1}}, testState, &utils.Mainnet, 1_000_000, "") + }, &BlockInfo{Header: &core.Header{Number: 1}}, testState, &utils.Mainnet, 1_000_000, "", false) require.NoError(t, err) assert.Equal(t, []*felt.Felt{new(felt.Felt).SetUint64(1337)}, ret.Result) } @@ -117,7 +117,7 @@ func TestV1Call(t *testing.T) { Calldata: []felt.Felt{ *storageLocation, }, - }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Goerli, 1_000_000, "") + }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Goerli, 1_000_000, "", false) require.NoError(t, err) assert.Equal(t, []*felt.Felt{&felt.Zero}, ret.Result) @@ -139,7 +139,7 @@ func TestV1Call(t *testing.T) { Calldata: []felt.Felt{ *storageLocation, }, - }, &BlockInfo{Header: &core.Header{Number: 1}}, testState, &utils.Goerli, 1_000_000, "") + }, &BlockInfo{Header: &core.Header{Number: 1}}, testState, &utils.Goerli, 1_000_000, "", false) require.NoError(t, err) assert.Equal(t, []*felt.Felt{new(felt.Felt).SetUint64(37)}, ret.Result) } @@ -179,7 +179,7 @@ func TestCall_MaxSteps(t *testing.T) { ContractAddress: contractAddr, ClassHash: classHash, Selector: entryPoint, - }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Mainnet, 0, "") + }, &BlockInfo{Header: &core.Header{}}, testState, &utils.Mainnet, 0, "", false) assert.ErrorContains(t, err, "RunResources has no remaining steps") } @@ -204,7 +204,7 @@ func TestExecute(t *testing.T) { L1GasPriceSTRK: &felt.Zero, }, }, state, - &network, false, false, false) + &network, false, false, false, false) require.NoError(t, err) }) t.Run("zero data", func(t *testing.T) { @@ -214,7 +214,7 @@ func TestExecute(t *testing.T) { L1GasPriceETH: &felt.Zero, L1GasPriceSTRK: &felt.Zero, }, - }, state, &network, false, false, false) + }, state, &network, false, false, false, false) require.NoError(t, err) }) }