github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/tests/proto3/proto3_compat_test.go (about)

     1  //go:build extensive_tests
     2  
     3  // only built if manually enforced (via the build tag above)
     4  package proto3
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"encoding/binary"
    10  	"math"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/golang/protobuf/proto"
    18  	"github.com/golang/protobuf/ptypes"
    19  
    20  	p3 "github.com/gnolang/gno/tm2/pkg/amino/tests/proto3/proto"
    21  
    22  	"github.com/gnolang/gno/tm2/pkg/amino"
    23  	"github.com/gnolang/gno/tm2/pkg/amino/tests"
    24  )
    25  
    26  // This file checks basic proto3 compatibility by checking encoding of some test-vectors generated by using protoc.
    27  
    28  var (
    29  	cdc   = amino.NewCodec()
    30  	epoch time.Time
    31  )
    32  
    33  func init() {
    34  	cdc.Seal()
    35  	epoch, _ = time.Parse("2006-01-02 15:04:05 +0000 UTC", "1970-01-01 00:00:00 +0000 UTC")
    36  }
    37  
    38  func TestFixed32Roundtrip(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	// amino fixed32 (int32) <-> protbuf fixed32 (uint32)
    42  	type testi32 struct {
    43  		Int32 int32 `binary:"fixed32"`
    44  	}
    45  	ab, err := cdc.Marshal(testi32{Int32: 150})
    46  	assert.NoError(t, err, "unexpected error")
    47  
    48  	pb, err := proto.Marshal(&p3.TestInt32Fixed{Fixed32: 150})
    49  	assert.NoError(t, err, "unexpected error")
    50  
    51  	assert.Equal(t, pb, ab, "fixed32 (int32) encoding doesn't match")
    52  
    53  	// unmarshal (from amino to proto and vice versa)
    54  	var att testi32
    55  	var pt p3.Test32
    56  	err = proto.Unmarshal(ab, &pt)
    57  	assert.NoError(t, err, "unexpected error")
    58  
    59  	err = cdc.Unmarshal(pb, &att)
    60  	assert.NoError(t, err, "unexpected error")
    61  
    62  	assert.Equal(t, uint32(att.Int32), pt.Foo)
    63  }
    64  
    65  func TestVarintZigzagRoundtrip(t *testing.T) {
    66  	t.Parallel()
    67  
    68  	t.Skip("zigzag encoding isn't default anymore for (unsigned) ints")
    69  	// amino varint (int) <-> protobuf zigzag32 (int32 in go sint32 in proto file)
    70  	type testInt32Varint struct {
    71  		Int32 int `binary:"varint"`
    72  	}
    73  	varint := testInt32Varint{Int32: 6000000}
    74  	ab, err := cdc.Marshal(varint)
    75  	assert.NoError(t, err, "unexpected error")
    76  	pb, err := proto.Marshal(&p3.TestInt32Varint{Int32: 6000000})
    77  	assert.NoError(t, err, "unexpected error")
    78  	assert.Equal(t, pb, ab, "varint encoding doesn't match")
    79  
    80  	var amToP3 p3.TestInt32Varint
    81  	var p3ToAm testInt32Varint
    82  	err = proto.Unmarshal(ab, &amToP3)
    83  	assert.NoError(t, err, "unexpected error")
    84  
    85  	err = cdc.Unmarshal(pb, &p3ToAm)
    86  	assert.NoError(t, err, "unexpected error")
    87  
    88  	assert.EqualValues(t, varint.Int32, amToP3.Int32)
    89  }
    90  
    91  func TestFixedU64Roundtrip(t *testing.T) {
    92  	t.Parallel()
    93  
    94  	type testFixed64Uint struct {
    95  		Int64 uint64 `binary:"fixed64"`
    96  	}
    97  
    98  	pvint64 := p3.TestFixedInt64{Int64: 150}
    99  	avint64 := testFixed64Uint{Int64: 150}
   100  	ab, err := cdc.Marshal(avint64)
   101  	assert.NoError(t, err, "unexpected error")
   102  
   103  	pb, err := proto.Marshal(&pvint64)
   104  	assert.NoError(t, err, "unexpected error")
   105  
   106  	assert.Equal(t, pb, ab, "fixed64 encoding doesn't match")
   107  
   108  	var amToP3 p3.TestFixedInt64
   109  	var p3ToAm testFixed64Uint
   110  	err = proto.Unmarshal(ab, &amToP3)
   111  	assert.NoError(t, err, "unexpected error")
   112  
   113  	err = cdc.Unmarshal(pb, &p3ToAm)
   114  	assert.NoError(t, err, "unexpected error")
   115  
   116  	assert.EqualValues(t, p3ToAm.Int64, amToP3.Int64)
   117  }
   118  
   119  func TestMultidimensionalSlices(t *testing.T) {
   120  	t.Parallel()
   121  
   122  	s := [][]int8{
   123  		{1, 2},
   124  		{3, 4, 5},
   125  	}
   126  
   127  	_, err := cdc.Marshal(s)
   128  	assert.Error(t, err, "expected error: multidimensional slices are not allowed")
   129  }
   130  
   131  func TestMultidimensionalArrays(t *testing.T) {
   132  	t.Parallel()
   133  
   134  	arr := [2][2]int8{
   135  		{1, 2},
   136  		{3, 4},
   137  	}
   138  
   139  	_, err := cdc.Marshal(arr)
   140  	assert.Error(t, err, "expected error: multidimensional arrays are not allowed")
   141  }
   142  
   143  func TestMultidimensionalByteArraysAndSlices(t *testing.T) {
   144  	t.Parallel()
   145  
   146  	arr := [2][2]byte{
   147  		{1, 2},
   148  		{3, 4},
   149  	}
   150  
   151  	_, err := cdc.Marshal(arr)
   152  	assert.NoError(t, err, "unexpected error: multidimensional arrays are allowed, as long as they are only of bytes")
   153  
   154  	s := [][]byte{
   155  		{1, 2},
   156  		{3, 4, 5},
   157  	}
   158  
   159  	_, err = cdc.Marshal(s)
   160  	assert.NoError(t, err, "unexpected error: multidimensional slices are allowed, as long as they are only of bytes")
   161  
   162  	s2 := [][][]byte{{
   163  		{1, 2},
   164  		{3, 4, 5},
   165  	}}
   166  
   167  	_, err = cdc.Marshal(s2)
   168  	assert.NoError(t, err, "unexpected error: multidimensional slices are allowed, as long as they are only of bytes")
   169  }
   170  
   171  func TestProto3CompatPtrsRoundtrip(t *testing.T) {
   172  	t.Parallel()
   173  
   174  	s := p3.SomeStruct{}
   175  
   176  	ab, err := cdc.Marshal(s)
   177  	assert.NoError(t, err)
   178  
   179  	pb, err := proto.Marshal(&s)
   180  	assert.NoError(t, err)
   181  	// This fails as amino currently returns []byte(nil)
   182  	// while protobuf returns []byte{}:
   183  	//
   184  	// assert.Equal(t, ab, pb)
   185  	//
   186  	// Semantically, that's no problem though. Hence, we only check for zero length:
   187  	assert.Zero(t, len(ab), "expected an empty encoding for a nil pointer")
   188  	t.Log(ab)
   189  
   190  	var amToP3 p3.SomeStruct
   191  	var p3ToAm p3.SomeStruct
   192  	err = proto.Unmarshal(ab, &amToP3)
   193  	assert.NoError(t, err, "unexpected error")
   194  
   195  	err = cdc.Unmarshal(pb, &p3ToAm)
   196  	assert.NoError(t, err, "unexpected error")
   197  
   198  	assert.EqualValues(t, p3ToAm, amToP3)
   199  
   200  	s2 := p3.SomeStruct{Emb: &p3.EmbeddedStruct{}}
   201  
   202  	ab, err = cdc.Marshal(s2)
   203  	assert.NoError(t, err)
   204  
   205  	pb, err = proto.Marshal(&s2)
   206  	assert.NoError(t, err)
   207  	assert.Equal(t, ab, pb)
   208  
   209  	err = proto.Unmarshal(ab, &amToP3)
   210  	assert.NoError(t, err, "unexpected error")
   211  
   212  	err = cdc.Unmarshal(pb, &p3ToAm)
   213  	assert.NoError(t, err, "unexpected error")
   214  
   215  	assert.EqualValues(t, p3ToAm, amToP3)
   216  
   217  	assert.NotZero(t, len(ab), "expected a non-empty encoding for a non-nil pointer to an empty struct")
   218  	t.Log(ab)
   219  }
   220  
   221  // ---------------------------------------------------------------
   222  //  ---- time.Time <-> timestamp.Timestamp (proto3 well known type) :
   223  // ---------------------------------------------------------------
   224  
   225  // equivalent go struct or "type" to the proto3 message:
   226  type goAminoGotTime struct {
   227  	T *time.Time
   228  }
   229  
   230  func TestProto3CompatEmptyTimestamp(t *testing.T) {
   231  	t.Parallel()
   232  
   233  	empty := p3.ProtoGotTime{}
   234  	// protobuf also marshals to empty bytes here:
   235  	pb, err := proto.Marshal(&empty)
   236  	assert.NoError(t, err)
   237  	assert.Len(t, pb, 0)
   238  
   239  	// unmarshaling an empty slice behaves a bit differently in proto3 compared to amino:
   240  	res := &goAminoGotTime{}
   241  	err = cdc.Unmarshal(pb, res)
   242  	assert.NoError(t, err)
   243  	// NOTE: this behaves differently because amino defaults the time to 1970-01-01 00:00:00 +0000 UTC while
   244  	// decoding; protobuf defaults to nil here (see the following lines below):
   245  	assert.NoError(t, err)
   246  	assert.Equal(t, goAminoGotTime{T: &epoch}, *res)
   247  	pbRes := p3.ProtoGotTime{}
   248  	err = proto.Unmarshal(pb, &pbRes)
   249  	assert.NoError(t, err)
   250  	assert.Equal(t, p3.ProtoGotTime{T: nil}, pbRes)
   251  }
   252  
   253  func TestProto3CompatTimestampNow(t *testing.T) {
   254  	t.Parallel()
   255  
   256  	// test with current time:
   257  	now := time.Now()
   258  	ptts, err := ptypes.TimestampProto(now)
   259  	assert.NoError(t, err)
   260  	pt := p3.ProtoGotTime{T: ptts}
   261  	at := goAminoGotTime{T: &now}
   262  	ab1, err := cdc.Marshal(at)
   263  	assert.NoError(t, err)
   264  	ab2, err := cdc.Marshal(pt)
   265  	assert.NoError(t, err)
   266  	// amino's encoding of time.Time is the same as proto's encoding of the well known type
   267  	// timestamp.Timestamp (they can be used interchangeably):
   268  	assert.Equal(t, ab1, ab2)
   269  	pb, err := proto.Marshal(&pt)
   270  	assert.NoError(t, err)
   271  	assert.Equal(t, ab1, pb)
   272  
   273  	pbRes := p3.ProtoGotTime{}
   274  	err = proto.Unmarshal(ab1, &pbRes)
   275  	assert.NoError(t, err)
   276  	got, err := ptypes.Timestamp(pbRes.T)
   277  	assert.NoError(t, err)
   278  	_, err = ptypes.TimestampProto(now)
   279  	assert.NoError(t, err)
   280  	err = proto.Unmarshal(pb, &pbRes)
   281  	assert.NoError(t, err)
   282  	// create time.Time from timestamp.Timestamp and check if they are the same:
   283  	got, err = ptypes.Timestamp(pbRes.T)
   284  	assert.Equal(t, got.UTC(), now.UTC())
   285  }
   286  
   287  func TestProto3EpochTime(t *testing.T) {
   288  	t.Parallel()
   289  
   290  	pbRes := p3.ProtoGotTime{}
   291  	// amino encode epoch (1970) and decode using proto; expect the resulting time to be epoch again:
   292  	ab, err := cdc.Marshal(goAminoGotTime{T: &epoch})
   293  	assert.NoError(t, err)
   294  	err = proto.Unmarshal(ab, &pbRes)
   295  	assert.NoError(t, err)
   296  	ts, err := ptypes.Timestamp(pbRes.T)
   297  	assert.NoError(t, err)
   298  	assert.EqualValues(t, ts, epoch)
   299  }
   300  
   301  func TestProtoNegativeSeconds(t *testing.T) {
   302  	t.Parallel()
   303  
   304  	pbRes := p3.ProtoGotTime{}
   305  	// test with negative seconds (0001-01-01 -> seconds = -62135596800, nanos = 0):
   306  	ntm, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC")
   307  	ab, err := cdc.Marshal(goAminoGotTime{T: &ntm})
   308  	assert.NoError(t, err)
   309  	res := &goAminoGotTime{}
   310  	err = cdc.Unmarshal(ab, res)
   311  	assert.NoError(t, err)
   312  	assert.EqualValues(t, ntm, *res.T)
   313  	err = proto.Unmarshal(ab, &pbRes)
   314  	assert.NoError(t, err)
   315  	got, err := ptypes.Timestamp(pbRes.T)
   316  	assert.NoError(t, err)
   317  	assert.Equal(t, got, ntm)
   318  }
   319  
   320  func TestIntVarintCompat(t *testing.T) {
   321  	t.Parallel()
   322  
   323  	tcs := []struct {
   324  		val32 int32
   325  		val64 int64
   326  	}{
   327  		{1, 1},
   328  		{-1, -1},
   329  		{2, 2},
   330  		{1000, 1000},
   331  		{math.MaxInt32, math.MaxInt64},
   332  		{math.MinInt32, math.MinInt64},
   333  	}
   334  	for _, tc := range tcs {
   335  		tv := p3.TestInts{Int32: tc.val32, Int64: tc.val64}
   336  		ab, err := cdc.Marshal(tv)
   337  		assert.NoError(t, err)
   338  		pb, err := proto.Marshal(&tv)
   339  		assert.NoError(t, err)
   340  		assert.Equal(t, ab, pb)
   341  		var res p3.TestInts
   342  		err = cdc.Unmarshal(pb, &res)
   343  		assert.NoError(t, err)
   344  		var res2 p3.TestInts
   345  		err = proto.Unmarshal(ab, &res2)
   346  		assert.NoError(t, err)
   347  		assert.Equal(t, res.Int32, tc.val32)
   348  		assert.Equal(t, res.Int64, tc.val64)
   349  		assert.Equal(t, res2.Int32, tc.val32)
   350  		assert.Equal(t, res2.Int64, tc.val64)
   351  	}
   352  	// special case: amino allows int as well
   353  	// test that ints are also varint encoded:
   354  	type TestInt struct {
   355  		Int int
   356  	}
   357  	tcs2 := []struct {
   358  		val int
   359  	}{
   360  		{0},
   361  		{-1},
   362  		{1000},
   363  		{-1000},
   364  		{math.MaxInt32},
   365  		{math.MinInt32},
   366  	}
   367  	for _, tc := range tcs2 {
   368  		ptv := p3.TestInts{Int32: int32(tc.val)}
   369  		pb, err := proto.Marshal(&ptv)
   370  		assert.NoError(t, err)
   371  		atv := TestInt{tc.val}
   372  		ab, err := cdc.Marshal(atv)
   373  		assert.NoError(t, err)
   374  		if tc.val == 0 {
   375  			// amino results in []byte(nil)
   376  			// protobuf in []byte{}
   377  			assert.Empty(t, ab)
   378  			assert.Empty(t, pb)
   379  		} else {
   380  			assert.Equal(t, ab, pb)
   381  		}
   382  		// can we get back the int from the proto?
   383  		var res TestInt
   384  		err = cdc.Unmarshal(pb, &res)
   385  		assert.NoError(t, err)
   386  		assert.EqualValues(t, res.Int, tc.val)
   387  	}
   388  
   389  	// purposely overflow by writing a too large value to first field (which is int32):
   390  	fieldNum := 1
   391  	fieldNumAndType := (uint64(fieldNum) << 3) | uint64(amino.Typ3Varint)
   392  	var b bytes.Buffer
   393  	writer := bufio.NewWriter(&b)
   394  	var buf [10]byte
   395  	n := binary.PutUvarint(buf[:], fieldNumAndType)
   396  	_, err := writer.Write(buf[0:n])
   397  	assert.NoError(t, err)
   398  	amino.EncodeUvarint(writer, math.MaxInt32+1)
   399  	err = writer.Flush()
   400  	assert.NoError(t, err)
   401  
   402  	var res p3.TestInts
   403  	err = cdc.Unmarshal(b.Bytes(), &res)
   404  	assert.Error(t, err)
   405  }
   406  
   407  // See if encoding of type def types matches the proto3 encoding
   408  func TestTypeDefCompatibility(t *testing.T) {
   409  	t.Parallel()
   410  
   411  	pNow := ptypes.TimestampNow()
   412  	now, err := ptypes.Timestamp(pNow)
   413  	require.NoError(t, err)
   414  
   415  	strSl := tests.PrimitivesStructSl{
   416  		{Int32: 1, Int64: -1, Varint: 2, String: "protobuf3", Bytes: []byte("got some bytes"), Time: now},
   417  		{Int32: 0, Int64: 1, Varint: -2, String: "amino", Bytes: []byte("more of these bytes"), Time: now},
   418  	}
   419  	strAr := tests.PrimitivesStructAr{strSl[0], strSl[1]}
   420  	p3StrSl := &p3.PrimitivesStructSl{
   421  		Structs: []*p3.PrimitivesStruct{
   422  			{Int32: 1, Int64: -1, Varint: 2, String_: "protobuf3", Bytes: []byte("got some bytes"), Time: pNow},
   423  			{Int32: 0, Int64: 1, Varint: -2, String_: "amino", Bytes: []byte("more of these bytes"), Time: pNow},
   424  		},
   425  	}
   426  
   427  	tcs := []struct {
   428  		AminoType interface{}
   429  		ProtoMsg  proto.Message
   430  	}{
   431  		// type IntDef int
   432  		0: {tests.IntDef(0), &p3.IntDef{}},
   433  		1: {tests.IntDef(0), &p3.IntDef{Val: 0}},
   434  		2: {tests.IntDef(1), &p3.IntDef{Val: 1}},
   435  		3: {tests.IntDef(-1), &p3.IntDef{Val: -1}},
   436  
   437  		// type IntAr [4]int
   438  		4: {tests.IntAr{1, 2, 3, 4}, &p3.IntArr{Val: []int64{1, 2, 3, 4}}},
   439  		5: {tests.IntAr{0, -2, 3, 4}, &p3.IntArr{Val: []int64{0, -2, 3, 4}}},
   440  
   441  		// type IntSl []int (protobuf doesn't really have arrays)
   442  		6: {tests.IntSl{1, 2, 3, 4}, &p3.IntArr{Val: []int64{1, 2, 3, 4}}},
   443  
   444  		// type PrimitivesStructSl []PrimitivesStruct
   445  		7: {strSl, p3StrSl},
   446  		// type PrimitivesStructAr [2]PrimitivesStruct
   447  		8: {strAr, p3StrSl},
   448  	}
   449  	for i, tc := range tcs {
   450  		ab, err := amino.Marshal(tc.AminoType)
   451  		require.NoError(t, err)
   452  
   453  		pb, err := proto.Marshal(tc.ProtoMsg)
   454  		require.NoError(t, err)
   455  
   456  		assert.Equal(t, pb, ab, "Amino and protobuf encoding do not match %v", i)
   457  	}
   458  }