mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
feat(core/types): fine-grained Body RLP override (#109)
## Why this should be merged
Allows for modification of `types.Body` payload data + RLP encoding
without placing the entire RLP burden on the `libevm` user as we did
with `types.HeaderHooks`.
## How this works
RLP encoding of a struct is simply a concatenation of RLP encodings of
fields, encompassed by an RLP "list". The
`AppendRLPFields(rlp.EncoderBuffer, ...)` hook exploits this and plugs
in before all `rlp:"optional"`-tagged fields to allow for inclusion of
any new fields. The `EncoderBuffer` SHOULD be used as the `io.Writer`
passed when encoding each field: `rlp.Encode(buffer, fieldValue)`.
`Body` doesn't have `{En,De}codeRLP` methods so they are implemented to
identically replicate original behaviour when a no-op hook is present.
This pattern is sufficient for the `ava-labs/coreth` modifications of
`Body` but can be modified / extended for more complex scenarios, like
`Header`.
> [!NOTE]
> This PR does not include registration of the hooks as that was not the
initial goal and adding them would create too much PR bloat. There is a
placeholder `var todoRegisteredBodyHooks` global variable that can only
be set in tests.
## How this was tested
- Backwards compatibility: the new methods are fuzzed against a `type
withoutMethods Body` passed directly to `rlp.{En,De}code()`
- `coreth` compatibility: unit test of a local implementation of
`BodyHooks` demonstrating reproducibility of RLP encoding.
---------
Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Co-authored-by: Quentin McGaw <quentin.mcgaw@avalabs.org>
This commit is contained in:
parent
be6e93eedb
commit
eda3b59f67
6 changed files with 518 additions and 3 deletions
20
common/pointer.libevm.go
Normal file
20
common/pointer.libevm.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package common
|
||||
|
||||
// PointerTo is a convenience wrapper for creating a pointer to a value.
|
||||
func PointerTo[T any](x T) *T { return &x }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2024 the libevm authors.
|
||||
// Copyright 2024-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
|
||||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/ava-labs/libevm/libevm/pseudo"
|
||||
"github.com/ava-labs/libevm/libevm/testonly"
|
||||
"github.com/ava-labs/libevm/rlp"
|
||||
)
|
||||
|
||||
|
|
@ -109,5 +110,104 @@ func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
|
|||
type withoutMethods Header
|
||||
return s.Decode((*withoutMethods)(h))
|
||||
}
|
||||
func (*NOOPHeaderHooks) PostCopy(dst *Header) {}
|
||||
|
||||
func (n *NOOPHeaderHooks) PostCopy(dst *Header) {}
|
||||
var (
|
||||
_ interface {
|
||||
rlp.Encoder
|
||||
rlp.Decoder
|
||||
} = (*Body)(nil)
|
||||
|
||||
// The implementations of [Body.EncodeRLP] and [Body.DecodeRLP] make
|
||||
// assumptions about the struct fields and their order, which we lock in here as a change
|
||||
// detector. If this breaks then it MUST be updated and the RLP methods
|
||||
// reviewed + new backwards-compatibility tests added.
|
||||
_ = &Body{[]*Transaction{}, []*Header{}, []*Withdrawal{}}
|
||||
)
|
||||
|
||||
// EncodeRLP implements the [rlp.Encoder] interface.
|
||||
func (b *Body) EncodeRLP(dst io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(dst)
|
||||
|
||||
return w.InList(func() error {
|
||||
if err := rlp.EncodeListToBuffer(w, b.Transactions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rlp.EncodeListToBuffer(w, b.Uncles); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasLaterOptionalField := b.Withdrawals != nil
|
||||
if err := b.hooks().AppendRLPFields(w, hasLaterOptionalField); err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasLaterOptionalField {
|
||||
return nil
|
||||
}
|
||||
return rlp.EncodeListToBuffer(w, b.Withdrawals)
|
||||
})
|
||||
}
|
||||
|
||||
// DecodeRLP implements the [rlp.Decoder] interface.
|
||||
func (b *Body) DecodeRLP(s *rlp.Stream) error {
|
||||
return s.FromList(func() error {
|
||||
txs, err := rlp.DecodeList[Transaction](s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uncles, err := rlp.DecodeList[Header](s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b = Body{
|
||||
Transactions: txs,
|
||||
Uncles: uncles,
|
||||
}
|
||||
|
||||
if err := b.hooks().DecodeExtraRLPFields(s); err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.MoreDataInList() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ws, err := rlp.DecodeList[Withdrawal](s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Withdrawals = ws
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BodyHooks are required for all types registered with [RegisterExtras] for
|
||||
// [Body] payloads.
|
||||
type BodyHooks interface {
|
||||
AppendRLPFields(_ rlp.EncoderBuffer, mustWriteEmptyOptional bool) error
|
||||
DecodeExtraRLPFields(*rlp.Stream) error
|
||||
}
|
||||
|
||||
// TestOnlyRegisterBodyHooks is a temporary means of "registering" BodyHooks for
|
||||
// the purpose of testing. It will panic if called outside of a test.
|
||||
func TestOnlyRegisterBodyHooks(h BodyHooks) {
|
||||
testonly.OrPanic(func() {
|
||||
todoRegisteredBodyHooks = h
|
||||
})
|
||||
}
|
||||
|
||||
// todoRegisteredBodyHooks is a temporary placeholder for "registering"
|
||||
// BodyHooks, before they are included in [RegisterExtras].
|
||||
var todoRegisteredBodyHooks BodyHooks = NOOPBodyHooks{}
|
||||
|
||||
func (b *Body) hooks() BodyHooks {
|
||||
// TODO(arr4n): when incorporating BodyHooks into [RegisterExtras], the
|
||||
// [todoRegisteredBodyHooks] variable MUST be removed.
|
||||
return todoRegisteredBodyHooks
|
||||
}
|
||||
|
||||
// NOOPBodyHooks implements [BodyHooks] such that they are equivalent to no type
|
||||
// having been registered.
|
||||
type NOOPBodyHooks struct{}
|
||||
|
||||
func (NOOPBodyHooks) AppendRLPFields(rlp.EncoderBuffer, bool) error { return nil }
|
||||
func (NOOPBodyHooks) DecodeExtraRLPFields(*rlp.Stream) error { return nil }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2024 the libevm authors.
|
||||
// Copyright 2024-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
|
||||
|
|
@ -20,10 +20,14 @@ import (
|
|||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ava-labs/libevm/common"
|
||||
. "github.com/ava-labs/libevm/core/types"
|
||||
"github.com/ava-labs/libevm/libevm/cmpeth"
|
||||
"github.com/ava-labs/libevm/libevm/ethtest"
|
||||
"github.com/ava-labs/libevm/rlp"
|
||||
)
|
||||
|
|
@ -106,3 +110,195 @@ func testHeaderRLPBackwardsCompatibility(t *testing.T) {
|
|||
assert.Equal(t, hdr, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBodyRLPBackwardsCompatibility(t *testing.T) {
|
||||
newTx := func(nonce uint64) *Transaction { return NewTx(&LegacyTx{Nonce: nonce}) }
|
||||
newHdr := func(hashLow byte) *Header { return &Header{ParentHash: common.Hash{hashLow}} }
|
||||
newWithdraw := func(idx uint64) *Withdrawal { return &Withdrawal{Index: idx} }
|
||||
|
||||
// We build up test-case [Body] instances from the power set of each of
|
||||
// these components.
|
||||
txMatrix := [][]*Transaction{
|
||||
nil, {}, // Must be equivalent for non-optional field
|
||||
{newTx(1)},
|
||||
{newTx(2), newTx(3)}, // Demonstrates nested lists
|
||||
}
|
||||
uncleMatrix := [][]*Header{
|
||||
nil, {},
|
||||
{newHdr(1)},
|
||||
{newHdr(2), newHdr(3)},
|
||||
}
|
||||
withdrawMatrix := [][]*Withdrawal{
|
||||
nil, {}, // Must be different for optional field
|
||||
{newWithdraw(1)},
|
||||
{newWithdraw(2), newWithdraw(3)},
|
||||
}
|
||||
|
||||
var bodies []*Body
|
||||
for _, tx := range txMatrix {
|
||||
for _, u := range uncleMatrix {
|
||||
for _, w := range withdrawMatrix {
|
||||
bodies = append(bodies, &Body{tx, u, w})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, body := range bodies {
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Logf("\n%s", pretty.Sprint(body))
|
||||
|
||||
// The original [Body] doesn't implement [rlp.Encoder] nor
|
||||
// [rlp.Decoder] so we can use a methodless equivalent as the gold
|
||||
// standard.
|
||||
type withoutMethods Body
|
||||
wantRLP, err := rlp.EncodeToBytes((*withoutMethods)(body))
|
||||
require.NoErrorf(t, err, "rlp.EncodeToBytes([%T with methods stripped])", body)
|
||||
|
||||
t.Run("Encode", func(t *testing.T) {
|
||||
got, err := rlp.EncodeToBytes(body)
|
||||
require.NoErrorf(t, err, "rlp.EncodeToBytes(%#v)", body)
|
||||
assert.Equalf(t, wantRLP, got, "rlp.EncodeToBytes(%#v)", body)
|
||||
})
|
||||
|
||||
t.Run("Decode", func(t *testing.T) {
|
||||
got := new(Body)
|
||||
err := rlp.DecodeBytes(wantRLP, got)
|
||||
require.NoErrorf(t, err, "rlp.DecodeBytes(%v, %T)", wantRLP, got)
|
||||
|
||||
want := body
|
||||
// Regular RLP decoding will never leave these non-optional
|
||||
// fields nil.
|
||||
if want.Transactions == nil {
|
||||
want.Transactions = []*Transaction{}
|
||||
}
|
||||
if want.Uncles == nil {
|
||||
want.Uncles = []*Header{}
|
||||
}
|
||||
|
||||
opts := cmp.Options{
|
||||
cmpeth.CompareHeadersByHash(),
|
||||
cmpeth.CompareTransactionsByBinary(t),
|
||||
}
|
||||
if diff := cmp.Diff(body, got, opts); diff != "" {
|
||||
t.Errorf("rlp.DecodeBytes(rlp.EncodeToBytes(%#v)) diff (-want +got):\n%s", body, diff)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// cChainBodyExtras carries the same additional fields as the Avalanche C-Chain
|
||||
// (ava-labs/coreth) [Body] and implements [BodyHooks] to achieve equivalent RLP
|
||||
// {en,de}coding.
|
||||
type cChainBodyExtras struct {
|
||||
Version uint32
|
||||
ExtData *[]byte
|
||||
}
|
||||
|
||||
var _ BodyHooks = (*cChainBodyExtras)(nil)
|
||||
|
||||
func (e *cChainBodyExtras) AppendRLPFields(b rlp.EncoderBuffer, _ bool) error {
|
||||
b.WriteUint64(uint64(e.Version))
|
||||
|
||||
var data []byte
|
||||
if e.ExtData != nil {
|
||||
data = *e.ExtData
|
||||
}
|
||||
b.WriteBytes(data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *cChainBodyExtras) DecodeExtraRLPFields(s *rlp.Stream) error {
|
||||
if err := s.Decode(&e.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf, err := s.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buf) > 0 {
|
||||
e.ExtData = &buf
|
||||
} else {
|
||||
// Respect the `rlp:"nil"` field tag.
|
||||
e.ExtData = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBodyRLPCChainCompat(t *testing.T) {
|
||||
// The inputs to this test were used to generate the expected RLP with
|
||||
// ava-labs/coreth. This serves as both an example of how to use [BodyHooks]
|
||||
// and a test of compatibility.
|
||||
|
||||
t.Cleanup(func() {
|
||||
TestOnlyRegisterBodyHooks(NOOPBodyHooks{})
|
||||
})
|
||||
|
||||
body := &Body{
|
||||
Transactions: []*Transaction{
|
||||
NewTx(&LegacyTx{
|
||||
Nonce: 42,
|
||||
To: common.PointerTo(common.HexToAddress(`decafc0ffeebad`)),
|
||||
}),
|
||||
},
|
||||
Uncles: []*Header{ /* RLP encoding differs in ava-labs/coreth */ },
|
||||
}
|
||||
|
||||
const version = 314159
|
||||
tests := []struct {
|
||||
name string
|
||||
extra *cChainBodyExtras
|
||||
// WARNING: changing these values might break backwards compatibility of
|
||||
// RLP encoding!
|
||||
wantRLPHex string
|
||||
}{
|
||||
{
|
||||
extra: &cChainBodyExtras{
|
||||
Version: version,
|
||||
},
|
||||
wantRLPHex: `e5dedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f80`,
|
||||
},
|
||||
{
|
||||
extra: &cChainBodyExtras{
|
||||
Version: version,
|
||||
ExtData: &[]byte{1, 4, 2, 8, 5, 7},
|
||||
},
|
||||
wantRLPHex: `ebdedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f86010402080507`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wantRLP, err := hex.DecodeString(tt.wantRLPHex)
|
||||
require.NoErrorf(t, err, "hex.DecodeString(%q)", tt.wantRLPHex)
|
||||
|
||||
t.Run("Encode", func(t *testing.T) {
|
||||
TestOnlyRegisterBodyHooks(tt.extra)
|
||||
got, err := rlp.EncodeToBytes(body)
|
||||
require.NoErrorf(t, err, "rlp.EncodeToBytes(%+v)", body)
|
||||
assert.Equalf(t, wantRLP, got, "rlp.EncodeToBytes(%+v)", body)
|
||||
})
|
||||
|
||||
t.Run("Decode", func(t *testing.T) {
|
||||
var extra cChainBodyExtras
|
||||
TestOnlyRegisterBodyHooks(&extra)
|
||||
|
||||
got := new(Body)
|
||||
err := rlp.DecodeBytes(wantRLP, got)
|
||||
require.NoErrorf(t, err, "rlp.DecodeBytes(%#x, %T)", wantRLP, got)
|
||||
assert.Equal(t, tt.extra, &extra, "rlp.DecodeBytes(%#x, [%T as registered extra in %T carrier])", wantRLP, &extra, got)
|
||||
|
||||
opts := cmp.Options{
|
||||
cmpeth.CompareHeadersByHash(),
|
||||
cmpeth.CompareTransactionsByBinary(t),
|
||||
}
|
||||
if diff := cmp.Diff(body, got, opts); diff != "" {
|
||||
t.Errorf("rlp.DecodeBytes(%#x, [%T while carrying registered %T extra payload]) diff (-want +got):\n%s", wantRLP, got, &extra, diff)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
libevm/cmpeth/cmpeth.go
Normal file
66
libevm/cmpeth/cmpeth.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package cmpeth provides ETH-specific options for the cmp package.
|
||||
package cmpeth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/ava-labs/libevm/core/types"
|
||||
)
|
||||
|
||||
// CompareHeadersByHash returns an option to compare Headers based on
|
||||
// [types.Header.Hash] equality.
|
||||
func CompareHeadersByHash() cmp.Option {
|
||||
return cmp.Comparer(func(a, b *types.Header) bool {
|
||||
return a.Hash() == b.Hash()
|
||||
})
|
||||
}
|
||||
|
||||
// CompareTransactionsByBinary returns an option to compare Transactions based
|
||||
// on [types.Transaction.MarshalBinary] equality. Two nil pointers are
|
||||
// considered equal.
|
||||
//
|
||||
// If MarshalBinary() returns an error, it will be reported with
|
||||
// [testing.TB.Fatal].
|
||||
func CompareTransactionsByBinary(tb testing.TB) cmp.Option {
|
||||
tb.Helper()
|
||||
return cmp.Comparer(func(a, b *types.Transaction) bool {
|
||||
tb.Helper()
|
||||
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(marshalTxBinary(tb, a), marshalTxBinary(tb, b))
|
||||
})
|
||||
}
|
||||
|
||||
func marshalTxBinary(tb testing.TB, tx *types.Transaction) []byte {
|
||||
tb.Helper()
|
||||
buf, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
tb.Fatalf("%T.MarshalBinary() error %v", tx, err)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
77
rlp/list.libevm.go
Normal file
77
rlp/list.libevm.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlp
|
||||
|
||||
// InList is a convenience wrapper, calling `fn` between calls to
|
||||
// [EncoderBuffer.List] and [EncoderBuffer.ListEnd]. If `fn` returns an error,
|
||||
// it is propagated directly.
|
||||
func (b EncoderBuffer) InList(fn func() error) error {
|
||||
l := b.List()
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
b.ListEnd(l)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeListToBuffer is equivalent to [Encode], writing the RLP encoding of
|
||||
// each element to `b`, except that it wraps the writes inside a call to
|
||||
// [EncoderBuffer.InList].
|
||||
func EncodeListToBuffer[T any](b EncoderBuffer, vals []T) error {
|
||||
return b.InList(func() error {
|
||||
for _, v := range vals {
|
||||
if err := Encode(b, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FromList is a convenience wrapper, calling `fn` between calls to
|
||||
// [Stream.List] and [Stream.ListEnd]. If `fn` returns an error, it is
|
||||
// propagated directly.
|
||||
func (s *Stream) FromList(fn func() error) error {
|
||||
if _, err := s.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.ListEnd()
|
||||
}
|
||||
|
||||
// DecodeList assumes that the next item in `s` is a list and decodes every item
|
||||
// in said list to a `*T`.
|
||||
//
|
||||
// The returned slice is guaranteed to be non-nil, even if the list is empty.
|
||||
// This is in keeping with other behaviour in this package and it is therefore
|
||||
// the responsibility of callers to respect `rlp:"nil"` struct tags.
|
||||
func DecodeList[T any](s *Stream) ([]*T, error) {
|
||||
vals := []*T{}
|
||||
err := s.FromList(func() error {
|
||||
for s.MoreDataInList() {
|
||||
var v T
|
||||
if err := s.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
vals = append(vals, &v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return vals, err
|
||||
}
|
||||
56
rlp/list.libevm_test.go
Normal file
56
rlp/list.libevm_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// 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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeListToBuffer(t *testing.T) {
|
||||
vals := []uint{1, 2, 3, 4, 5}
|
||||
|
||||
want, err := EncodeToBytes(vals)
|
||||
require.NoErrorf(t, err, "EncodeToBytes(%T{%[1]v})", vals)
|
||||
|
||||
var got bytes.Buffer
|
||||
buf := NewEncoderBuffer(&got)
|
||||
err = EncodeListToBuffer(buf, vals)
|
||||
require.NoErrorf(t, err, "EncodeListToBuffer(..., %T{%[1]v})", vals)
|
||||
require.NoErrorf(t, buf.Flush(), "%T.Flush()", buf)
|
||||
|
||||
assert.Equal(t, want, got.Bytes(), "EncodeListToBuffer(..., %T{%[1]v})", vals)
|
||||
}
|
||||
|
||||
func TestDecodeList(t *testing.T) {
|
||||
vals := []uint{0, 1, 42, 314159}
|
||||
|
||||
rlp, err := EncodeToBytes(vals)
|
||||
require.NoErrorf(t, err, "EncodeToBytes(%T{%[1]v})", vals)
|
||||
|
||||
s := NewStream(bytes.NewReader(rlp), 0)
|
||||
got, err := DecodeList[uint](s)
|
||||
require.NoErrorf(t, err, "DecodeList[%T]()", vals[0])
|
||||
|
||||
require.Equal(t, len(vals), len(got), "number of values returned by DecodeList()")
|
||||
for i, gotPtr := range got {
|
||||
assert.Equalf(t, vals[i], *gotPtr, "DecodeList()[%d]", i)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue