github.com/mavryk-network/mvgo@v1.19.9/codec/op.go (about)

     1  // Copyright (c) 2020-2023 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package codec
     5  
     6  import (
     7  	"bytes"
     8  	"encoding"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"strconv"
    13  
    14  	"github.com/mavryk-network/mvgo/mavryk"
    15  	"github.com/mavryk-network/mvgo/micheline"
    16  )
    17  
    18  const (
    19  	EmmyBlockWatermark                byte = 0x01 // deprecated
    20  	EmmyEndorsementWatermark          byte = 0x02 // deprecated
    21  	OperationWatermark                byte = 0x03
    22  	TenderbakeBlockWatermark          byte = 0x11
    23  	TenderbakePreendorsementWatermark byte = 0x12
    24  	TenderbakeEndorsementWatermark    byte = 0x13
    25  )
    26  
    27  var (
    28  	// enc defines the default wire encoding used for Tezos messages
    29  	enc = binary.BigEndian
    30  )
    31  
    32  // Operation is a generic type used to handle different Tezos operation
    33  // types inside an operation's contents list.
    34  type Operation interface {
    35  	Kind() mavryk.OpType
    36  	Limits() mavryk.Limits
    37  	GetCounter() int64
    38  	WithSource(mavryk.Address)
    39  	WithCounter(int64)
    40  	WithLimits(mavryk.Limits)
    41  	encoding.BinaryMarshaler
    42  	encoding.BinaryUnmarshaler
    43  	MarshalJSON() ([]byte, error)
    44  	EncodeBuffer(buf *bytes.Buffer, p *mavryk.Params) error
    45  	DecodeBuffer(buf *bytes.Buffer, p *mavryk.Params) error
    46  }
    47  
    48  // Op is a container used to collect, serialize and sign Tezos operations.
    49  // It serves as a low level building block for constructing and serializing
    50  // operations, but is agnostic to the order/lifecycle in which data is added
    51  // or updated.
    52  type Op struct {
    53  	Branch    mavryk.BlockHash    `json:"branch"`    // used for TTL handling
    54  	Contents  []Operation         `json:"contents"`  // non-zero list of transactions
    55  	Signature mavryk.Signature    `json:"signature"` // added during the lifecycle
    56  	ChainId   *mavryk.ChainIdHash `json:"-"`         // optional, used for remote signing only
    57  	TTL       int64               `json:"-"`         // optional, specify TTL in blocks
    58  	Params    *mavryk.Params      `json:"-"`         // optional, define protocol to encode for
    59  	Source    mavryk.Address      `json:"-"`         // optional, used as manager/sender
    60  }
    61  
    62  // NewOp creates a new empty operation that uses default params and a
    63  // default operation TTL.
    64  func NewOp() *Op {
    65  	return &Op{
    66  		Params: mavryk.DefaultParams,
    67  		TTL:    mavryk.DefaultParams.MaxOperationsTTL - 2, // Ithaca recommendation
    68  	}
    69  }
    70  
    71  // NeedCounter returns true if any of the contained operations has not assigned
    72  // a valid counter value.
    73  func (o Op) NeedCounter() bool {
    74  	for _, v := range o.Contents {
    75  		if v.GetCounter() == 0 {
    76  			return true
    77  		}
    78  	}
    79  	return false
    80  }
    81  
    82  // WithParams defines the protocol and other chain configuration params for which
    83  // the operation will be encoded. If unset, defaults to mavryk.DefaultParams.
    84  func (o *Op) WithParams(p *mavryk.Params) *Op {
    85  	o.Params = p
    86  	return o
    87  }
    88  
    89  // WithContents adds a Tezos operation to the end of the contents list.
    90  func (o *Op) WithContents(op Operation) *Op {
    91  	o.Contents = append(o.Contents, op)
    92  	return o
    93  }
    94  
    95  // WithContentsFront adds a Tezos operation to the front of the contents list.
    96  func (o *Op) WithContentsFront(op Operation) *Op {
    97  	o.Contents = append([]Operation{op}, o.Contents...)
    98  	return o
    99  }
   100  
   101  // WithSource sets the source for all manager operations to addr. It is required
   102  // before calling other WithXXX functions.
   103  func (o *Op) WithSource(addr mavryk.Address) *Op {
   104  	for _, v := range o.Contents {
   105  		v.WithSource(addr)
   106  	}
   107  	o.Source = addr
   108  	return o
   109  }
   110  
   111  // WithTransfer adds a simple value transfer transaction to the contents list.
   112  // Source must be defined via WithSource() before calling this function.
   113  func (o *Op) WithTransfer(to mavryk.Address, amount int64) *Op {
   114  	o.Contents = append(o.Contents, &Transaction{
   115  		Manager: Manager{
   116  			Source:  o.Source,
   117  			Counter: 0,
   118  		},
   119  		Amount:      mavryk.N(amount),
   120  		Destination: to,
   121  	})
   122  	return o
   123  }
   124  
   125  // WithCall adds a contract call transaction to the contents list.
   126  // Source must be defined via WithSource() before calling this function.
   127  func (o *Op) WithCall(to mavryk.Address, params micheline.Parameters) *Op {
   128  	o.Contents = append(o.Contents, &Transaction{
   129  		Manager: Manager{
   130  			Source:  o.Source,
   131  			Counter: 0,
   132  		},
   133  		Destination: to,
   134  		Parameters:  &params,
   135  	})
   136  	return o
   137  }
   138  
   139  // WithCallExt adds a contract call with value transfer transaction to the contents list.
   140  // Source must be defined via WithSource() before calling this function.
   141  func (o *Op) WithCallExt(to mavryk.Address, params micheline.Parameters, amount int64) *Op {
   142  	o.Contents = append(o.Contents, &Transaction{
   143  		Manager: Manager{
   144  			Source:  o.Source,
   145  			Counter: 0,
   146  		},
   147  		Amount:      mavryk.N(amount),
   148  		Destination: to,
   149  		Parameters:  &params,
   150  	})
   151  	return o
   152  }
   153  
   154  // WithOrigination adds a contract origination transaction to the contents list.
   155  // Source must be defined via WithSource() before calling this function.
   156  func (o *Op) WithOrigination(script micheline.Script) *Op {
   157  	o.Contents = append(o.Contents, &Origination{
   158  		Manager: Manager{
   159  			Source:  o.Source,
   160  			Counter: 0,
   161  		},
   162  		Script: script,
   163  	})
   164  	return o
   165  }
   166  
   167  // WithOriginationExt adds a contract origination transaction with optional delegation to
   168  // baker and an optional value transfer to the contents list.
   169  // Source must be defined via WithSource() before calling this function.
   170  func (o *Op) WithOriginationExt(script micheline.Script, baker mavryk.Address, amount int64) *Op {
   171  	o.Contents = append(o.Contents, &Origination{
   172  		Manager: Manager{
   173  			Source:  o.Source,
   174  			Counter: 0,
   175  		},
   176  		Balance:  mavryk.N(amount),
   177  		Delegate: baker,
   178  		Script:   script,
   179  	})
   180  	return o
   181  }
   182  
   183  // WithDelegation adds a delegation transaction to the contents list.
   184  // Source must be defined via WithSource() before calling this function.
   185  func (o *Op) WithDelegation(to mavryk.Address) *Op {
   186  	o.Contents = append(o.Contents, &Delegation{
   187  		Manager: Manager{
   188  			Source:  o.Source,
   189  			Counter: 0,
   190  		},
   191  		Delegate: to,
   192  	})
   193  	return o
   194  }
   195  
   196  // WithUndelegation adds a delegation transaction that resets the callers baker to null
   197  // to the contents list.
   198  // Source must be defined via WithSource() before calling this function.
   199  func (o *Op) WithUndelegation() *Op {
   200  	o.Contents = append(o.Contents, &Delegation{
   201  		Manager: Manager{
   202  			Source:  o.Source,
   203  			Counter: 0,
   204  		},
   205  	})
   206  	return o
   207  }
   208  
   209  // WithRegisterBaker adds a delegation transaction that registers the caller
   210  // as baker to the contents list.
   211  // Source must be defined via WithSource() before calling this function.
   212  func (o *Op) WithRegisterBaker() *Op {
   213  	o.Contents = append(o.Contents, &Delegation{
   214  		Manager: Manager{
   215  			Source:  o.Source,
   216  			Counter: 0,
   217  		},
   218  		Delegate: o.Source,
   219  	})
   220  	return o
   221  }
   222  
   223  // WithSetBakerParams adds a set_delegate_parameters call where target is
   224  // source. The caller must be a registered baker.
   225  // Source must be defined via WithSource() before calling this function.
   226  func (o *Op) WithSetBakerParams(edge, limit int64) *Op {
   227  	return o.WithCall(
   228  		o.Source,
   229  		micheline.Parameters{
   230  			Entrypoint: micheline.SET_DELEGATE_PARAMETERS,
   231  			Value: micheline.NewCombPair(
   232  				micheline.NewInt64(edge),
   233  				micheline.NewInt64(limit),
   234  				micheline.Unit,
   235  			),
   236  		},
   237  	)
   238  }
   239  
   240  // WithStake sends a stake pseudo call to source to lock tokens for staking.
   241  // The caller must delegate to a baker and this baker is implictly chosen to
   242  // stake with.
   243  // Source must be defined via WithSource() before calling this function.
   244  func (o *Op) WithStake(amount int64) *Op {
   245  	return o.WithCallExt(
   246  		o.Source,
   247  		micheline.Parameters{
   248  			Entrypoint: micheline.STAKE,
   249  			Value:      micheline.Unit,
   250  		},
   251  		amount,
   252  	)
   253  }
   254  
   255  // WithUnstake sends an unstake pseudo call to source which creates an
   256  // unstake request for amount tokens.
   257  // Source must be defined via WithSource() before calling this function.
   258  func (o *Op) WithUnstake(amount int64) *Op {
   259  	return o.WithCallExt(
   260  		o.Source,
   261  		micheline.Parameters{
   262  			Entrypoint: micheline.UNSTAKE,
   263  			Value:      micheline.Unit,
   264  		},
   265  		amount,
   266  	)
   267  }
   268  
   269  // WithUnstakeAll sends an unstake pseudo call to source which creates an
   270  // unstake request for all currently staked tokens.
   271  // Source must be defined via WithSource() before calling this function.
   272  func (o *Op) WithUnstakeAll(amount int64) *Op {
   273  	return o.WithCallExt(
   274  		o.Source,
   275  		micheline.Parameters{
   276  			Entrypoint: micheline.UNSTAKE,
   277  			Value:      micheline.Unit,
   278  		},
   279  		9223372036854775807,
   280  	)
   281  }
   282  
   283  // WithFinalizeUnstake sends a finalize_unstake pseudo call to source which
   284  // moves all unfrozen unstaked tokens back to spendable balance.
   285  // Source must be defined via WithSource() before calling this function.
   286  func (o *Op) WithFinalizeUnstake() *Op {
   287  	return o.WithCall(
   288  		o.Source,
   289  		micheline.Parameters{
   290  			Entrypoint: micheline.FINALIZE_UNSTAKE,
   291  			Value:      micheline.Unit,
   292  		},
   293  	)
   294  }
   295  
   296  // WithRegisterConstant adds a global constant registration transaction to the contents list.
   297  // Source must be defined via WithSource() before calling this function.
   298  func (o *Op) WithRegisterConstant(value micheline.Prim) *Op {
   299  	o.Contents = append(o.Contents, &RegisterGlobalConstant{
   300  		Manager: Manager{
   301  			Source:  o.Source,
   302  			Counter: 0,
   303  		},
   304  		Value: value,
   305  	})
   306  	return o
   307  }
   308  
   309  // WithTTL sets a time-to-live for the operation in number of blocks. This may be
   310  // used as a convenience method instead of setting a branch directly, but requires
   311  // to use an autocomplete handler, wallet or custom function that fetches the hash
   312  // of block head~N as branch. Note that serialization will fail until a brach is set.
   313  func (o *Op) WithTTL(n int64) *Op {
   314  	if n > o.Params.MaxOperationsTTL {
   315  		n = o.Params.MaxOperationsTTL - 2 // Ithaca adjusted
   316  	} else if n < 0 {
   317  		n = 1
   318  	}
   319  	o.TTL = n
   320  	return o
   321  }
   322  
   323  // WithBranch sets the branch for this operation to hash.
   324  func (o *Op) WithBranch(hash mavryk.BlockHash) *Op {
   325  	o.Branch = hash
   326  	return o
   327  }
   328  
   329  // WithChainId sets chain_id for this operation to id. Use this only for remote signing
   330  // of (pre)endorsements as it creates an invalid binary encoding otherwise.
   331  func (o *Op) WithChainId(id mavryk.ChainIdHash) *Op {
   332  	clone := id.Clone()
   333  	o.ChainId = &clone
   334  	return o
   335  }
   336  
   337  // WithLimits sets the limits (fee, gas and storage limit) of each
   338  // contained operation to provided limits. Use this to apply values from
   339  // simulation with an optional safety margin on gas and storage. This will also
   340  // calculate the minFee for each operation in the list and add the minFee
   341  // for header bytes (branch and signature) to the first operation in a list.
   342  //
   343  // Setting a user-defined fee for each individual operation is only honored
   344  // when its higher than minFee. Note that when sending batch operations all
   345  // fees must be >= the individual minFee. Otherwise the minFee rule will
   346  // apply to all zero/lower fee operations and the entire batch may overpay
   347  // (e.g. if you have the first operation pay all fees for example and set
   348  // remaining fees to zero).
   349  func (o *Op) WithLimits(limits []mavryk.Limits, margin int64) *Op {
   350  	for i, v := range o.Contents {
   351  		if len(limits) < i {
   352  			continue
   353  		}
   354  		// apply simulated limit to get a better size estimate
   355  		v.WithLimits(limits[i])
   356  
   357  		// re-calculate limits with safety margins
   358  		gas := limits[i].GasLimit + margin
   359  		storage := limits[i].StorageLimit
   360  		if storage > 0 {
   361  			storage += margin
   362  		}
   363  		adj := mavryk.Limits{
   364  			GasLimit:     gas,
   365  			StorageLimit: storage,
   366  		}
   367  
   368  		// Apply limits, and re-compute the fee if needed.
   369  		// This is required, because fee value has an impact on operation size.
   370  		var lastFee int64 = -1
   371  		for lastFee < adj.Fee {
   372  			lastFee = adj.Fee
   373  
   374  			adj.Fee = max64(limits[i].Fee, CalculateMinFee(v, gas, i == 0, o.Params))
   375  			v.WithLimits(adj)
   376  		}
   377  	}
   378  	return o
   379  }
   380  
   381  func (o *Op) WithMinFee() *Op {
   382  	for i, v := range o.Contents {
   383  		// extend current limit with minimum fee estimate based on size + gas
   384  		lim := v.Limits()
   385  
   386  		adj := mavryk.Limits{
   387  			GasLimit:     lim.GasLimit,
   388  			StorageLimit: lim.StorageLimit,
   389  			Fee:          max64(lim.Fee, CalculateMinFee(v, lim.GasLimit, i == 0, o.Params)),
   390  		}
   391  
   392  		// use adjusted limits
   393  		v.WithLimits(adj)
   394  	}
   395  	return o
   396  }
   397  
   398  // Limits returns the sum of all limits (fee, gas, storage limit) currently
   399  // set for all contained operations.
   400  func (o Op) Limits() mavryk.Limits {
   401  	var l mavryk.Limits
   402  	for _, v := range o.Contents {
   403  		l = l.Add(v.Limits())
   404  	}
   405  	return l
   406  }
   407  
   408  // Bytes serializes the operation into binary form. When no signature is set, the
   409  // result can be used as input for signing, if a signature is set the result is
   410  // ready to be broadcast. Returns a nil slice when branch or contents are empty.
   411  func (o *Op) Bytes() []byte {
   412  	if len(o.Contents) == 0 || !o.Branch.IsValid() {
   413  		return nil
   414  	}
   415  	p := o.Params
   416  	if p == nil {
   417  		p = mavryk.DefaultParams
   418  	}
   419  	buf := bytes.NewBuffer(nil)
   420  	buf.Write(o.Branch.Bytes())
   421  	for _, v := range o.Contents {
   422  		_ = v.EncodeBuffer(buf, p)
   423  	}
   424  	switch o.Contents[0].Kind() {
   425  	case mavryk.OpTypeEndorsementWithSlot:
   426  		// no signature
   427  	default:
   428  		if o.Signature.IsValid() {
   429  			buf.Write(o.Signature.Data) // raw, without type (!)
   430  		}
   431  	}
   432  	return buf.Bytes()
   433  }
   434  
   435  // WatermarkedBytes serializes the operation and prefixes it with a watermark.
   436  // This format is only used for signing. Watermarked data is not useful anywhere
   437  // else.
   438  func (o *Op) WatermarkedBytes() []byte {
   439  	if len(o.Contents) == 0 || !o.Branch.IsValid() {
   440  		return nil
   441  	}
   442  	p := o.Params
   443  	if p == nil {
   444  		p = mavryk.DefaultParams
   445  	}
   446  	buf := bytes.NewBuffer(nil)
   447  	switch o.Contents[0].Kind() {
   448  	case mavryk.OpTypeEndorsement, mavryk.OpTypeEndorsementWithSlot:
   449  		if p.OperationTagsVersion < 2 {
   450  			buf.WriteByte(EmmyEndorsementWatermark)
   451  		} else {
   452  			buf.WriteByte(TenderbakeEndorsementWatermark)
   453  		}
   454  		if o.ChainId != nil {
   455  			buf.Write(o.ChainId.Bytes())
   456  		}
   457  	case mavryk.OpTypePreendorsement:
   458  		buf.WriteByte(TenderbakePreendorsementWatermark)
   459  		if o.ChainId != nil {
   460  			buf.Write(o.ChainId.Bytes())
   461  		}
   462  	default:
   463  		buf.WriteByte(OperationWatermark)
   464  	}
   465  	buf.Write(o.Branch.Bytes())
   466  	for _, v := range o.Contents {
   467  		_ = v.EncodeBuffer(buf, p)
   468  	}
   469  	return buf.Bytes()
   470  }
   471  
   472  // Digest returns a 32 byte blake2b hash for signing the operation. The pre-image
   473  // is the binary serialized operation (without signature) prefixed with a
   474  // type-dependent watermark byte.
   475  func (o *Op) Digest() []byte {
   476  	d := mavryk.Digest(o.WatermarkedBytes())
   477  	return d[:]
   478  }
   479  
   480  // WithSignature adds an externally created signature to the operation.
   481  // No signature validation is performed, it is assumed the signature is correct.
   482  func (o *Op) WithSignature(sig mavryk.Signature) *Op {
   483  	o.Signature = sig
   484  	return o
   485  }
   486  
   487  // Sign signs the operation using provided private key. If a valid signature
   488  // already exists this function is a noop. Fails when either branch or contents
   489  // are empty.
   490  func (o *Op) Sign(key mavryk.PrivateKey) error {
   491  	if !o.Branch.IsValid() {
   492  		return fmt.Errorf("tezos: missing branch")
   493  	}
   494  	if len(o.Contents) == 0 {
   495  		return fmt.Errorf("tezos: empty operation contents")
   496  	}
   497  	sig, err := key.Sign(o.Digest())
   498  	if err != nil {
   499  		return err
   500  	}
   501  	o.Signature = sig
   502  	return nil
   503  }
   504  
   505  // Hash calculates the operation hash. For the hash to be correct, the operation
   506  // must contain a valid signature.
   507  func (o *Op) Hash() (h mavryk.OpHash) {
   508  	d := mavryk.Digest(o.Bytes())
   509  	copy(h[:], d[:])
   510  	return
   511  }
   512  
   513  // MarshalJSON conditionally marshals the JSON format of the operation with checks
   514  // for required fields. Omits signature for unsigned ops so that the encoding is
   515  // compatible with remote forging.
   516  func (o *Op) MarshalJSON() ([]byte, error) {
   517  	buf := bytes.NewBuffer(nil)
   518  	buf.WriteByte('{')
   519  	buf.WriteString(`"branch":`)
   520  	buf.WriteString(strconv.Quote(o.Branch.String()))
   521  	buf.WriteString(`,"contents":[`)
   522  	for i, op := range o.Contents {
   523  		if i > 0 {
   524  			buf.WriteByte(',')
   525  		}
   526  		if b, err := op.MarshalJSON(); err != nil {
   527  			return nil, err
   528  		} else {
   529  			buf.Write(b)
   530  		}
   531  	}
   532  	buf.WriteByte(']')
   533  	sig := o.Signature
   534  	if len(o.Contents) > 0 && o.Contents[0].Kind() == mavryk.OpTypeEndorsementWithSlot {
   535  		// no signature
   536  		sig = mavryk.InvalidSignature
   537  	}
   538  	if sig.IsValid() {
   539  		buf.WriteString(`,"signature":`)
   540  		buf.WriteString(strconv.Quote(sig.String()))
   541  	}
   542  	buf.WriteByte('}')
   543  	return buf.Bytes(), nil
   544  }
   545  
   546  // DecodeOp decodes an operation from its binary representation. The encoded
   547  // data may or may not contain a signature.
   548  func DecodeOp(data []byte) (*Op, error) {
   549  	// check for shortest message
   550  	if len(data) < 32+5 {
   551  		return nil, io.ErrShortBuffer
   552  	}
   553  
   554  	// decode
   555  	buf := bytes.NewBuffer(data)
   556  	o := &Op{
   557  		Contents: make([]Operation, 0),
   558  		Params:   mavryk.DefaultParams,
   559  	}
   560  	if err := o.Branch.UnmarshalBinary(buf.Next(32)); err != nil {
   561  		return nil, err
   562  	}
   563  	for buf.Len() > 0 {
   564  		var op Operation
   565  		tag, _ := buf.ReadByte()
   566  		buf.UnreadByte()
   567  		switch mavryk.ParseOpTag(tag) {
   568  		case mavryk.OpTypeEndorsement:
   569  			if o.Params.OperationTagsVersion < 2 {
   570  				op = new(Endorsement)
   571  			} else {
   572  				op = new(TenderbakeEndorsement)
   573  			}
   574  		case mavryk.OpTypePreattestation:
   575  			op = new(TenderbakePreendorsement)
   576  		case mavryk.OpTypeEndorsementWithSlot:
   577  			op = new(EndorsementWithSlot)
   578  		case mavryk.OpTypeSeedNonceRevelation:
   579  			op = new(SeedNonceRevelation)
   580  		case mavryk.OpTypeDoubleAttestationEvidence:
   581  			if o.Params.OperationTagsVersion < 2 {
   582  				op = new(DoubleEndorsementEvidence)
   583  			} else {
   584  				op = new(TenderbakeDoubleEndorsementEvidence)
   585  			}
   586  		case mavryk.OpTypeDoublePreattestationEvidence:
   587  			op = new(TenderbakeDoublePreendorsementEvidence)
   588  		case mavryk.OpTypeDoubleBakingEvidence:
   589  			op = new(DoubleBakingEvidence)
   590  		case mavryk.OpTypeActivateAccount:
   591  			op = new(ActivateAccount)
   592  		case mavryk.OpTypeProposals:
   593  			op = new(Proposals)
   594  		case mavryk.OpTypeBallot:
   595  			op = new(Ballot)
   596  		case mavryk.OpTypeReveal:
   597  			op = new(Reveal)
   598  		case mavryk.OpTypeTransaction:
   599  			op = new(Transaction)
   600  		case mavryk.OpTypeOrigination:
   601  			op = new(Origination)
   602  		case mavryk.OpTypeDelegation:
   603  			op = new(Delegation)
   604  		case mavryk.OpTypeFailingNoop:
   605  			op = new(FailingNoop)
   606  		case mavryk.OpTypeRegisterConstant:
   607  			op = new(RegisterGlobalConstant)
   608  		case mavryk.OpTypeSetDepositsLimit:
   609  			op = new(SetDepositsLimit)
   610  		case mavryk.OpTypeTransferTicket:
   611  			op = new(TransferTicket)
   612  		case mavryk.OpTypeVdfRevelation:
   613  			op = new(VdfRevelation)
   614  		case mavryk.OpTypeIncreasePaidStorage:
   615  			op = new(IncreasePaidStorage)
   616  		case mavryk.OpTypeDrainDelegate:
   617  			op = new(DrainDelegate)
   618  		case mavryk.OpTypeUpdateConsensusKey:
   619  			op = new(UpdateConsensusKey)
   620  		case mavryk.OpTypeSmartRollupOriginate:
   621  			op = new(SmartRollupOriginate)
   622  		case mavryk.OpTypeSmartRollupAddMessages:
   623  			op = new(SmartRollupAddMessages)
   624  		case mavryk.OpTypeSmartRollupCement:
   625  			op = new(SmartRollupCement)
   626  		case mavryk.OpTypeSmartRollupPublish:
   627  			op = new(SmartRollupPublish)
   628  		// TODO
   629  		// case mavryk.OpTypeSmartRollupRefute:
   630  		// 	op = new(SmartRollupRefute)
   631  		case mavryk.OpTypeSmartRollupTimeout:
   632  			op = new(SmartRollupTimeout)
   633  		case mavryk.OpTypeSmartRollupExecuteOutboxMessage:
   634  			op = new(SmartRollupExecuteOutboxMessage)
   635  		case mavryk.OpTypeSmartRollupRecoverBond:
   636  			op = new(SmartRollupRecoverBond)
   637  		case mavryk.OpTypeDalPublishCommitment:
   638  			op = new(DalPublishCommitment)
   639  
   640  		default:
   641  			// stop if rest looks like a signature
   642  			// FIXME: BLS sigs are 96 bytes, but accepting this here will
   643  			// collide with detecting valid operation types in a batch
   644  			if buf.Len() == 64 {
   645  				break
   646  			}
   647  			return nil, fmt.Errorf("tezos: unsupported operation tag %d", tag)
   648  		}
   649  		if err := op.DecodeBuffer(buf, mavryk.DefaultParams); err != nil {
   650  			return nil, err
   651  		}
   652  		o.Contents = append(o.Contents, op)
   653  	}
   654  
   655  	if buf.Len() > 0 {
   656  		// FIXME: BLS sigs are 96 byte
   657  		if err := o.Signature.UnmarshalBinary(buf.Next(64)); err != nil {
   658  			return nil, err
   659  		}
   660  	}
   661  	return o, nil
   662  }