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 }