Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
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
7 changes: 5 additions & 2 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,8 +918,11 @@ func (m callMsg) IsL1MessageTx() bool { return false }
func (m callMsg) SetCodeAuthorizations() []types.SetCodeAuthorization {
return m.CallMsg.AuthorizationList
}
func (m callMsg) FeeTokenID() uint16 { return m.CallMsg.FeeTokenID }
func (m callMsg) FeeLimit() *big.Int { return m.CallMsg.FeeLimit }
func (m callMsg) FeeTokenID() uint16 { return m.CallMsg.FeeTokenID }
func (m callMsg) FeeLimit() *big.Int { return m.CallMsg.FeeLimit }
func (m callMsg) Version() uint8 { return m.CallMsg.Version }
func (m callMsg) Reference() *common.Reference { return m.CallMsg.Reference }
func (m callMsg) Memo() *[]byte { return m.CallMsg.Memo }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
78 changes: 72 additions & 6 deletions accounts/abi/bind/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ type TransactOpts struct {
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)

FeeTokenID uint16 // alt fee token id of transaction execution
FeeLimit *big.Int // alt fee token limit of transaction execution
FeeTokenID uint16 // alt fee token id of transaction execution
FeeLimit *big.Int // alt fee token limit of transaction execution
Version *uint8 // version of morph tx (nil = auto-detect)
Reference *common.Reference // reference key for the transaction (optional)
Memo *[]byte // memo for the transaction (optional)

Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)

Expand Down Expand Up @@ -290,12 +293,19 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add
return types.NewTx(baseTx), nil
}

