// Copyright 2019 The go-ethereum Authors
// (original work)
// Copyright 2024 The Erigon Authors
// (modifications)
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it 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.
//
// Erigon is distributed in the hope that it 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 Erigon. If not, see <http://www.gnu.org/licenses/>.

package types

import (
	"bytes"
	"errors"
	"reflect"
	"testing"

	"github.com/holiman/uint256"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/u256"
	"github.com/erigontech/erigon/execution/rlp"
)

func TestDecodeEmptyTypedReceipt(t *testing.T) {
	t.Parallel()
	input := []byte{0x80}
	var r Receipt
	err := rlp.DecodeBytes(input, &r)
	if !errors.Is(err, rlp.EOL) {
		t.Fatal("wrong error:", err)
	}
}

func TestLegacyReceiptDecoding(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name   string
		encode func(*Receipt) ([]byte, error)
	}{
		{
			"StoredReceiptRLP",
			encodeAsStoredReceiptRLP,
		},
		// Erigon: all the legacy formats are removed intentionally
	}

	tx := NewTransaction(1, common.HexToAddress("0x1"), &u256.Num1, 1, &u256.Num1, nil)
	receipt := &Receipt{
		Status:            ReceiptStatusFailed,
		CumulativeGasUsed: 1,
		Logs: []*Log{
			{
				Address: common.BytesToAddress([]byte{0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
				Index:   999,
			},
			{
				Address: common.BytesToAddress([]byte{0x01, 0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
				Index:   1000,
			},
		},
		TxHash:          tx.Hash(),
		ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
		GasUsed:         111111,
		BlockNumber:     uint256.NewInt(1),
	}
	receipt.Bloom = CreateBloom(Receipts{receipt})

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			enc, err := tc.encode(receipt)
			if err != nil {
				t.Fatalf("Error encoding receipt: %v", err)
			}
			var dec ReceiptForStorage
			if err := rlp.DecodeBytes(enc, &dec); err != nil {
				t.Fatalf("Error decoding RLP receipt: %v", err)
			}
			// Check whether all consensus fields are correct.
			if dec.Status != receipt.Status {
				t.Fatalf("Receipt status mismatch, want %v, have %v", receipt.Status, dec.Status)
			}
			if dec.CumulativeGasUsed != receipt.CumulativeGasUsed {
				t.Fatalf("Receipt CumulativeGasUsed mismatch, want %v, have %v", receipt.CumulativeGasUsed, dec.CumulativeGasUsed)
			}
			assert.Equal(t, uint32(receipt.Logs[0].Index), dec.FirstLogIndexWithinBlock)
			//if len(dec.Logs) != len(receipt.Logs) {
			//	t.Fatalf("Receipt log number mismatch, want %v, have %v", len(receipt.Logs), len(dec.Logs))
			//}
			//for i := 0; i < len(dec.Logs); i++ {
			//	if dec.Logs[i].Address != receipt.Logs[i].Address {
			//		t.Fatalf("Receipt log %d address mismatch, want %v, have %v", i, receipt.Logs[i].Address, dec.Logs[i].Address)
			//	}
			//	if !reflect.DeepEqual(dec.Logs[i].Topics, receipt.Logs[i].Topics) {
			//		t.Fatalf("Receipt log %d topics mismatch, want %v, have %v", i, receipt.Logs[i].Topics, dec.Logs[i].Topics)
			//	}
			//	if !bytes.Equal(dec.Logs[i].Data, receipt.Logs[i].Data) {
			//		t.Fatalf("Receipt log %d data mismatch, want %v, have %v", i, receipt.Logs[i].Data, dec.Logs[i].Data)
			//	}
			//}
		})
	}
}

func encodeAsStoredReceiptRLP(want *Receipt) ([]byte, error) {
	w := bytes.NewBuffer(nil)
	casted := ReceiptForStorage(*want)
	err := casted.EncodeRLP(w)
	if err != nil {
		return nil, err
	}
	return w.Bytes(), nil
}

// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt
// rlp decoder, which failed due to a shadowing error.
func TestTypedReceiptEncodingDecoding(t *testing.T) {
	t.Parallel()
	var payload = common.FromHex("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0")
	check := func(bundle []*Receipt) {
		t.Helper()
		for i, receipt := range bundle {
			if got, want := receipt.Type, uint8(1); got != want {
				t.Fatalf("bundle %d: got %x, want %x", i, got, want)
			}
		}
	}
	{
		var bundle []*Receipt
		if err := rlp.DecodeBytes(payload, &bundle); err != nil {
			t.Fatal(err)
		}
		check(bundle)
	}
	{
		var bundle []*Receipt
		r := bytes.NewReader(payload)
		s := rlp.NewStream(r, uint64(len(payload)))
		if err := s.Decode(&bundle); err != nil {
			t.Fatal(err)
		}
		check(bundle)
	}
}

func TestReceiptUnmarshalBinary(t *testing.T) {
	legacyReceipt := &Receipt{
		Status:            ReceiptStatusFailed,
		CumulativeGasUsed: 1,
		Logs: []*Log{
			{
				Address: common.BytesToAddress([]byte{0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
			{
				Address: common.BytesToAddress([]byte{0x01, 0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
		},
	}
	accessListReceipt := &Receipt{
		Status:            ReceiptStatusFailed,
		CumulativeGasUsed: 1,
		Logs: []*Log{
			{
				Address: common.BytesToAddress([]byte{0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
			{
				Address: common.BytesToAddress([]byte{0x01, 0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
		},
		Type: AccessListTxType,
	}
	eip1559Receipt := &Receipt{
		Status:            ReceiptStatusFailed,
		CumulativeGasUsed: 1,
		Logs: []*Log{
			{
				Address: common.BytesToAddress([]byte{0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
			{
				Address: common.BytesToAddress([]byte{0x01, 0x11}),
				Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
				Data:    []byte{0x01, 0x00, 0xff},
			},
		},
		Type: DynamicFeeTxType,
	}

	t.Run("MarshalBinary", func(t *testing.T) {
		// Legacy Receipt
		legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt})
		have, err := legacyReceipt.MarshalBinary()
		if err != nil {
			t.Fatalf("marshal binary error: %v", err)
		}
		legacyReceipts := Receipts{legacyReceipt}
		buf := new(bytes.Buffer)
		legacyReceipts.EncodeIndex(0, buf)
		haveEncodeIndex := buf.Bytes()
		if !bytes.Equal(have, haveEncodeIndex) {
			t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex)
		}
		buf.Reset()
		if err := legacyReceipt.EncodeRLP(buf); err != nil {
			t.Fatalf("encode rlp error: %v", err)
		}
		haveRLPEncode := buf.Bytes()
		if !bytes.Equal(have, haveRLPEncode) {
			t.Errorf("BinaryMarshal and EncodeRLP mismatch for legacy tx, got %x want %x", have, haveRLPEncode)
		}
		legacyWant := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		if !bytes.Equal(have, legacyWant) {
			t.Errorf("encoded RLP mismatch, got %x want %x", have, legacyWant)
		}

		// 2930 Receipt
		buf.Reset()
		accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt})
		have, err = accessListReceipt.MarshalBinary()
		if err != nil {
			t.Fatalf("marshal binary error: %v", err)
		}
		accessListReceipts := Receipts{accessListReceipt}
		accessListReceipts.EncodeIndex(0, buf)
		haveEncodeIndex = buf.Bytes()
		if !bytes.Equal(have, haveEncodeIndex) {
			t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex)
		}
		accessListWant := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		if !bytes.Equal(have, accessListWant) {
			t.Errorf("encoded RLP mismatch, got %x want %x", have, accessListWant)
		}

		// 1559 Receipt
		buf.Reset()
		eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt})
		have, err = eip1559Receipt.MarshalBinary()
		if err != nil {
			t.Fatalf("marshal binary error: %v", err)
		}
		eip1559Receipts := Receipts{eip1559Receipt}
		eip1559Receipts.EncodeIndex(0, buf)
		haveEncodeIndex = buf.Bytes()
		if !bytes.Equal(have, haveEncodeIndex) {
			t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex)
		}
		eip1559Want := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		if !bytes.Equal(have, eip1559Want) {
			t.Errorf("encoded RLP mismatch, got %x want %x", have, eip1559Want)
		}
	})

	t.Run("UnmarshalBinary", func(t *testing.T) {
		// Legacy Receipt
		legacyBinary := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		gotLegacyReceipt := new(Receipt)
		if err := gotLegacyReceipt.UnmarshalBinary(legacyBinary); err != nil {
			t.Fatalf("unmarshal binary error: %v", err)
		}
		legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt})
		if !reflect.DeepEqual(gotLegacyReceipt, legacyReceipt) {
			t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotLegacyReceipt, legacyReceipt)
		}

		// 2930 Receipt
		accessListBinary := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		gotAccessListReceipt := new(Receipt)
		if err := gotAccessListReceipt.UnmarshalBinary(accessListBinary); err != nil {
			t.Fatalf("unmarshal binary error: %v", err)
		}
		accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt})
		if !reflect.DeepEqual(gotAccessListReceipt, accessListReceipt) {
			t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotAccessListReceipt, accessListReceipt)
		}

		// 1559 Receipt
		eip1559RctBinary := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff")
		got1559Receipt := new(Receipt)
		if err := got1559Receipt.UnmarshalBinary(eip1559RctBinary); err != nil {
			t.Fatalf("unmarshal binary error: %v", err)
		}
		eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt})
		if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) {
			t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt)
		}
	})
}

