Skip to content

Commit 8849d75

Browse files
authored
fix: make estimatemessage fee return Contract Not Found error when To address is missing (#3325)
1 parent 43e03da commit 8849d75

File tree

6 files changed

+272
-6
lines changed

6 files changed

+272
-6
lines changed

.github/workflows/deploy-and-test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,17 @@ jobs:
162162
STARKNET_PRIVATE_KEY: ${{ secrets.TEST_ACCOUNT_PRIVATE_KEY_3 }}
163163
STARKNET_PUBLIC_KEY: ${{ secrets.TEST_ACCOUNT_PUBLIC_KEY_3 }}
164164
STARKNET_ACCOUNT_ADDRESS: ${{ secrets.TEST_ACCOUNT_ADDRESS_3 }}
165+
166+
starknet-go-rpcv10:
167+
needs: [deploy]
168+
uses: ./.github/workflows/starknet-go-tests.yml
169+
with:
170+
ref: c647990ca2b58c470aa74f0c0e2022088720c28a
171+
rpc_version: v0_10
172+
secrets:
173+
TEST_RPC_URL: ${{ secrets.RPC_URL }}
174+
TEST_WS_RPC_URL: ${{ secrets.WS_RPC_URL }}
175+
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
176+
STARKNET_PRIVATE_KEY: ${{ secrets.TEST_ACCOUNT_PRIVATE_KEY_3 }}
177+
STARKNET_PUBLIC_KEY: ${{ secrets.TEST_ACCOUNT_PUBLIC_KEY_3 }}
178+
STARKNET_ACCOUNT_ADDRESS: ${{ secrets.TEST_ACCOUNT_ADDRESS_3 }}

rpc/handlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,12 @@ func (h *Handler) MethodsV0_10() ([]jsonrpc.Method, string) {
253253
Params: []jsonrpc.Parameter{
254254
{Name: "request"}, {Name: "simulation_flags"}, {Name: "block_id"},
255255
},
256-
Handler: h.rpcv9Handler.EstimateFee,
256+
Handler: h.rpcv10Handler.EstimateFee,
257257
},
258258
{
259259
Name: "starknet_estimateMessageFee",
260260
Params: []jsonrpc.Parameter{{Name: "message"}, {Name: "block_id"}},
261-
Handler: h.rpcv9Handler.EstimateMessageFee,
261+
Handler: h.rpcv10Handler.EstimateMessageFee,
262262
},
263263
{
264264
Name: "starknet_traceTransaction",

rpc/v10/estimate_fee.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package rpcv10
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/NethermindEth/juno/core/felt"
7+
"github.com/NethermindEth/juno/jsonrpc"
8+
"github.com/NethermindEth/juno/rpc/rpccore"
9+
rpcv6 "github.com/NethermindEth/juno/rpc/v6"
10+
rpcv9 "github.com/NethermindEth/juno/rpc/v9"
11+
)
12+
13+
/*
14+
***************************************************
15+
16+
Estimate Fee Handlers
17+
18+
****************************************************
19+
*/
20+
func (h *Handler) EstimateFee(
21+
broadcastedTxns BroadcastedTransactionInputs,
22+
simulationFlags []rpcv6.SimulationFlag,
23+
id *rpcv9.BlockID,
24+
) ([]rpcv9.FeeEstimate, http.Header, *jsonrpc.Error) {
25+
txnResults, httpHeader, err := h.simulateTransactions(
26+
id,
27+
broadcastedTxns.Data,
28+
append(simulationFlags, rpcv6.SkipFeeChargeFlag),
29+
true,
30+
true,
31+
)
32+
if err != nil {
33+
return nil, httpHeader, err
34+
}
35+
36+
feeEstimates := make([]rpcv9.FeeEstimate, len(txnResults))
37+
for i := range feeEstimates {
38+
feeEstimates[i] = txnResults[i].FeeEstimation
39+
}
40+
41+
return feeEstimates, httpHeader, nil
42+
}
43+
44+
func (h *Handler) EstimateMessageFee(
45+
msg *rpcv6.MsgFromL1, id *rpcv9.BlockID,
46+
) (rpcv9.FeeEstimate, http.Header, *jsonrpc.Error) {
47+
calldata := make([]*felt.Felt, len(msg.Payload)+1)
48+
// msg.From needs to be the first element
49+
calldata[0] = felt.NewFromBytes[felt.Felt](msg.From.Bytes())
50+
for i := range msg.Payload {
51+
calldata[i+1] = &msg.Payload[i]
52+
}
53+
54+
state, closer, rpcErr := h.stateByBlockID(id)
55+
if rpcErr != nil {
56+
return rpcv9.FeeEstimate{}, nil, rpcErr
57+
}
58+
defer h.callAndLogErr(closer, "Failed to close state in starknet_estimateMessageFee")
59+
60+
if _, err := state.ContractClassHash(&msg.To); err != nil {
61+
return rpcv9.FeeEstimate{}, nil, rpccore.ErrContractNotFound
62+
}
63+
64+
tx := rpcv9.BroadcastedTransaction{
65+
Transaction: rpcv9.Transaction{
66+
Type: rpcv9.TxnL1Handler,
67+
ContractAddress: &msg.To,
68+
EntryPointSelector: &msg.Selector,
69+
CallData: &calldata,
70+
Version: &felt.Zero, // Needed for transaction hash calculation.
71+
Nonce: &felt.Zero, // Needed for transaction hash calculation.
72+
},
73+
// Needed to marshal to blockifier type.
74+
// Must be greater than zero to successfully execute transaction.
75+
PaidFeeOnL1: felt.NewFromUint64[felt.Felt](1),
76+
}
77+
78+
bcTxn := [1]rpcv9.BroadcastedTransaction{tx}
79+
estimates, httpHeader, err := h.EstimateFee(
80+
rpccore.LimitSlice[rpcv9.BroadcastedTransaction, rpccore.SimulationLimit]{Data: bcTxn[:]},
81+
nil,
82+
id,
83+
)
84+
if err != nil {
85+
if err.Code == rpccore.ErrTransactionExecutionError.Code {
86+
data := err.Data.(rpcv9.TransactionExecutionErrorData)
87+
return rpcv9.FeeEstimate{}, httpHeader, rpcv9.MakeContractError(data.ExecutionError)
88+
}
89+
return rpcv9.FeeEstimate{}, httpHeader, err
90+
}
91+
return estimates[0], httpHeader, nil
92+
}

rpc/v10/estimate_fee_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package rpcv10_test
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/NethermindEth/juno/core"
8+
"github.com/NethermindEth/juno/core/felt"
9+
"github.com/NethermindEth/juno/jsonrpc"
10+
"github.com/NethermindEth/juno/mocks"
11+
"github.com/NethermindEth/juno/rpc/rpccore"
12+
rpcv10 "github.com/NethermindEth/juno/rpc/v10"
13+
rpcv6 "github.com/NethermindEth/juno/rpc/v6"
14+
rpcv9 "github.com/NethermindEth/juno/rpc/v9"
15+
"github.com/NethermindEth/juno/utils"
16+
"github.com/NethermindEth/juno/vm"
17+
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
19+
"go.uber.org/mock/gomock"
20+
)
21+
22+
func TestEstimateFee(t *testing.T) {
23+
mockCtrl := gomock.NewController(t)
24+
defer mockCtrl.Finish()
25+
26+
n := &utils.Mainnet
27+
28+
mockReader := mocks.NewMockReader(mockCtrl)
29+
mockReader.EXPECT().Network().Return(n).AnyTimes()
30+
mockVM := mocks.NewMockVM(mockCtrl)
31+
log := utils.NewNopZapLogger()
32+
handler := rpcv10.New(mockReader, nil, mockVM, log)
33+
34+
mockState := mocks.NewMockStateHistoryReader(mockCtrl)
35+
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil).AnyTimes()
36+
mockReader.EXPECT().HeadsHeader().Return(&core.Header{}, nil).AnyTimes()
37+
38+
blockID := rpcv9.BlockIDLatest()
39+
40+
blockInfo := vm.BlockInfo{Header: &core.Header{}}
41+
t.Run("ok with zero values", func(t *testing.T) {
42+
mockVM.EXPECT().Execute(
43+
[]core.Transaction{},
44+
nil,
45+
[]*felt.Felt{},
46+
&blockInfo,
47+
mockState,
48+
true,
49+
false,
50+
true, true, true, true).
51+
Return(vm.ExecutionResults{
52+
OverallFees: []*felt.Felt{},
53+
DataAvailability: []core.DataAvailability{},
54+
GasConsumed: []core.GasConsumed{},
55+
Traces: []vm.TransactionTrace{},
56+
NumSteps: uint64(123),
57+
}, nil)
58+
59+
_, httpHeader, err := handler.EstimateFee(
60+
rpcv10.BroadcastedTransactionInputs{},
61+
[]rpcv6.SimulationFlag{},
62+
&blockID,
63+
)
64+
require.Nil(t, err)
65+
assert.Equal(t, httpHeader.Get(rpcv10.ExecutionStepsHeader), "123")
66+
})
67+
68+
t.Run("ok with zero values, skip validate", func(t *testing.T) {
69+
mockVM.EXPECT().Execute(
70+
[]core.Transaction{},
71+
nil,
72+
[]*felt.Felt{},
73+
&blockInfo,
74+
mockState,
75+
true,
76+
true,
77+
true, true, true, true).
78+
Return(
79+
vm.ExecutionResults{
80+
OverallFees: []*felt.Felt{},
81+
DataAvailability: []core.DataAvailability{},
82+
GasConsumed: []core.GasConsumed{},
83+
Traces: []vm.TransactionTrace{},
84+
NumSteps: uint64(123),
85+
},
86+
nil,
87+
)
88+
89+
_, httpHeader, err := handler.EstimateFee(
90+
rpcv10.BroadcastedTransactionInputs{},
91+
[]rpcv6.SimulationFlag{rpcv6.SkipValidateFlag},
92+
&blockID,
93+
)
94+
require.Nil(t, err)
95+
assert.Equal(t, httpHeader.Get(rpcv10.ExecutionStepsHeader), "123")
96+
})
97+
98+
t.Run("transaction execution error", func(t *testing.T) {
99+
mockVM.EXPECT().Execute(
100+
[]core.Transaction{},
101+
nil,
102+
[]*felt.Felt{},
103+
&blockInfo,
104+
mockState,
105+
true,
106+
true,
107+
true, true, true, true).
108+
Return(vm.ExecutionResults{}, vm.TransactionExecutionError{
109+
Index: 44,
110+
Cause: json.RawMessage("oops"),
111+
})
112+
113+
_, httpHeader, err := handler.EstimateFee(
114+
rpcv10.BroadcastedTransactionInputs{},
115+
[]rpcv6.SimulationFlag{rpcv6.SkipValidateFlag},
116+
&blockID,
117+
)
118+
require.Equal(t, rpccore.ErrTransactionExecutionError.CloneWithData(
119+
rpcv9.TransactionExecutionErrorData{
120+
TransactionIndex: 44,
121+
ExecutionError: json.RawMessage("oops"),
122+
}), err)
123+
require.Equal(t, httpHeader.Get(rpcv10.ExecutionStepsHeader), "0")
124+
})
125+
126+
t.Run("transaction with invalid contract class", func(t *testing.T) {
127+
toFelt := func(hex string) *felt.Felt {
128+
return felt.NewUnsafeFromString[felt.Felt](hex)
129+
}
130+
invalidTx := rpcv9.BroadcastedTransaction{
131+
Transaction: rpcv9.Transaction{
132+
Type: rpcv9.TxnDeclare,
133+
Version: toFelt("0x1"),
134+
Nonce: toFelt("0x0"),
135+
MaxFee: toFelt("0x1"),
136+
SenderAddress: toFelt("0x2"),
137+
Signature: &[]*felt.Felt{
138+
toFelt("0x123"),
139+
},
140+
},
141+
ContractClass: json.RawMessage(`{}`),
142+
}
143+
_, _, err := handler.EstimateFee(
144+
rpcv10.BroadcastedTransactionInputs{Data: []rpcv9.BroadcastedTransaction{invalidTx}},
145+
[]rpcv6.SimulationFlag{},
146+
&blockID,
147+
)
148+
expectedErr := &jsonrpc.Error{
149+
Code: jsonrpc.InvalidParams,
150+
Message: "Invalid Params",
151+
Data: "invalid program",
152+
}
153+
require.Equal(t, expectedErr, err)
154+
})
155+
}

