diff --git a/CHANGELOG.md b/CHANGELOG.md index 628475f..9f86969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the Aptos Go SDK will be captured in this file. This chan adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Unreleased +- Add BCS support for optional values # v1.1.0 (11/07/2024) - Add icon uri and project uri to FA client, reduce duplicated code diff --git a/bcs/bcs_test.go b/bcs/bcs_test.go index 04f576f..821b8bd 100644 --- a/bcs/bcs_test.go +++ b/bcs/bcs_test.go @@ -415,6 +415,32 @@ func Test_ConvenienceFunctions(t *testing.T) { assert.Equal(t, []byte{0x01, 0x05}, serializedBytes) } +func Test_SerializeOptional(t *testing.T) { + ser := Serializer{} + someValue := uint8(0xFF) + SerializeOption(&ser, &someValue, func(ser *Serializer, val uint8) { + ser.U8(val) + }) + assert.Equal(t, []byte{0x01, 0xFF}, ser.ToBytes()) + des := NewDeserializer(ser.ToBytes()) + desValue := DeserializeOption(des, func(des *Deserializer, out *uint8) { + *out = des.U8() + }) + assert.Equal(t, &someValue, desValue) + + ser2 := Serializer{} + SerializeOption(&ser2, nil, func(ser *Serializer, val uint8) { + ser.U8(val) + }) + assert.Equal(t, []byte{0x00}, ser2.ToBytes()) + + des2 := NewDeserializer(ser2.ToBytes()) + desValue2 := DeserializeOption(des2, func(des *Deserializer, out *uint8) { + *out = des.U8() + }) + assert.Nil(t, desValue2) +} + func Test_NilStructs(t *testing.T) { ser := Serializer{} ser.Struct(nil) diff --git a/bcs/deserializer.go b/bcs/deserializer.go index 4913675..34e57ff 100644 --- a/bcs/deserializer.go +++ b/bcs/deserializer.go @@ -275,6 +275,42 @@ func DeserializeSequenceWithFunction[T any](des *Deserializer, deserialize func( return out } +// DeserializeOption deserializes an optional value +// +// # Under the hood, this is represented as a 0 or 1 length array +// +// Here's an example for handling an optional value: +// +// // For a Some(10) value +// bytes == []byte{0x01, 0x0A} +// des := NewDeserializer(bytes) +// output := DeserializeOption(des, nil, func(des *Deserializer, out *uint8) { +// out = des.U8() +// }) +// // output == &10 +// +// // For a None value +// bytes2 == []byte{0x00} +// des2 := NewDeserializer(bytes2) +// output := DeserializeOption(des2, nil, func(des *Deserializer, out *uint8) { +// out = des.U8() +// }) +// // output == nil +func DeserializeOption[T any](des *Deserializer, deserialize func(des *Deserializer, out *T)) *T { + array := DeserializeSequenceWithFunction(des, deserialize) + switch len(array) { + case 0: + // None + return nil + case 1: + // Some + return &array[0] + default: + des.setError("expected 0 or 1 element as an option, got %d", len(array)) + } + return nil +} + // setError overrides the previous error, this can only be called from within the bcs package func (des *Deserializer) setError(msg string, args ...any) { if des.err != nil { diff --git a/bcs/serializer.go b/bcs/serializer.go index f7e3ddd..e923e8c 100644 --- a/bcs/serializer.go +++ b/bcs/serializer.go @@ -324,3 +324,31 @@ func SerializeSingle(marshal func(ser *Serializer)) (bytes []byte, err error) { bytes = ser.ToBytes() return bytes, nil } + +// SerializeOption serializes an optional value +// +// # Under the hood, this is represented as a 0 or 1 length array +// +// Here's an example for handling an optional value: +// +// // For a Some(10) value +// input := uint8(10) +// ser := &Serializer{} +// bytes, _ := SerializeOption(ser, &input, func(ser *Serializer, item uint8) { +// ser.U8(item) +// }) +// // bytes == []byte{0x01,0x0A} +// +// // For a None value +// ser2 := &Serializer{} +// bytes2, _ := SerializeOption(ser2, nil, func(ser *Serializer, item uint8) { +// ser.U8(item) +// }) +// // bytes2 == []byte{0x00} +func SerializeOption[T any](ser *Serializer, input *T, serialize func(ser *Serializer, item T)) { + if input == nil { + SerializeSequenceWithFunction([]T{}, ser, serialize) + } else { + SerializeSequenceWithFunction([]T{*input}, ser, serialize) + } +}