// TestReceiptEncodeRLP69_AllTypesAreList verifies that EncodeRLP69 produces a flat RLP
// list [tx-type, post-state-or-status, cumulative-gas, logs] for every transaction type,
// conforming to the ETH69 spec. In particular, typed receipts must NOT use the old
// type_byte || rlp(data) byte-string envelope from ETH68.
func TestReceiptEncodeRLP69_AllTypesAreList(t *testing.T) {
	t.Parallel()

	logs := []*Log{
		{
			Address: common.BytesToAddress([]byte{0x11}),
			Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
			Data:    []byte{0x01, 0x00, 0xff},
		},
	}

	txTypes := []struct {
		name   string
		txType uint8
	}{
		{"Legacy", LegacyTxType},
		{"AccessList", AccessListTxType},
		{"DynamicFee", DynamicFeeTxType},
		{"Blob", BlobTxType},
		{"SetCode", SetCodeTxType},
	}

	for _, tt := range txTypes {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			receipt := &Receipt{
				Type:              tt.txType,
				Status:            ReceiptStatusSuccessful,
				CumulativeGasUsed: 50000,
				Logs:              logs,
			}

			var buf bytes.Buffer
			err := receipt.EncodeRLP69(&buf)
			require.NoError(t, err)
			encoded := buf.Bytes()
			require.NotEmpty(t, encoded)

			// The first byte must indicate an RLP list (0xc0..0xf7 for short lists,
			// 0xf8..0xff for long lists), NOT a byte string (0x80..0xbf).
			firstByte := encoded[0]
			assert.True(t, firstByte >= 0xc0,
				"expected RLP list prefix (>= 0xc0), got 0x%02x — typed receipt was likely wrapped as byte string", firstByte)

			// Decode back using the receiptRLP69 struct to verify all 4 fields.
			var decoded receiptRLP69
			err = rlp.DecodeBytes(encoded, &decoded)
			require.NoError(t, err)

			assert.Equal(t, tt.txType, decoded.Type, "Type mismatch")
			assert.Equal(t, receiptStatusSuccessfulRLP, decoded.PostStateOrStatus, "PostStateOrStatus mismatch")
			assert.Equal(t, uint64(50000), decoded.CumulativeGasUsed, "CumulativeGasUsed mismatch")
			require.Len(t, decoded.Logs, 1)
			assert.Equal(t, logs[0].Address, decoded.Logs[0].Address)
		})
	}
}