func (c *BoundContract) createAltFeeTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) {
func (c *BoundContract) createMorphTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) {
// Normalize value
value := opts.Value
if value == nil {
value = new(big.Int)
}

// Determine version and validate fields
version, err := c.morphTxVersion(opts)
if err != nil {
return nil, err
}

// Estimate TipCap
gasTipCap := opts.GasTipCap
if gasTipCap == nil {
Expand Down Expand Up @@ -334,20 +344,73 @@ func (c *BoundContract) createAltFeeTx(opts *TransactOpts, contract *common.Addr
if err != nil {
return nil, err
}
baseTx := &types.AltFeeTx{
baseTx := &types.MorphTx{
To: contract,
Nonce: nonce,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
FeeTokenID: opts.FeeTokenID,
FeeLimit: opts.FeeLimit,
Gas: gasLimit,
Version: version,
Reference: opts.Reference,
Memo: opts.Memo,
Value: value,
Data: input,
}
return types.NewTx(baseTx), nil
}

// morphTxVersion determines the MorphTx version and validates field requirements.
// If version is explicitly specified, validate that parameters match:
// - Version 0: FeeTokenID must be > 0, Reference and Memo must not be set
// - Version 1: FeeTokenID, Reference, Memo are all optional;
// if FeeTokenID is 0, FeeLimit must not be set
//
// If version is not explicitly specified, use heuristic detection:
// - V1 if V1-specific fields (Reference, Memo) are present
// - V0 otherwise (backward compatible with AltFeeTx behavior)
func (c *BoundContract) morphTxVersion(opts *TransactOpts) (uint8, error) {
// Validate memo length
if opts.Memo != nil && len(*opts.Memo) > common.MaxMemoLength {
return 0, types.ErrMemoTooLong
}

// If version is not explicitly specified, determine based on fields:
// - V1 if V1-specific fields (Reference, Memo) are present
// - V0 otherwise (backward compatible with AltFeeTx behavior)
if opts.Version == nil {
hasV1Fields := (opts.Reference != nil && *opts.Reference != (common.Reference{})) ||
(opts.Memo != nil && len(*opts.Memo) > 0)
if hasV1Fields {
if opts.FeeTokenID == 0 && opts.FeeLimit != nil && opts.FeeLimit.Sign() != 0 {
return 0, types.ErrMorphTxV1IllegalExtraParams
}
return types.MorphTxVersion1, nil
}
return types.MorphTxVersion0, nil
}

// Version explicitly specified - validate parameters match
version := *opts.Version
switch version {
case types.MorphTxVersion0:
if opts.FeeTokenID == 0 ||
opts.Reference != nil && *opts.Reference != (common.Reference{}) ||
opts.Memo != nil && len(*opts.Memo) > 0 {
return 0, types.ErrMorphTxV0IllegalExtraParams
}
case types.MorphTxVersion1:
if opts.FeeTokenID == 0 && opts.FeeLimit != nil && opts.FeeLimit.Sign() != 0 {
return 0, types.ErrMorphTxV1IllegalExtraParams
}
Comment thread
SegueII marked this conversation as resolved.
default:
return 0, types.ErrMorphTxUnsupportedVersion
}

return version, nil
}

func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
if opts.GasFeeCap != nil || opts.GasTipCap != nil {
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but curie is not active yet")
Expand Down Expand Up @@ -438,8 +501,11 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil {
return nil, errHead
} else if head.BaseFee != nil {
if opts.FeeTokenID != 0 {
rawTx, err = c.createAltFeeTx(opts, contract, input, head)
if opts.FeeTokenID != 0 ||
opts.Version != nil ||
(opts.Reference != nil && *opts.Reference != (common.Reference{})) ||
(opts.Memo != nil && len(*opts.Memo) > 0) {
rawTx, err = c.createMorphTx(opts, contract, input, head)
} else {
rawTx, err = c.createDynamicTx(opts, contract, input, head)
}
Expand Down
183 changes: 183 additions & 0 deletions accounts/abi/bind/morph_tx_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package bind

import (
"math/big"
"testing"

"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/core/types"
)

func versionPtr(v uint8) *uint8 {
return &v
}

func refTestPtr(r common.Reference) *common.Reference {
return &r
}

func memoTestPtr(b []byte) *[]byte {
return &b
}

// TestMorphTxVersion_HeuristicDefault tests the heuristic version defaulting logic
// in morphTxVersion():
// - Version == nil + no V1 fields → V0
// - Version == nil + Reference or Memo → V1
// - Explicit Version → use as-is (with validation)
func TestMorphTxVersion_HeuristicDefault(t *testing.T) {
ref := common.HexToReference("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
emptyRef := common.Reference{}
memo := []byte("test memo")
emptyMemo := []byte{}

bc := &BoundContract{} // morphTxVersion doesn't use BoundContract fields

tests := []struct {
name string
opts *TransactOpts
wantVersion uint8
wantErr error
}{
// === Heuristic defaults (Version == nil) ===
{
name: "nil Version, FeeTokenID > 0, no V1 fields → V0",
opts: &TransactOpts{FeeTokenID: 1},
wantVersion: types.MorphTxVersion0,
},
{
name: "nil Version, FeeTokenID > 0, with Reference → V1",
opts: &TransactOpts{FeeTokenID: 1, Reference: refTestPtr(ref)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, FeeTokenID > 0, with Memo → V1",
opts: &TransactOpts{FeeTokenID: 1, Memo: memoTestPtr(memo)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, FeeTokenID > 0, with Reference + Memo → V1",
opts: &TransactOpts{FeeTokenID: 1, Reference: refTestPtr(ref), Memo: memoTestPtr(memo)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, FeeTokenID = 0, with Reference → V1",
opts: &TransactOpts{FeeTokenID: 0, Reference: refTestPtr(ref)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, FeeTokenID = 0, with Memo → V1",
opts: &TransactOpts{FeeTokenID: 0, Memo: memoTestPtr(memo)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, FeeTokenID = 0, no V1 fields → V0",
opts: &TransactOpts{FeeTokenID: 0},
wantVersion: types.MorphTxVersion0,
},
{
name: "nil Version, empty Reference → V0",
opts: &TransactOpts{FeeTokenID: 1, Reference: refTestPtr(emptyRef)},
wantVersion: types.MorphTxVersion0,
},
{
name: "nil Version, empty Memo → V0",
opts: &TransactOpts{FeeTokenID: 1, Memo: memoTestPtr(emptyMemo)},
wantVersion: types.MorphTxVersion0,
},
{
name: "nil Version, nil Reference, nil Memo → V0",
opts: &TransactOpts{FeeTokenID: 1, Reference: nil, Memo: nil},
wantVersion: types.MorphTxVersion0,
},

// === V1 heuristic with illegal params ===
{
name: "nil Version, Reference + FeeTokenID=0 + FeeLimit > 0 → error",
opts: &TransactOpts{FeeTokenID: 0, FeeLimit: big.NewInt(100), Reference: refTestPtr(ref)},
wantErr: types.ErrMorphTxV1IllegalExtraParams,
},
{
name: "nil Version, Reference + FeeTokenID=0 + FeeLimit=0 → V1 (ok)",
opts: &TransactOpts{FeeTokenID: 0, FeeLimit: big.NewInt(0), Reference: refTestPtr(ref)},
wantVersion: types.MorphTxVersion1,
},
{
name: "nil Version, Reference + FeeTokenID=0 + nil FeeLimit → V1 (ok)",
opts: &TransactOpts{FeeTokenID: 0, FeeLimit: nil, Reference: refTestPtr(ref)},
wantVersion: types.MorphTxVersion1,
},

// === Explicit Version ===
{
name: "explicit V0, FeeTokenID > 0 → V0",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion0), FeeTokenID: 1},
wantVersion: types.MorphTxVersion0,
},
{
name: "explicit V1, no special fields → V1",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion1)},
wantVersion: types.MorphTxVersion1,
},
{
name: "explicit V1, with Reference + Memo → V1",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion1), Reference: refTestPtr(ref), Memo: memoTestPtr(memo)},
wantVersion: types.MorphTxVersion1,
},
{
name: "explicit V0, FeeTokenID = 0 → error (V0 requires FeeTokenID > 0)",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion0), FeeTokenID: 0},
wantErr: types.ErrMorphTxV0IllegalExtraParams,
},
{
name: "explicit V0, with Reference → error",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion0), FeeTokenID: 1, Reference: refTestPtr(ref)},
wantErr: types.ErrMorphTxV0IllegalExtraParams,
},
{
name: "explicit V0, with Memo → error",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion0), FeeTokenID: 1, Memo: memoTestPtr(memo)},
wantErr: types.ErrMorphTxV0IllegalExtraParams,
},
{
name: "explicit V1, FeeTokenID=0 + FeeLimit>0 → error",
opts: &TransactOpts{Version: versionPtr(types.MorphTxVersion1), FeeTokenID: 0, FeeLimit: big.NewInt(100)},
wantErr: types.ErrMorphTxV1IllegalExtraParams,
},
{
name: "unsupported version 255 → error",
opts: &TransactOpts{Version: versionPtr(255)},
wantErr: types.ErrMorphTxUnsupportedVersion,
},

// === Memo length validation ===
{
name: "memo too long → error",
opts: &TransactOpts{FeeTokenID: 1, Memo: memoTestPtr(make([]byte, common.MaxMemoLength+1))},
wantErr: types.ErrMemoTooLong,
},
{
name: "memo at max length → ok",
opts: &TransactOpts{FeeTokenID: 1, Memo: memoTestPtr(make([]byte, common.MaxMemoLength))},
wantVersion: types.MorphTxVersion1, // non-empty memo → V1
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
version, err := bc.morphTxVersion(tt.opts)
if tt.wantErr != nil {
if err != tt.wantErr {
t.Errorf("error: got %v, want %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if version != tt.wantVersion {
t.Errorf("version: got %d, want %d", version, tt.wantVersion)
}
})
}
}
9 changes: 8 additions & 1 deletion accounts/external/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,19 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
case types.DynamicFeeTxType, types.SetCodeTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
case types.AltFeeTxType:
case types.MorphTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
feeTokenID := hexutil.Uint16(tx.FeeTokenID())
args.FeeTokenID = &feeTokenID
args.FeeLimit = (*hexutil.Big)(tx.FeeLimit())
version := hexutil.Uint64(tx.Version())
args.Version = &version
args.Reference = tx.Reference()
if tx.Memo() != nil {
memo := hexutil.Bytes(*tx.Memo())
args.Memo = &memo
}
default:
return nil, fmt.Errorf("unsupported tx type %d", tx.Type())
}
Expand Down
24 changes: 18 additions & 6 deletions cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,24 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
receipt.TxHash = tx.Hash()
receipt.GasUsed = msgResult.UsedGas
receipt.L1Fee = msgResult.L1DataFee
if msg.FeeTokenID() != 0 {
tokenID := msg.FeeTokenID()
receipt.FeeTokenID = &tokenID
receipt.FeeLimit = msg.FeeLimit()
receipt.FeeRate = msgResult.FeeRate
receipt.TokenScale = msgResult.TokenScale

if msg.FeeTokenID() != 0 ||
msg.Version() != 0 ||
(msg.Reference() != nil && *msg.Reference() != (common.Reference{})) ||
(msg.Memo() != nil && len(*msg.Memo()) > 0) {
if msg.FeeTokenID() != 0 {
tokenID := msg.FeeTokenID()
receipt.FeeTokenID = &tokenID
receipt.FeeLimit = msg.FeeLimit()
receipt.FeeRate = msgResult.FeeRate
receipt.TokenScale = msgResult.TokenScale
}
// Only include V1 fields (version, reference, memo) for V1+ transactions
if msg.Version() >= types.MorphTxVersion1 {
receipt.Version = msg.Version()
receipt.Reference = msg.Reference()
receipt.Memo = msg.Memo()
}
}

// If the transaction created a contract, store the creation address in the receipt.
Expand Down
8 changes: 4 additions & 4 deletions cmd/geth/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var (
utils.OverrideMorph203TimeFlag,
utils.OverrideViridianTimeFlag,
utils.OverrideEmeraldTimeFlag,
utils.OverrideMPTForkTimeFlag,
utils.OverrideJadeForkTimeFlag,
},
Category: "BLOCKCHAIN COMMANDS",
Description: `
Expand Down Expand Up @@ -226,9 +226,9 @@ func initGenesis(ctx *cli.Context) error {
v := ctx.Uint64(utils.OverrideEmeraldTimeFlag.Name)
overrides.EmeraldTime = &v
}
if ctx.IsSet(utils.OverrideMPTForkTimeFlag.Name) {
v := ctx.Uint64(utils.OverrideMPTForkTimeFlag.Name)
overrides.MPTForkTime = &v
if ctx.IsSet(utils.OverrideJadeForkTimeFlag.Name) {
v := ctx.Uint64(utils.OverrideJadeForkTimeFlag.Name)
overrides.JadeForkTime = &v
}

for _, name := range []string{"chaindata", "lightchaindata"} {
Expand Down
Loading