-
Notifications
You must be signed in to change notification settings - Fork 28
feat: add Aptos capability view proto helpers #1992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1d36041
571c98d
82182b1
b4da11d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| 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.Module.Name == "" { | ||
| return nil, fmt.Errorf("viewRequest.Payload.Module.Name 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 | ||
| } | ||
|
Comment on lines
+17
to
+33
|
||
|
|
||
| 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, fmt.Errorf("invalid vector element type: %w", 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 | ||
|
Comment on lines
+90
to
+124
|
||
| 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| 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: []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", | ||
| }) | ||
| require.ErrorContains(t, err, "module address too long") | ||
| } | ||
|
Comment on lines
+57
to
+117
|
||
|
|
||
| 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_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}}, | ||
| }) | ||
| require.ErrorContains(t, err, "generic type index out of range") | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation errors in this helper reference
viewRequest.Payload..., but the API accepts a*ViewPayloaddirectly. Consider aligning the error strings with the function’s input (e.g.,payload is required,payload.module is required) so callers who use the helper outside aViewRequestcontext aren’t misled.