// TestReceiptEncodeRLP69_NoBloom confirms that the ETH69 encoding omits the bloom filter.
func TestReceiptEncodeRLP69_NoBloom(t *testing.T) {
	t.Parallel()

	receipt := &Receipt{
		Type:              DynamicFeeTxType,
		Status:            ReceiptStatusSuccessful,
		CumulativeGasUsed: 21000,
		Bloom:             CreateBloom(Receipts{{Status: ReceiptStatusSuccessful, Logs: []*Log{{Address: common.BytesToAddress([]byte{0x11})}}}}),
		Logs:              []*Log{},
	}

	var buf bytes.Buffer
	err := receipt.EncodeRLP69(&buf)
	require.NoError(t, err)

	// Decode using the 4-field struct; if a bloom was incorrectly included,
	// the RLP decoder would either fail or produce wrong field values.
	var decoded receiptRLP69
	err = rlp.DecodeBytes(buf.Bytes(), &decoded)
	require.NoError(t, err)
	assert.Equal(t, uint8(DynamicFeeTxType), decoded.Type)
	assert.Equal(t, uint64(21000), decoded.CumulativeGasUsed)
}

// TestReceiptsEncodeRLP69_MixedTypes verifies that Receipts.EncodeRLP69 correctly
// encodes a batch of mixed-type receipts.
func TestReceiptsEncodeRLP69_MixedTypes(t *testing.T) {
	t.Parallel()

	receipts := Receipts{
		{
			Type:              LegacyTxType,
			Status:            ReceiptStatusSuccessful,
			CumulativeGasUsed: 21000,
			Logs:              []*Log{},
		},
		{
			Type:              AccessListTxType,
			Status:            ReceiptStatusFailed,
			CumulativeGasUsed: 42000,
			Logs:              []*Log{},
		},
		{
			Type:              DynamicFeeTxType,
			Status:            ReceiptStatusSuccessful,
			CumulativeGasUsed: 63000,
			Logs:              []*Log{},
		},
		{
			Type:              BlobTxType,
			Status:            ReceiptStatusSuccessful,
			CumulativeGasUsed: 84000,
			Logs:              []*Log{},
		},
	}

	var buf bytes.Buffer
	err := receipts.EncodeRLP69(&buf)
	require.NoError(t, err)

	// Decode the outer list of receipt lists.
	var decodedList []receiptRLP69
	err = rlp.DecodeBytes(buf.Bytes(), &decodedList)
	require.NoError(t, err)
	require.Len(t, decodedList, 4)

	assert.Equal(t, uint8(LegacyTxType), decodedList[0].Type)
	assert.Equal(t, uint64(21000), decodedList[0].CumulativeGasUsed)

	assert.Equal(t, uint8(AccessListTxType), decodedList[1].Type)
	assert.Equal(t, receiptStatusFailedRLP, decodedList[1].PostStateOrStatus)

	assert.Equal(t, uint8(DynamicFeeTxType), decodedList[2].Type)
	assert.Equal(t, uint64(63000), decodedList[2].CumulativeGasUsed)

	assert.Equal(t, uint8(BlobTxType), decodedList[3].Type)
	assert.Equal(t, uint64(84000), decodedList[3].CumulativeGasUsed)
}