rpc/v10/simulation.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ func (h *Handler) SimulateTransactions(
4343
transactions BroadcastedTransactionInputs,
4444
simulationFlags []rpcv6.SimulationFlag,
4545
) ([]SimulatedTransaction, http.Header, *jsonrpc.Error) {
46-
return h.simulateTransactions(id, transactions.Data, simulationFlags, false)
46+
return h.simulateTransactions(id, transactions.Data, simulationFlags, false, false)
4747
}
4848

4949
func (h *Handler) simulateTransactions(
5050
id *rpcv9.BlockID,
5151
transactions []rpcv9.BroadcastedTransaction,
5252
simulationFlags []rpcv6.SimulationFlag,
5353
errOnRevert bool,
54+
isEstimateFee bool,
5455
) ([]SimulatedTransaction, http.Header, *jsonrpc.Error) {
5556
skipFeeCharge := slices.Contains(simulationFlags, rpcv6.SkipFeeChargeFlag)
5657
skipValidate := slices.Contains(simulationFlags, rpcv6.SkipValidateFlag)
@@ -95,7 +96,7 @@ func (h *Handler) simulateTransactions(
9596
errOnRevert,
9697
true,
9798
true,
98-
false,
99+
isEstimateFee,
99100
)
100101
if err != nil {
101102
return nil, httpHeader, handleExecutionError(err)

rpc/v9/simulation.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ func (h *Handler) SimulateTransactions(
4646
return h.simulateTransactions(id, transactions.Data, simulationFlags, false, false)
4747
}
4848

49-
func (h *Handler) simulateTransactions(id *BlockID, transactions []BroadcastedTransaction,
50-
simulationFlags []rpcv6.SimulationFlag, errOnRevert bool, isEstimateFee bool,
49+
func (h *Handler) simulateTransactions(
50+
id *BlockID,
51+
transactions []BroadcastedTransaction,
52+
simulationFlags []rpcv6.SimulationFlag,
53+
errOnRevert bool,
54+
isEstimateFee bool,
5155
) ([]SimulatedTransaction, http.Header, *jsonrpc.Error) {
5256
skipFeeCharge := slices.Contains(simulationFlags, rpcv6.SkipFeeChargeFlag)
5357
skipValidate := slices.Contains(simulationFlags, rpcv6.SkipValidateFlag)

0 commit comments

Comments
 (0)