Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions internal/tezos/prepare_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -98,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)
}
}
Expand All @@ -108,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()

Expand Down
37 changes: 37 additions & 0 deletions internal/tezos/prepare_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down