From 1d360418d441e0bc78c622a8bd076c35312437e0 Mon Sep 17 00:00:00 2001 From: cawthorne Date: Tue, 21 Apr 2026 13:06:00 +0100 Subject: [PATCH 1/4] feat: add aptos capability view proto helpers --- .../chain-capabilities/aptos/proto_helpers.go | 132 ++++++++++++++++++ .../aptos/proto_helpers_test.go | 100 +++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go create mode 100644 pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go new file mode 100644 index 000000000..d97fa92ae --- /dev/null +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go @@ -0,0 +1,132 @@ +package aptos + +import ( + "fmt" + "math" + + typesaptos "github.com/smartcontractkit/chainlink-common/pkg/types/chains/aptos" +) + +// ConvertViewPayloadFromProto converts a capability ViewPayload into Aptos domain types. +// Capability requests currently accept shortened Aptos addresses, so this helper left-pads +// addresses up to 32 bytes instead of requiring exact-length address bytes. +func ConvertViewPayloadFromProto(payload *ViewPayload) (*typesaptos.ViewPayload, error) { + if payload == nil { + return nil, fmt.Errorf("viewRequest.Payload is required") + } + if payload.Module == nil { + return nil, fmt.Errorf("viewRequest.Payload.Module is required") + } + if payload.Function == "" { + return nil, fmt.Errorf("viewRequest.Payload.Function is required") + } + + moduleAddress, err := convertAccountAddressFromProto(payload.Module.Address, "module") + if err != nil { + return nil, err + } + + argTypes := make([]typesaptos.TypeTag, 0, len(payload.ArgTypes)) + for i, tag := range payload.ArgTypes { + converted, err := ConvertTypeTagFromProto(tag) + if err != nil { + return nil, fmt.Errorf("invalid arg type at index %d: %w", i, err) + } + argTypes = append(argTypes, *converted) + } + + return &typesaptos.ViewPayload{ + Module: typesaptos.ModuleID{ + Address: moduleAddress, + Name: payload.Module.Name, + }, + Function: payload.Function, + ArgTypes: argTypes, + Args: payload.Args, + }, nil +} + +// ConvertTypeTagFromProto converts a capability TypeTag into Aptos domain types. +func ConvertTypeTagFromProto(tag *TypeTag) (*typesaptos.TypeTag, error) { + if tag == nil { + return nil, fmt.Errorf("type tag is nil") + } + + switch tag.Kind { + case TypeTagKind_TYPE_TAG_KIND_BOOL: + return &typesaptos.TypeTag{Value: typesaptos.BoolTag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U8: + return &typesaptos.TypeTag{Value: typesaptos.U8Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U16: + return &typesaptos.TypeTag{Value: typesaptos.U16Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U32: + return &typesaptos.TypeTag{Value: typesaptos.U32Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U64: + return &typesaptos.TypeTag{Value: typesaptos.U64Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U128: + return &typesaptos.TypeTag{Value: typesaptos.U128Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_U256: + return &typesaptos.TypeTag{Value: typesaptos.U256Tag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_ADDRESS: + return &typesaptos.TypeTag{Value: typesaptos.AddressTag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_SIGNER: + return &typesaptos.TypeTag{Value: typesaptos.SignerTag{}}, nil + case TypeTagKind_TYPE_TAG_KIND_VECTOR: + vector := tag.GetVector() + if vector == nil { + return nil, fmt.Errorf("vector tag missing vector value") + } + elementType, err := ConvertTypeTagFromProto(vector.ElementType) + if err != nil { + return nil, err + } + return &typesaptos.TypeTag{Value: typesaptos.VectorTag{ElementType: *elementType}}, nil + case TypeTagKind_TYPE_TAG_KIND_STRUCT: + structTag := tag.GetStruct() + if structTag == nil { + return nil, fmt.Errorf("struct tag missing struct value") + } + + structAddress, err := convertAccountAddressFromProto(structTag.Address, "struct") + if err != nil { + return nil, err + } + + typeParams := make([]typesaptos.TypeTag, 0, len(structTag.TypeParams)) + for i, tp := range structTag.TypeParams { + converted, err := ConvertTypeTagFromProto(tp) + if err != nil { + return nil, fmt.Errorf("invalid struct type param at index %d: %w", i, err) + } + typeParams = append(typeParams, *converted) + } + + return &typesaptos.TypeTag{Value: typesaptos.StructTag{ + Address: structAddress, + Module: structTag.Module, + Name: structTag.Name, + TypeParams: typeParams, + }}, nil + case TypeTagKind_TYPE_TAG_KIND_GENERIC: + generic := tag.GetGeneric() + if generic == nil { + return nil, fmt.Errorf("generic tag missing generic value") + } + if generic.Index > math.MaxUint16 { + return nil, fmt.Errorf("generic type index out of range: %d", generic.Index) + } + return &typesaptos.TypeTag{Value: typesaptos.GenericTag{Index: uint16(generic.Index)}}, nil + default: + return nil, fmt.Errorf("unsupported type tag kind: %v", tag.Kind) + } +} + +func convertAccountAddressFromProto(address []byte, field string) (typesaptos.AccountAddress, error) { + if len(address) > typesaptos.AccountAddressLength { + return typesaptos.AccountAddress{}, fmt.Errorf("%s address too long: %d", field, len(address)) + } + + var converted typesaptos.AccountAddress + copy(converted[typesaptos.AccountAddressLength-len(address):], address) + return converted, nil +} diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go new file mode 100644 index 000000000..0d70fbb55 --- /dev/null +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go @@ -0,0 +1,100 @@ +package aptos_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + aptoscap "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/aptos" + typesaptos "github.com/smartcontractkit/chainlink-common/pkg/types/chains/aptos" +) + +func TestConvertViewPayloadFromProto_ConvertsNestedVectorStructAndGenericTags(t *testing.T) { + t.Parallel() + + payload, err := aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{0x01}, Name: "coin"}, + Function: "name", + ArgTypes: []*aptoscap.TypeTag{ + { + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_VECTOR, + Value: &aptoscap.TypeTag_Vector{Vector: &aptoscap.VectorTag{ + ElementType: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: []byte{0x02}, + Module: "aptos_coin", + Name: "Coin", + TypeParams: []*aptoscap.TypeTag{ + { + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_GENERIC, + Value: &aptoscap.TypeTag_Generic{Generic: &aptoscap.GenericTag{Index: 7}}, + }, + }, + }}, + }, + }}, + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, payload) + require.Equal(t, "name", payload.Function) + require.Len(t, payload.ArgTypes, 1) + + vectorTag, ok := payload.ArgTypes[0].Value.(typesaptos.VectorTag) + require.True(t, ok) + structTag, ok := vectorTag.ElementType.Value.(typesaptos.StructTag) + require.True(t, ok) + require.Equal(t, "aptos_coin", structTag.Module) + require.Equal(t, "Coin", structTag.Name) + require.Len(t, structTag.TypeParams, 1) + genericTag, ok := structTag.TypeParams[0].Value.(typesaptos.GenericTag) + require.True(t, ok) + require.EqualValues(t, 7, genericTag.Index) +} + +func TestConvertViewPayloadFromProto_RejectsInvalidPayloadInputs(t *testing.T) { + t.Parallel() + + _, err := aptoscap.ConvertViewPayloadFromProto(nil) + require.ErrorContains(t, err, "viewRequest.Payload is required") + + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{Function: "name"}) + require.ErrorContains(t, err, "viewRequest.Payload.Module is required") + + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{0x01}, Name: "coin"}, + }) + require.ErrorContains(t, err, "viewRequest.Payload.Function is required") + + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: make([]byte, typesaptos.AccountAddressLength+1), Name: "coin"}, + Function: "name", + }) + require.ErrorContains(t, err, "module address too long") +} + +func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { + t.Parallel() + + _, err := aptoscap.ConvertTypeTagFromProto(nil) + require.ErrorContains(t, err, "type tag is nil") + + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{Kind: aptoscap.TypeTagKind(255)}) + require.ErrorContains(t, err, "unsupported type tag kind") + + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: make([]byte, typesaptos.AccountAddressLength+1), + }}, + }) + require.ErrorContains(t, err, "struct address too long") + + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_GENERIC, + Value: &aptoscap.TypeTag_Generic{Generic: &aptoscap.GenericTag{Index: 1 << 16}}, + }) + require.ErrorContains(t, err, "generic type index out of range") +} From 571c98daa94d8e9eff0ba35b46c26d7988d770e1 Mon Sep 17 00:00:00 2001 From: cawthorne Date: Tue, 21 Apr 2026 14:09:18 +0100 Subject: [PATCH 2/4] fix: tighten aptos view proto helper validation --- .../chain-capabilities/aptos/proto_helpers.go | 5 ++++- .../aptos/proto_helpers_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go index d97fa92ae..4338fcd52 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go @@ -17,6 +17,9 @@ func ConvertViewPayloadFromProto(payload *ViewPayload) (*typesaptos.ViewPayload, if payload.Module == nil { return nil, fmt.Errorf("viewRequest.Payload.Module is required") } + if payload.Module.Name == "" { + return nil, fmt.Errorf("viewRequest.Payload.Module.Name is required") + } if payload.Function == "" { return nil, fmt.Errorf("viewRequest.Payload.Function is required") } @@ -78,7 +81,7 @@ func ConvertTypeTagFromProto(tag *TypeTag) (*typesaptos.TypeTag, error) { } elementType, err := ConvertTypeTagFromProto(vector.ElementType) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid vector element type: %w", err) } return &typesaptos.TypeTag{Value: typesaptos.VectorTag{ElementType: *elementType}}, nil case TypeTagKind_TYPE_TAG_KIND_STRUCT: diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go index 0d70fbb55..7cdb6fe5b 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go @@ -68,6 +68,12 @@ func TestConvertViewPayloadFromProto_RejectsInvalidPayloadInputs(t *testing.T) { }) require.ErrorContains(t, err, "viewRequest.Payload.Function is required") + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{0x01}}, + Function: "name", + }) + require.ErrorContains(t, err, "viewRequest.Payload.Module.Name is required") + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ Module: &aptoscap.ModuleID{Address: make([]byte, typesaptos.AccountAddressLength+1), Name: "coin"}, Function: "name", @@ -92,6 +98,19 @@ func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { }) require.ErrorContains(t, err, "struct address too long") + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_VECTOR, + Value: &aptoscap.TypeTag_Vector{Vector: &aptoscap.VectorTag{ + ElementType: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: make([]byte, typesaptos.AccountAddressLength+1), + }}, + }, + }}, + }) + require.ErrorContains(t, err, "invalid vector element type: struct address too long") + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_GENERIC, Value: &aptoscap.TypeTag_Generic{Generic: &aptoscap.GenericTag{Index: 1 << 16}}, From 82182b12f60b3bbfaf4f7f39c327193b0630da91 Mon Sep 17 00:00:00 2001 From: cawthorne Date: Tue, 21 Apr 2026 14:58:26 +0100 Subject: [PATCH 3/4] fix: tighten aptos proto helper validation --- .../chain-capabilities/aptos/proto_helpers.go | 20 +++++++-- .../aptos/proto_helpers_test.go | 45 +++++++++++++++++-- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go index 4338fcd52..0cb970ed0 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go @@ -12,16 +12,19 @@ import ( // addresses up to 32 bytes instead of requiring exact-length address bytes. func ConvertViewPayloadFromProto(payload *ViewPayload) (*typesaptos.ViewPayload, error) { if payload == nil { - return nil, fmt.Errorf("viewRequest.Payload is required") + return nil, fmt.Errorf("payload is required") } if payload.Module == nil { - return nil, fmt.Errorf("viewRequest.Payload.Module is required") + return nil, fmt.Errorf("payload.module is required") + } + if len(payload.Module.Address) == 0 { + return nil, fmt.Errorf("payload.module.address is required") } if payload.Module.Name == "" { - return nil, fmt.Errorf("viewRequest.Payload.Module.Name is required") + return nil, fmt.Errorf("payload.module.name is required") } if payload.Function == "" { - return nil, fmt.Errorf("viewRequest.Payload.Function is required") + return nil, fmt.Errorf("payload.function is required") } moduleAddress, err := convertAccountAddressFromProto(payload.Module.Address, "module") @@ -89,6 +92,15 @@ func ConvertTypeTagFromProto(tag *TypeTag) (*typesaptos.TypeTag, error) { if structTag == nil { return nil, fmt.Errorf("struct tag missing struct value") } + if len(structTag.Address) == 0 { + return nil, fmt.Errorf("struct address is required") + } + if structTag.Module == "" { + return nil, fmt.Errorf("struct module is required") + } + if structTag.Name == "" { + return nil, fmt.Errorf("struct name is required") + } structAddress, err := convertAccountAddressFromProto(structTag.Address, "struct") if err != nil { diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go index 7cdb6fe5b..5ada76dfb 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go @@ -58,21 +58,27 @@ func TestConvertViewPayloadFromProto_RejectsInvalidPayloadInputs(t *testing.T) { t.Parallel() _, err := aptoscap.ConvertViewPayloadFromProto(nil) - require.ErrorContains(t, err, "viewRequest.Payload is required") + require.ErrorContains(t, err, "payload is required") _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{Function: "name"}) - require.ErrorContains(t, err, "viewRequest.Payload.Module is required") + require.ErrorContains(t, err, "payload.module is required") + + _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Name: "coin"}, + Function: "name", + }) + require.ErrorContains(t, err, "payload.module.address is required") _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ Module: &aptoscap.ModuleID{Address: []byte{0x01}, Name: "coin"}, }) - require.ErrorContains(t, err, "viewRequest.Payload.Function is required") + require.ErrorContains(t, err, "payload.function is required") _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ Module: &aptoscap.ModuleID{Address: []byte{0x01}}, Function: "name", }) - require.ErrorContains(t, err, "viewRequest.Payload.Module.Name is required") + require.ErrorContains(t, err, "payload.module.name is required") _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ Module: &aptoscap.ModuleID{Address: make([]byte, typesaptos.AccountAddressLength+1), Name: "coin"}, @@ -90,10 +96,39 @@ func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{Kind: aptoscap.TypeTagKind(255)}) require.ErrorContains(t, err, "unsupported type tag kind") + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Module: "coin", + Name: "Coin", + }}, + }) + require.ErrorContains(t, err, "struct address is required") + + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: []byte{0x01}, + Name: "Coin", + }}, + }) + require.ErrorContains(t, err, "struct module is required") + + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: []byte{0x01}, + Module: "coin", + }}, + }) + require.ErrorContains(t, err, "struct name is required") + _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ Address: make([]byte, typesaptos.AccountAddressLength+1), + Module: "coin", + Name: "Coin", }}, }) require.ErrorContains(t, err, "struct address too long") @@ -105,6 +140,8 @@ func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ Address: make([]byte, typesaptos.AccountAddressLength+1), + Module: "coin", + Name: "Coin", }}, }, }}, From b4da11dd6b5711b52979351df03298f7878a9edc Mon Sep 17 00:00:00 2001 From: cawthorne Date: Tue, 21 Apr 2026 16:38:15 +0100 Subject: [PATCH 4/4] refactor: tighten aptos proto validation helpers --- .../chain-capabilities/aptos/proto_helpers.go | 38 ++- .../aptos/proto_helpers_test.go | 224 +++++++++++------- 2 files changed, 169 insertions(+), 93 deletions(-) diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go index 0cb970ed0..6887edfb3 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers.go @@ -17,14 +17,14 @@ func ConvertViewPayloadFromProto(payload *ViewPayload) (*typesaptos.ViewPayload, if payload.Module == nil { return nil, fmt.Errorf("payload.module is required") } - if len(payload.Module.Address) == 0 { - return nil, fmt.Errorf("payload.module.address is required") + if err := requireNonEmptyBytes(payload.Module.Address, "payload.module.address"); err != nil { + return nil, err } - if payload.Module.Name == "" { - return nil, fmt.Errorf("payload.module.name is required") + if err := requireNonEmptyString(payload.Module.Name, "payload.module.name"); err != nil { + return nil, err } - if payload.Function == "" { - return nil, fmt.Errorf("payload.function is required") + if err := requireNonEmptyString(payload.Function, "payload.function"); err != nil { + return nil, err } moduleAddress, err := convertAccountAddressFromProto(payload.Module.Address, "module") @@ -92,14 +92,14 @@ func ConvertTypeTagFromProto(tag *TypeTag) (*typesaptos.TypeTag, error) { if structTag == nil { return nil, fmt.Errorf("struct tag missing struct value") } - if len(structTag.Address) == 0 { - return nil, fmt.Errorf("struct address is required") + if err := requireNonEmptyBytes(structTag.Address, "struct address"); err != nil { + return nil, err } - if structTag.Module == "" { - return nil, fmt.Errorf("struct module is required") + if err := requireNonEmptyString(structTag.Module, "struct module"); err != nil { + return nil, err } - if structTag.Name == "" { - return nil, fmt.Errorf("struct name is required") + if err := requireNonEmptyString(structTag.Name, "struct name"); err != nil { + return nil, err } structAddress, err := convertAccountAddressFromProto(structTag.Address, "struct") @@ -136,6 +136,20 @@ func ConvertTypeTagFromProto(tag *TypeTag) (*typesaptos.TypeTag, error) { } } +func requireNonEmptyBytes(value []byte, field string) error { + if len(value) == 0 { + return fmt.Errorf("%s is required", field) + } + return nil +} + +func requireNonEmptyString(value string, field string) error { + if value == "" { + return fmt.Errorf("%s is required", field) + } + return nil +} + func convertAccountAddressFromProto(address []byte, field string) (typesaptos.AccountAddress, error) { if len(address) > typesaptos.AccountAddressLength { return typesaptos.AccountAddress{}, fmt.Errorf("%s address too long: %d", field, len(address)) diff --git a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go index 5ada76dfb..770b96752 100644 --- a/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go +++ b/pkg/capabilities/v2/chain-capabilities/aptos/proto_helpers_test.go @@ -57,86 +57,119 @@ func TestConvertViewPayloadFromProto_ConvertsNestedVectorStructAndGenericTags(t func TestConvertViewPayloadFromProto_RejectsInvalidPayloadInputs(t *testing.T) { t.Parallel() - _, err := aptoscap.ConvertViewPayloadFromProto(nil) - require.ErrorContains(t, err, "payload is required") - - _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{Function: "name"}) - require.ErrorContains(t, err, "payload.module is required") - - _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ - Module: &aptoscap.ModuleID{Name: "coin"}, - Function: "name", - }) - require.ErrorContains(t, err, "payload.module.address is required") + testCases := []struct { + name string + payload *aptoscap.ViewPayload + wantErr string + }{ + { + name: "missing payload", + payload: nil, + wantErr: "payload is required", + }, + { + name: "missing module", + payload: &aptoscap.ViewPayload{Function: "name"}, + wantErr: "payload.module is required", + }, + { + name: "missing module address", + payload: &aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Name: "coin"}, + Function: "name", + }, + wantErr: "payload.module.address is required", + }, + { + name: "missing function", + payload: &aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{0x01}, Name: "coin"}, + }, + wantErr: "payload.function is required", + }, + { + name: "missing module name", + payload: &aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{0x01}}, + Function: "name", + }, + wantErr: "payload.module.name is required", + }, + { + name: "oversized module address", + payload: &aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: make([]byte, typesaptos.AccountAddressLength+1), Name: "coin"}, + Function: "name", + }, + wantErr: "module address too long", + }, + } - _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ - Module: &aptoscap.ModuleID{Address: []byte{0x01}, Name: "coin"}, - }) - require.ErrorContains(t, err, "payload.function is required") + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() - _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ - Module: &aptoscap.ModuleID{Address: []byte{0x01}}, - Function: "name", - }) - require.ErrorContains(t, err, "payload.module.name is required") - - _, err = aptoscap.ConvertViewPayloadFromProto(&aptoscap.ViewPayload{ - Module: &aptoscap.ModuleID{Address: make([]byte, typesaptos.AccountAddressLength+1), Name: "coin"}, - Function: "name", - }) - require.ErrorContains(t, err, "module address too long") + _, err := aptoscap.ConvertViewPayloadFromProto(tc.payload) + require.ErrorContains(t, err, tc.wantErr) + }) + } } func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { t.Parallel() - _, err := aptoscap.ConvertTypeTagFromProto(nil) - require.ErrorContains(t, err, "type tag is nil") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{Kind: aptoscap.TypeTagKind(255)}) - require.ErrorContains(t, err, "unsupported type tag kind") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, - Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ - Module: "coin", - Name: "Coin", - }}, - }) - require.ErrorContains(t, err, "struct address is required") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, - Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ - Address: []byte{0x01}, - Name: "Coin", - }}, - }) - require.ErrorContains(t, err, "struct module is required") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, - Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ - Address: []byte{0x01}, - Module: "coin", - }}, - }) - require.ErrorContains(t, err, "struct name is required") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, - Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ - Address: make([]byte, typesaptos.AccountAddressLength+1), - Module: "coin", - Name: "Coin", - }}, - }) - require.ErrorContains(t, err, "struct address too long") - - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_VECTOR, - Value: &aptoscap.TypeTag_Vector{Vector: &aptoscap.VectorTag{ - ElementType: &aptoscap.TypeTag{ + testCases := []struct { + name string + tag *aptoscap.TypeTag + wantErr string + }{ + { + name: "nil type tag", + tag: nil, + wantErr: "type tag is nil", + }, + { + name: "unsupported kind", + tag: &aptoscap.TypeTag{Kind: aptoscap.TypeTagKind(255)}, + wantErr: "unsupported type tag kind", + }, + { + name: "missing struct address", + tag: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Module: "coin", + Name: "Coin", + }}, + }, + wantErr: "struct address is required", + }, + { + name: "missing struct module", + tag: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: []byte{0x01}, + Name: "Coin", + }}, + }, + wantErr: "struct module is required", + }, + { + name: "missing struct name", + tag: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: []byte{0x01}, + Module: "coin", + }}, + }, + wantErr: "struct name is required", + }, + { + name: "oversized struct address", + tag: &aptoscap.TypeTag{ Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ Address: make([]byte, typesaptos.AccountAddressLength+1), @@ -144,13 +177,42 @@ func TestConvertTypeTagFromProto_RejectsInvalidInput(t *testing.T) { Name: "Coin", }}, }, - }}, - }) - require.ErrorContains(t, err, "invalid vector element type: struct address too long") + wantErr: "struct address too long", + }, + { + name: "invalid vector element type", + tag: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_VECTOR, + Value: &aptoscap.TypeTag_Vector{Vector: &aptoscap.VectorTag{ + ElementType: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_STRUCT, + Value: &aptoscap.TypeTag_Struct{Struct: &aptoscap.StructTag{ + Address: make([]byte, typesaptos.AccountAddressLength+1), + Module: "coin", + Name: "Coin", + }}, + }, + }}, + }, + wantErr: "invalid vector element type: struct address too long", + }, + { + name: "generic index out of range", + tag: &aptoscap.TypeTag{ + Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_GENERIC, + Value: &aptoscap.TypeTag_Generic{Generic: &aptoscap.GenericTag{Index: 1 << 16}}, + }, + wantErr: "generic type index out of range", + }, + } - _, err = aptoscap.ConvertTypeTagFromProto(&aptoscap.TypeTag{ - Kind: aptoscap.TypeTagKind_TYPE_TAG_KIND_GENERIC, - Value: &aptoscap.TypeTag_Generic{Generic: &aptoscap.GenericTag{Index: 1 << 16}}, - }) - require.ErrorContains(t, err, "generic type index out of range") + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + _, err := aptoscap.ConvertTypeTagFromProto(tc.tag) + require.ErrorContains(t, err, tc.wantErr) + }) + } }