// TestReceiptEncodeRLP69_DiffersFromETH68 ensures that the ETH69 encoding
// is structurally different from the ETH68 encoding for typed receipts.
func TestReceiptEncodeRLP69_DiffersFromETH68(t *testing.T) {
	t.Parallel()

	receipt := &Receipt{
		Type:              DynamicFeeTxType,
		Status:            ReceiptStatusSuccessful,
		CumulativeGasUsed: 21000,
		Bloom:             CreateBloom(Receipts{{Status: ReceiptStatusSuccessful, Logs: []*Log{}}}),
		Logs:              []*Log{},
	}

	// ETH68 encoding
	var buf68 bytes.Buffer
	err := receipt.EncodeRLP(&buf68)
	require.NoError(t, err)

	// ETH69 encoding
	var buf69 bytes.Buffer
	err = receipt.EncodeRLP69(&buf69)
	require.NoError(t, err)

	// They must differ: ETH68 wraps typed receipts as byte strings; ETH69 uses flat lists.
	assert.False(t, bytes.Equal(buf68.Bytes(), buf69.Bytes()),
		"ETH68 and ETH69 typed receipt encodings should differ")

	// ETH69 result must be a list (first byte >= 0xc0).
	assert.True(t, buf69.Bytes()[0] >= 0xc0,
		"ETH69 encoding should be an RLP list, got first byte 0x%02x", buf69.Bytes()[0])

	// ETH68 result for typed receipt is a byte string (first byte < 0xc0).
	assert.True(t, buf68.Bytes()[0] < 0xc0,
		"ETH68 typed receipt encoding should be an RLP byte string, got first byte 0x%02x", buf68.Bytes()[0])
}

func TestReceiptEncode(t *testing.T) {
	t.Run("Enc.FirstLogIndexWithinBlock", func(t *testing.T) {
		r1 := &ReceiptForStorage{FirstLogIndexWithinBlock: 1}
		buf, err := rlp.EncodeToBytes(r1)
		require.NoError(t, err)
		r2 := &ReceiptForStorage{}
		err = rlp.DecodeBytes(buf, r2)
		require.NoError(t, err)
		require.Equal(t, r1.FirstLogIndexWithinBlock, r2.FirstLogIndexWithinBlock)
	})

	t.Run("Enc.Empty.FirstLogIndexWithinBlock", func(t *testing.T) {
		r1 := &ReceiptForStorage{Logs: Logs{
			&Log{Index: 1},
		}}
		buf, err := rlp.EncodeToBytes(r1)
		require.NoError(t, err)
		r2 := &ReceiptForStorage{}
		err = rlp.DecodeBytes(buf, r2)
		require.NoError(t, err)
		require.Equal(t, int(r1.Logs[0].Index), int(r2.FirstLogIndexWithinBlock))
	})

	t.Run("Enc.EmptyLogs", func(t *testing.T) {
		r1 := &ReceiptForStorage{FirstLogIndexWithinBlock: 1,
			Logs: Logs{
				&Log{Index: 1},
			},
		}
		buf, err := rlp.EncodeToBytes(r1)
		require.NoError(t, err)
		r2 := &ReceiptForStorage{}
		err = rlp.DecodeBytes(buf, r2)
		require.NoError(t, err)
		require.Equal(t, r1.FirstLogIndexWithinBlock, r2.FirstLogIndexWithinBlock)
	})
	t.Run("Enc.List", func(t *testing.T) {
		r1 := &ReceiptForStorage{FirstLogIndexWithinBlock: 1}
		for i := 0; i < 13; i++ {
			r1.Logs = append(r1.Logs, &Log{Topics: make([]common.Hash, 300)})
		}

		buf, err := rlp.EncodeToBytes(r1)
		require.NoError(t, err)
		r2 := &ReceiptForStorage{}
		err = rlp.DecodeBytes(buf, r2)
		require.NoError(t, err)
		require.Equal(t, r1.FirstLogIndexWithinBlock, r2.FirstLogIndexWithinBlock)
		require.Equal(t, len(r1.Logs), len(r2.Logs))
		require.Equal(t, len(r1.Logs[0].Topics), len(r2.Logs[0].Topics))
	})
}
