From f993b6b683d3e7e5a45f582cfccb226595736138 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:03:56 +0000 Subject: [PATCH 1/3] Initial plan From a2e000820bbf052b182cbb42c39d617ad5257714 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:16:08 +0000 Subject: [PATCH 2/3] Fix: guard costs[i] access in estimateAndAssignTxCost to prevent panic on incomplete sim receipt Co-authored-by: alex-semenyuk <5480441+alex-semenyuk@users.noreply.github.com> --- internal/tezos/prepare_transaction.go | 6 ++++ internal/tezos/prepare_transaction_test.go | 37 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/internal/tezos/prepare_transaction.go b/internal/tezos/prepare_transaction.go index cfdb69f..d0c4186 100644 --- a/internal/tezos/prepare_transaction.go +++ b/internal/tezos/prepare_transaction.go @@ -65,6 +65,12 @@ func (c *tezosConnector) estimateAndAssignTxCost(ctx context.Context, op *codec. verb = "forced" } limits := v.Limits() + if i >= len(costs) { + log.L(ctx).Debugf("OP#%03d: %s fee(%s)=%d gas_limit(%s)=%d storage_limit(%s)=%d (no simulation cost data)", + i, v.Kind(), verb, limits.Fee, verb, limits.GasLimit, verb, limits.StorageLimit, + ) + continue + } log.L(ctx).Debugf("OP#%03d: %s gas_used(sim)=%d storage_used(sim)=%d storage_burn(sim)=%d alloc_burn(sim)=%d fee(%s)=%d gas_limit(%s)=%d storage_limit(%s)=%d ", i, v.Kind(), costs[i].GasUsed, costs[i].StorageUsed, costs[i].StorageBurn, costs[i].AllocationBurn, verb, limits.Fee, verb, limits.GasLimit, verb, limits.StorageLimit, diff --git a/internal/tezos/prepare_transaction_test.go b/internal/tezos/prepare_transaction_test.go index 043094b..a0b82c2 100644 --- a/internal/tezos/prepare_transaction_test.go +++ b/internal/tezos/prepare_transaction_test.go @@ -421,6 +421,43 @@ func TestTransactionPrepareWithRevealEmptyServerError(t *testing.T) { assert.Nil(t, resp) } +func Test_estimateAndAssignTxCostFewerSimResultsThanOpContents(t *testing.T) { + ctx, c, mRPC, done := newTestConnector(t) + defer done() + + // Simulate returns only one result, but the op has two contents entries. + // The bounds check should prevent an index-out-of-bounds panic for the second entry. + mRPC.On("Simulate", ctx, mock.Anything, mock.Anything). + Return(&rpc.Receipt{ + Op: &rpc.Operation{ + Contents: []rpc.TypedOperation{ + rpc.Transaction{ + Manager: rpc.Manager{ + Generic: rpc.Generic{ + Metadata: rpc.OperationMetadata{ + Result: rpc.OperationResult{ + Status: tezos.OpStatusApplied, + }, + }, + }, + }, + }, + }, + }, + }, nil) + + op := codec.NewOp() + txArgs := contract.TxArgs{} + op.WithContents(txArgs.Encode()) + op.WithContents(txArgs.Encode()) // second entry has no corresponding simulation cost + + opts := &rpc.DefaultOptions + opts.IgnoreLimits = true + + _, err := c.estimateAndAssignTxCost(ctx, op, opts) + assert.NoError(t, err) +} + func Test_getNetworkParamsByName(t *testing.T) { params := getNetworkParamsByName("ghostnet") assert.Equal(t, params, tezos.GhostnetParams) From 6ac68c5d61d3ae1115bdf36e73e3f01fd4e9e5ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Aug 2026 06:06:09 +0000 Subject: [PATCH 3/3] Fix stack overflow under Go 1.27: bypass (*micheline.Parameters).UnmarshalJSON recursion Co-authored-by: alex-semenyuk <5480441+alex-semenyuk@users.noreply.github.com> --- internal/tezos/prepare_transaction.go | 39 +++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/internal/tezos/prepare_transaction.go b/internal/tezos/prepare_transaction.go index d0c4186..040cbef 100644 --- a/internal/tezos/prepare_transaction.go +++ b/internal/tezos/prepare_transaction.go @@ -104,8 +104,7 @@ func (c *tezosConnector) prepareInputParams(ctx context.Context, req *ffcapi.Tra for i, p := range req.Params { if p != nil { - err := tezosParams.UnmarshalJSON([]byte(*p)) - if err != nil { + if err := unmarshalParameters([]byte(*p), &tezosParams); err != nil { return tezosParams, i18n.NewError(ctx, msgs.MsgUnmarshalParamFail, i, err) } } @@ -114,6 +113,42 @@ func (c *tezosConnector) prepareInputParams(ctx context.Context, req *ffcapi.Tra return tezosParams, nil } +// unmarshalParameters parses micheline.Parameters from JSON, replicating the +// logic of (*micheline.Parameters).UnmarshalJSON but avoiding infinite +// recursion under Go 1.27+. In Go 1.27, encoding/json/v2 resolves the method +// set of a pointer-alias's underlying type, so the original +// +// type alias *Parameters; json.Unmarshal(data, alias(p)) +// +// pattern inside (*Parameters).UnmarshalJSON calls itself recursively until +// the stack overflows. Using a plain struct alias breaks the method-set chain. +func unmarshalParameters(data []byte, p *micheline.Parameters) error { + if len(data) == 0 { + return nil + } + if data[0] == '[' { + // non-entrypoint calling convention: value only + return json.Unmarshal(data, &p.Value) + } + // entrypoint calling convention: {"entrypoint": "...", "value": {...}} + type paramsAlias struct { + Entrypoint string `json:"entrypoint"` + Value micheline.Prim `json:"value"` + } + var alias paramsAlias + if err := json.Unmarshal(data, &alias); err != nil { + return err + } + p.Entrypoint = alias.Entrypoint + p.Value = alias.Value + if p.Value.IsValid() { + return nil + } + // legacy calling convention: bare prim value without entrypoint wrapper + p.Entrypoint = "default" + return json.Unmarshal(data, &p.Value) +} + func (c *tezosConnector) buildOp(ctx context.Context, params micheline.Parameters, fromString, toString string, nonce *fftypes.FFBigInt) (*codec.Op, error) { op := codec.NewOp()