From 78550141c4af5e45bbe9b795d2e2463b6daf8de2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 1 Oct 2025 21:55:48 +0300 Subject: [PATCH 1/6] feat: support underlying basic types in rlpgen --- rlp/rlpgen/gen.go | 27 +++++++++++++++++++++++++++ rlp/rlpgen/testdata/alias.in.txt | 2 ++ rlp/rlpgen/testdata/alias.out.txt | 8 ++++++++ rlp/rlpgen/types.go | 10 ++++++++++ 4 files changed, 47 insertions(+) diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index 7ec38a4c38f..c1659bebe48 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -373,6 +373,30 @@ func (op uint256Op) genDecode(ctx *genContext) (string, string) { return result, b.String() } +func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) { + underlying := named.Underlying() + basic, ok := underlying.(*types.Basic) + if !ok { + return nil, fmt.Errorf("expected basic type, got %T", underlying) + } + + // Use the existing makeBasicOp function to get the base operation + baseOp, err := bctx.makeBasicOp(basic) + if err != nil { + return nil, err + } + + // Cast to basicOp and modify the typ field to use the named type + op := baseOp.(basicOp) + op.typ = named // Use the named type as the main type instead of the underlying basic type + + // For decoding, we want to decode as the underlying basic type and convert to named type + // So we need to set decResultType to the underlying basic type + op.decResultType = basic + + return op, nil +} + // encoderDecoderOp handles rlp.Encoder and rlp.Decoder. // In order to be used with this, the type must implement both interfaces. // This restriction may be lifted in the future by creating separate ops for @@ -684,6 +708,9 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if typ == bctx.rawValueType { return bctx.makeRawValueOp(), nil } + if isNamedWithBasicUnderlying(typ) { + return bctx.makeNamedBasicOp(typ) + } if bctx.isDecoder(typ) { return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) } diff --git a/rlp/rlpgen/testdata/alias.in.txt b/rlp/rlpgen/testdata/alias.in.txt index c7aa8a3106f..92246a74b67 100644 --- a/rlp/rlpgen/testdata/alias.in.txt +++ b/rlp/rlpgen/testdata/alias.in.txt @@ -14,9 +14,11 @@ type ( // Demonstrate recursive unaliasing intermediate = uint256.Int Uint256 = intermediate + Uint64T uint64 // This is not an alias, it's a new type ) type Test struct { BigAlias Big Uint256Alias Uint256 + Uint64NewT Uint64T } diff --git a/rlp/rlpgen/testdata/alias.out.txt b/rlp/rlpgen/testdata/alias.out.txt index 0746f974946..e680e6fa108 100644 --- a/rlp/rlpgen/testdata/alias.out.txt +++ b/rlp/rlpgen/testdata/alias.out.txt @@ -12,6 +12,7 @@ func (obj *Test) EncodeRLP(_w io.Writer) error { } w.WriteBigInt(&obj.BigAlias) w.WriteUint256(&obj.Uint256Alias) + w.WriteUint64(uint64(obj.Uint64NewT)) w.ListEnd(_tmp0) return w.Flush() } @@ -34,6 +35,13 @@ func (obj *Test) DecodeRLP(dec *rlp.Stream) error { return err } _tmp0.Uint256Alias = _tmp2 + // Uint64NewT: + _tmp3, err := dec.Uint64() + if err != nil { + return err + } + _tmp4 := Uint64T(_tmp3) + _tmp0.Uint64NewT = _tmp4 if err := dec.ListEnd(); err != nil { return err } diff --git a/rlp/rlpgen/types.go b/rlp/rlpgen/types.go index ea7dc96d881..5f108721efd 100644 --- a/rlp/rlpgen/types.go +++ b/rlp/rlpgen/types.go @@ -113,6 +113,16 @@ func isByte(typ types.Type) bool { return ok && basic.Kind() == types.Uint8 } +// isNamedWithBasicUnderlying checks whether 'typ' is a named type with an underlying basic type. +func isNamedWithBasicUnderlying(typ types.Type) bool { + named, ok := typ.(*types.Named) + if !ok { + return false + } + _, ok = named.Underlying().(*types.Basic) + return ok +} + func resolveUnderlying(typ types.Type) types.Type { for { t := typ.Underlying() From 95d00210d6433366e5e5dc39bf60e5f4cabccf63 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 1 Oct 2025 22:00:50 +0300 Subject: [PATCH 2/6] check the type --- rlp/rlpgen/gen.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index c1659bebe48..f57355d8d9f 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -387,7 +387,10 @@ func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) { } // Cast to basicOp and modify the typ field to use the named type - op := baseOp.(basicOp) + op, ok := baseOp.(basicOp) + if !ok { + return nil, fmt.Errorf("expected basicOp, got %T", baseOp) + } op.typ = named // Use the named type as the main type instead of the underlying basic type // For decoding, we want to decode as the underlying basic type and convert to named type From 16afd515e78b6e8a7722dafab0dbc10da345dd30 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 1 Oct 2025 22:22:44 +0300 Subject: [PATCH 3/6] remove unnecessary decResultType setting --- rlp/rlpgen/gen.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index f57355d8d9f..24abed319a5 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -393,10 +393,6 @@ func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) { } op.typ = named // Use the named type as the main type instead of the underlying basic type - // For decoding, we want to decode as the underlying basic type and convert to named type - // So we need to set decResultType to the underlying basic type - op.decResultType = basic - return op, nil } From 9334bdeb9392cdac1cb3161c28b1a06d7d5ba332 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 1 Oct 2025 22:22:54 +0300 Subject: [PATCH 4/6] add named tests --- rlp/rlpgen/gen_test.go | 4 +-- rlp/rlpgen/testdata/alias.in.txt | 2 -- rlp/rlpgen/testdata/alias.out.txt | 8 ----- rlp/rlpgen/testdata/named.in.txt | 15 ++++++++++ rlp/rlpgen/testdata/named.out.txt | 49 +++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 rlp/rlpgen/testdata/named.in.txt create mode 100644 rlp/rlpgen/testdata/named.out.txt diff --git a/rlp/rlpgen/gen_test.go b/rlp/rlpgen/gen_test.go index be439902610..a32e3472318 100644 --- a/rlp/rlpgen/gen_test.go +++ b/rlp/rlpgen/gen_test.go @@ -47,7 +47,7 @@ func init() { } } -var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias"} +var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias", "named"} func TestOutput(t *testing.T) { for _, test := range tests { @@ -66,7 +66,7 @@ func TestOutput(t *testing.T) { // Set this environment variable to regenerate the test outputs. if os.Getenv("WRITE_TEST_FILES") != "" { - os.WriteFile(outputFile, output, 0644) + os.WriteFile(outputFile, output, 0o644) } // Check if output matches. diff --git a/rlp/rlpgen/testdata/alias.in.txt b/rlp/rlpgen/testdata/alias.in.txt index 92246a74b67..c7aa8a3106f 100644 --- a/rlp/rlpgen/testdata/alias.in.txt +++ b/rlp/rlpgen/testdata/alias.in.txt @@ -14,11 +14,9 @@ type ( // Demonstrate recursive unaliasing intermediate = uint256.Int Uint256 = intermediate - Uint64T uint64 // This is not an alias, it's a new type ) type Test struct { BigAlias Big Uint256Alias Uint256 - Uint64NewT Uint64T } diff --git a/rlp/rlpgen/testdata/alias.out.txt b/rlp/rlpgen/testdata/alias.out.txt index e680e6fa108..0746f974946 100644 --- a/rlp/rlpgen/testdata/alias.out.txt +++ b/rlp/rlpgen/testdata/alias.out.txt @@ -12,7 +12,6 @@ func (obj *Test) EncodeRLP(_w io.Writer) error { } w.WriteBigInt(&obj.BigAlias) w.WriteUint256(&obj.Uint256Alias) - w.WriteUint64(uint64(obj.Uint64NewT)) w.ListEnd(_tmp0) return w.Flush() } @@ -35,13 +34,6 @@ func (obj *Test) DecodeRLP(dec *rlp.Stream) error { return err } _tmp0.Uint256Alias = _tmp2 - // Uint64NewT: - _tmp3, err := dec.Uint64() - if err != nil { - return err - } - _tmp4 := Uint64T(_tmp3) - _tmp0.Uint64NewT = _tmp4 if err := dec.ListEnd(); err != nil { return err } diff --git a/rlp/rlpgen/testdata/named.in.txt b/rlp/rlpgen/testdata/named.in.txt new file mode 100644 index 00000000000..654acd78bbd --- /dev/null +++ b/rlp/rlpgen/testdata/named.in.txt @@ -0,0 +1,15 @@ +// -*- mode: go -*- + +package test + +type ( + BoolT bool + Uint64T uint64 + StringT string +) + +type Test struct { + BoolNewT BoolT + Uint64NewT Uint64T + StringNewT StringT +} diff --git a/rlp/rlpgen/testdata/named.out.txt b/rlp/rlpgen/testdata/named.out.txt new file mode 100644 index 00000000000..c80f98b0591 --- /dev/null +++ b/rlp/rlpgen/testdata/named.out.txt @@ -0,0 +1,49 @@ +package test + +import "github.com/ava-labs/libevm/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteBool(bool(obj.BoolNewT)) + w.WriteUint64(uint64(obj.Uint64NewT)) + w.WriteString(string(obj.StringNewT)) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // BoolNewT: + _tmp1, err := dec.Bool() + if err != nil { + return err + } + _tmp2 := BoolT(_tmp1) + _tmp0.BoolNewT = _tmp2 + // Uint64NewT: + _tmp3, err := dec.Uint64() + if err != nil { + return err + } + _tmp4 := Uint64T(_tmp3) + _tmp0.Uint64NewT = _tmp4 + // StringNewT: + _tmp5, err := dec.String() + if err != nil { + return err + } + _tmp6 := StringT(_tmp5) + _tmp0.StringNewT = _tmp6 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} From 96d49b8caaf64d1ade152e2453e8ab4dcb84e8b9 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 6 Oct 2025 18:43:25 +0300 Subject: [PATCH 5/6] reviews --- rlp/rlpgen/gen.go | 32 ++--------- rlp/rlpgen/gen_test.go | 4 +- rlp/rlpgen/named.libevm.go | 55 +++++++++++++++++++ .../{named.in.txt => named.libevm.in.txt} | 0 .../{named.out.txt => named.libevm.out.txt} | 0 rlp/rlpgen/types.go | 10 ---- 6 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 rlp/rlpgen/named.libevm.go rename rlp/rlpgen/testdata/{named.in.txt => named.libevm.in.txt} (100%) rename rlp/rlpgen/testdata/{named.out.txt => named.libevm.out.txt} (100%) diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index 24abed319a5..ff78cbbb9ad 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -373,29 +373,6 @@ func (op uint256Op) genDecode(ctx *genContext) (string, string) { return result, b.String() } -func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) { - underlying := named.Underlying() - basic, ok := underlying.(*types.Basic) - if !ok { - return nil, fmt.Errorf("expected basic type, got %T", underlying) - } - - // Use the existing makeBasicOp function to get the base operation - baseOp, err := bctx.makeBasicOp(basic) - if err != nil { - return nil, err - } - - // Cast to basicOp and modify the typ field to use the named type - op, ok := baseOp.(basicOp) - if !ok { - return nil, fmt.Errorf("expected basicOp, got %T", baseOp) - } - op.typ = named // Use the named type as the main type instead of the underlying basic type - - return op, nil -} - // encoderDecoderOp handles rlp.Encoder and rlp.Decoder. // In order to be used with this, the type must implement both interfaces. // This restriction may be lifted in the future by creating separate ops for @@ -707,12 +684,15 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if typ == bctx.rawValueType { return bctx.makeRawValueOp(), nil } - if isNamedWithBasicUnderlying(typ) { - return bctx.makeNamedBasicOp(typ) - } if bctx.isDecoder(typ) { return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) } + // libevm: named types are reduced to their underlying basic type in this loop. + // We're handling named types here by passing the named type as the main type. + // See [named.libevm.go] for more details. + if hasBasicUnderlying(typ) { + return bctx.makeNamedBasicOp(typ) + } // TODO: same check for encoder? return bctx.makeOp(typ, typ.Underlying(), tags) case *types.Pointer: diff --git a/rlp/rlpgen/gen_test.go b/rlp/rlpgen/gen_test.go index a32e3472318..69e3fe80771 100644 --- a/rlp/rlpgen/gen_test.go +++ b/rlp/rlpgen/gen_test.go @@ -47,7 +47,7 @@ func init() { } } -var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias", "named"} +var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias", "named.libevm"} func TestOutput(t *testing.T) { for _, test := range tests { @@ -66,7 +66,7 @@ func TestOutput(t *testing.T) { // Set this environment variable to regenerate the test outputs. if os.Getenv("WRITE_TEST_FILES") != "" { - os.WriteFile(outputFile, output, 0o644) + os.WriteFile(outputFile, output, 0644) } // Check if output matches. diff --git a/rlp/rlpgen/named.libevm.go b/rlp/rlpgen/named.libevm.go new file mode 100644 index 00000000000..7f5aad1e8b8 --- /dev/null +++ b/rlp/rlpgen/named.libevm.go @@ -0,0 +1,55 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package main + +import ( + "fmt" + "go/types" +) + +// makeNamedBasicOp is a convenience wrapper for basicOp. +// It returns a basicOp with the named type as the main type instead of the underlying basic type. +func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) { + underlying := named.Underlying() + basic, ok := underlying.(*types.Basic) + if !ok { + return nil, fmt.Errorf("expected basic type, got %T", underlying) + } + + // We use basic op because it actually supports necessary conversions (through writeNeedsConversion and decodeNeedsConversion) + // for named types. + // The only problem with that is it does not support the named type as the main type. + // So we use the named type as the main type instead of the underlying basic type. + baseOp, err := bctx.makeBasicOp(basic) + if err != nil { + return nil, err + } + + op, ok := baseOp.(basicOp) + if !ok { + return nil, fmt.Errorf("expected basicOp, got %T", baseOp) + } + op.typ = named + + return op, nil +} + +// hasBasicUnderlying checks whether `named` has an underlying basic type. +func hasBasicUnderlying(named *types.Named) bool { + _, ok := named.Underlying().(*types.Basic) + return ok +} diff --git a/rlp/rlpgen/testdata/named.in.txt b/rlp/rlpgen/testdata/named.libevm.in.txt similarity index 100% rename from rlp/rlpgen/testdata/named.in.txt rename to rlp/rlpgen/testdata/named.libevm.in.txt diff --git a/rlp/rlpgen/testdata/named.out.txt b/rlp/rlpgen/testdata/named.libevm.out.txt similarity index 100% rename from rlp/rlpgen/testdata/named.out.txt rename to rlp/rlpgen/testdata/named.libevm.out.txt diff --git a/rlp/rlpgen/types.go b/rlp/rlpgen/types.go index 5f108721efd..ea7dc96d881 100644 --- a/rlp/rlpgen/types.go +++ b/rlp/rlpgen/types.go @@ -113,16 +113,6 @@ func isByte(typ types.Type) bool { return ok && basic.Kind() == types.Uint8 } -// isNamedWithBasicUnderlying checks whether 'typ' is a named type with an underlying basic type. -func isNamedWithBasicUnderlying(typ types.Type) bool { - named, ok := typ.(*types.Named) - if !ok { - return false - } - _, ok = named.Underlying().(*types.Basic) - return ok -} - func resolveUnderlying(typ types.Type) types.Type { for { t := typ.Underlying() From 2eea63d919dcafb01ded394ed9d0505877abd72e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 7 Oct 2025 21:55:47 +0300 Subject: [PATCH 6/6] move comment to if block Signed-off-by: Ceyhun Onur --- rlp/rlpgen/gen.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go index ff78cbbb9ad..c0492b4de93 100644 --- a/rlp/rlpgen/gen.go +++ b/rlp/rlpgen/gen.go @@ -687,10 +687,10 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru if bctx.isDecoder(typ) { return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) } - // libevm: named types are reduced to their underlying basic type in this loop. - // We're handling named types here by passing the named type as the main type. - // See [named.libevm.go] for more details. if hasBasicUnderlying(typ) { + // libevm: named types are reduced to their underlying basic type in this loop. + // We're handling named types here by passing the named type as the main type. + // See [named.libevm.go] for more details. return bctx.makeNamedBasicOp(typ) } // TODO: same check for encoder?