diff --git a/registry/error.go b/registry/error.go index b07d7406d..f4b91e5df 100644 --- a/registry/error.go +++ b/registry/error.go @@ -48,4 +48,13 @@ const ( ErrValueDecoding = libErr.Error("value decoding") ErrRecursiveFieldDecoderNotFound = libErr.Error("recursive field decoder not found") ErrBitVecDecoding = libErr.Error("bit vec decoding") + ErrNilTypeDecoder = libErr.Error("nil type decoder") + ErrNilField = libErr.Error("nil field") + ErrNilFieldDecoder = libErr.Error("nil field decoder") + ErrFieldEncode = libErr.Error("field encoding") + ErrDecodeToTarget = libErr.Error("decode to target") + ErrDecodedFieldNotFound = libErr.Error("decoded field not found") + ErrDecodedFieldValueTypeMismatch = libErr.Error("decoded field value type mismatch") + ErrDecodedFieldValueProcessingError = libErr.Error("decoded field value processing error") + ErrDecodedFieldValueNotAGenericSlice = libErr.Error("decoded field value is not a generic slice") ) diff --git a/registry/registry.go b/registry/factory.go similarity index 82% rename from registry/registry.go rename to registry/factory.go index dd0be8619..924eb7dac 100644 --- a/registry/registry.go +++ b/registry/factory.go @@ -18,14 +18,19 @@ type Factory interface { CreateEventRegistry(meta *types.Metadata) (EventRegistry, error) } -// CallRegistry maps a call name to its Type. -type CallRegistry map[types.CallIndex]*Type +// CallRegistry maps a call name to its TypeDecoder. +type CallRegistry map[types.CallIndex]*TypeDecoder -// ErrorRegistry maps an error name to its Type. -type ErrorRegistry map[string]*Type +type ErrorID struct { + ModuleIndex types.U8 + ErrorIndex [4]types.U8 +} + +// ErrorRegistry maps an error name to its TypeDecoder. +type ErrorRegistry map[ErrorID]*TypeDecoder -// EventRegistry maps an event ID to its Type. -type EventRegistry map[types.EventID]*Type +// EventRegistry maps an event ID to its TypeDecoder. +type EventRegistry map[types.EventID]*TypeDecoder // FieldOverride is used to override the default FieldDecoder for a particular type. type FieldOverride struct { @@ -36,26 +41,32 @@ type FieldOverride struct { type factory struct { fieldStorage map[int64]FieldDecoder recursiveFieldStorage map[int64]*RecursiveDecoder + fieldOverrides []FieldOverride } // NewFactory creates a new Factory using the provided overrides, if any. func NewFactory(fieldOverrides ...FieldOverride) Factory { f := &factory{} + f.fieldOverrides = fieldOverrides + + return f +} +func (f *factory) resetStorages() { f.fieldStorage = make(map[int64]FieldDecoder) f.recursiveFieldStorage = make(map[int64]*RecursiveDecoder) - for _, fieldOverride := range fieldOverrides { + for _, fieldOverride := range f.fieldOverrides { f.fieldStorage[fieldOverride.FieldLookupIndex] = fieldOverride.FieldDecoder } - - return f } // CreateErrorRegistry creates the registry that contains the types for errors. // nolint:dupl func (f *factory) CreateErrorRegistry(meta *types.Metadata) (ErrorRegistry, error) { - errorRegistry := make(map[string]*Type) + f.resetStorages() + + errorRegistry := make(map[ErrorID]*TypeDecoder) for _, mod := range meta.AsMetadataV14.Pallets { if !mod.HasErrors { @@ -81,7 +92,12 @@ func (f *factory) CreateErrorRegistry(meta *types.Metadata) (ErrorRegistry, erro return nil, ErrErrorFieldsRetrieval.WithMsg(errorName).Wrap(err) } - errorRegistry[errorName] = &Type{ + errorID := ErrorID{ + ModuleIndex: mod.Index, + ErrorIndex: [4]types.U8{errorVariant.Index}, + } + + errorRegistry[errorID] = &TypeDecoder{ Name: errorName, Fields: errorFields, } @@ -98,7 +114,9 @@ func (f *factory) CreateErrorRegistry(meta *types.Metadata) (ErrorRegistry, erro // CreateCallRegistry creates the registry that contains the types for calls. // nolint:dupl func (f *factory) CreateCallRegistry(meta *types.Metadata) (CallRegistry, error) { - callRegistry := make(map[types.CallIndex]*Type) + f.resetStorages() + + callRegistry := make(map[types.CallIndex]*TypeDecoder) for _, mod := range meta.AsMetadataV14.Pallets { if !mod.HasCalls { @@ -129,7 +147,7 @@ func (f *factory) CreateCallRegistry(meta *types.Metadata) (CallRegistry, error) return nil, ErrCallFieldsRetrieval.WithMsg(callName).Wrap(err) } - callRegistry[callIndex] = &Type{ + callRegistry[callIndex] = &TypeDecoder{ Name: callName, Fields: callFields, } @@ -145,7 +163,9 @@ func (f *factory) CreateCallRegistry(meta *types.Metadata) (CallRegistry, error) // CreateEventRegistry creates the registry that contains the types for events. func (f *factory) CreateEventRegistry(meta *types.Metadata) (EventRegistry, error) { - eventRegistry := make(map[types.EventID]*Type) + f.resetStorages() + + eventRegistry := make(map[types.EventID]*TypeDecoder) for _, mod := range meta.AsMetadataV14.Pallets { if !mod.HasEvents { @@ -173,7 +193,7 @@ func (f *factory) CreateEventRegistry(meta *types.Metadata) (EventRegistry, erro return nil, ErrEventFieldsRetrieval.WithMsg(eventName).Wrap(err) } - eventRegistry[eventID] = &Type{ + eventRegistry[eventID] = &TypeDecoder{ Name: eventName, Fields: eventFields, } @@ -641,35 +661,6 @@ func getFieldName(field types.Si1Field) string { } } -// Type represents a parsed metadata type. -type Type struct { - Name string - Fields []*Field -} - -func (t *Type) Decode(decoder *scale.Decoder) (map[string]any, error) { - fieldMap := make(map[string]any) - - for _, field := range t.Fields { - value, err := field.FieldDecoder.Decode(decoder) - - if err != nil { - return nil, ErrTypeFieldDecoding.Wrap(err) - } - - fieldMap[field.Name] = value - } - - return fieldMap, nil -} - -// Field represents one field of a Type. -type Field struct { - Name string - FieldDecoder FieldDecoder - LookupIndex int64 -} - // FieldDecoder is the interface implemented by all the different types that are available. type FieldDecoder interface { Decode(decoder *scale.Decoder) (any, error) @@ -772,7 +763,7 @@ type CompositeDecoder struct { } func (e *CompositeDecoder) Decode(decoder *scale.Decoder) (any, error) { - fieldMap := make(map[string]any) + var decodedFields DecodedFields for _, field := range e.Fields { value, err := field.FieldDecoder.Decode(decoder) @@ -781,10 +772,14 @@ func (e *CompositeDecoder) Decode(decoder *scale.Decoder) (any, error) { return nil, ErrCompositeFieldDecoding.Wrap(err) } - fieldMap[field.Name] = value + decodedFields = append(decodedFields, &DecodedField{ + Name: field.Name, + Value: value, + LookupIndex: field.LookupIndex, + }) } - return fieldMap, nil + return decodedFields, nil } // ValueDecoder decodes a primitive type. @@ -830,3 +825,171 @@ func (b *BitSequenceDecoder) Decode(decoder *scale.Decoder) (any, error) { b.FieldName: bitVec.String(), }, nil } + +// TypeDecoder holds all information required to decode a particular type. +type TypeDecoder struct { + Name string + Fields []*Field +} + +func (t *TypeDecoder) Decode(decoder *scale.Decoder) (DecodedFields, error) { + if t == nil { + return nil, ErrNilTypeDecoder + } + + var decodedFields DecodedFields + + for _, field := range t.Fields { + decodedField, err := field.Decode(decoder) + + if err != nil { + return nil, ErrTypeFieldDecoding.Wrap(err) + } + + decodedFields = append(decodedFields, decodedField) + } + + return decodedFields, nil +} + +// Field represents one field of a TypeDecoder. +type Field struct { + Name string + FieldDecoder FieldDecoder + LookupIndex int64 +} + +func (f *Field) Decode(decoder *scale.Decoder) (*DecodedField, error) { + if f == nil { + return nil, ErrNilField + } + + if f.FieldDecoder == nil { + return nil, ErrNilFieldDecoder + } + + value, err := f.FieldDecoder.Decode(decoder) + + if err != nil { + return nil, err + } + + return &DecodedField{ + Name: f.Name, + Value: value, + LookupIndex: f.LookupIndex, + }, nil +} + +// DecodedField holds the name, value and lookup index of a field that was decoded. +type DecodedField struct { + Name string + Value any + LookupIndex int64 +} + +func (d DecodedField) Encode(encoder scale.Encoder) error { + if d.Value == nil { + return nil + } + + return encoder.Encode(d.Value) +} + +type DecodedFields []*DecodedField + +type DecodedFieldPredicateFn func(fieldIndex int, field *DecodedField) bool +type DecodedValueProcessingFn[T any] func(value any) (T, error) + +// ProcessDecodedFieldValue applies the processing func to the value of the field +// that matches the provided predicate func. +func ProcessDecodedFieldValue[T any]( + decodedFields DecodedFields, + fieldPredicateFn DecodedFieldPredicateFn, + valueProcessingFn DecodedValueProcessingFn[T], +) (T, error) { + var t T + + for decodedFieldIndex, decodedField := range decodedFields { + if !fieldPredicateFn(decodedFieldIndex, decodedField) { + continue + } + + res, err := valueProcessingFn(decodedField.Value) + + if err != nil { + return t, ErrDecodedFieldValueProcessingError.Wrap(err) + } + + return res, nil + } + + return t, ErrDecodedFieldNotFound +} + +// GetDecodedFieldAsType returns the value of the field that matches the provided predicate func +// as the provided generic argument. +func GetDecodedFieldAsType[T any]( + decodedFields DecodedFields, + fieldPredicateFn DecodedFieldPredicateFn, +) (T, error) { + return ProcessDecodedFieldValue( + decodedFields, + fieldPredicateFn, + func(value any) (T, error) { + if res, ok := value.(T); ok { + return res, nil + } + + var t T + + err := fmt.Errorf("expected %T, got %T", t, value) + + return t, ErrDecodedFieldValueTypeMismatch.Wrap(err) + }, + ) +} + +// GetDecodedFieldAsSliceOfType returns the value of the field that matches the provided predicate func +// as a slice of the provided generic argument. +func GetDecodedFieldAsSliceOfType[T any]( + decodedFields DecodedFields, + fieldPredicateFn DecodedFieldPredicateFn, +) ([]T, error) { + return ProcessDecodedFieldValue( + decodedFields, + fieldPredicateFn, + func(value any) ([]T, error) { + v, ok := value.([]any) + + if !ok { + return nil, ErrDecodedFieldValueNotAGenericSlice + } + + res, err := convertSliceToType[T](v) + + if err != nil { + return nil, ErrDecodedFieldValueTypeMismatch.Wrap(err) + } + + return res, nil + }, + ) +} + +func convertSliceToType[T any](slice []any) ([]T, error) { + res := make([]T, 0) + + for _, item := range slice { + if v, ok := item.(T); ok { + res = append(res, v) + continue + } + + var t T + + return nil, fmt.Errorf("expected %T, got %T", t, item) + } + + return res, nil +} diff --git a/registry/registry_test.go b/registry/factory_test.go similarity index 83% rename from registry/registry_test.go rename to registry/factory_test.go index 5f2258a94..539f2838a 100644 --- a/registry/registry_test.go +++ b/registry/factory_test.go @@ -1,9 +1,13 @@ package registry import ( + "bytes" + "errors" "fmt" "testing" + "github.com/centrifuge/go-substrate-rpc-client/v4/scale" + "github.com/centrifuge/go-substrate-rpc-client/v4/registry/test" "github.com/centrifuge/go-substrate-rpc-client/v4/types" "github.com/centrifuge/go-substrate-rpc-client/v4/types/codec" @@ -66,10 +70,13 @@ func TestFactory_CreateErrorRegistryWithLiveMetadata(t *testing.T) { assert.True(t, errorsType.Def.IsVariant, fmt.Sprintf("Error type %d not a variant", pallet.Events.Type.Int64())) for _, errorVariant := range errorsType.Def.Variant.Variants { - errorName := fmt.Sprintf("%s.%s", pallet.Name, errorVariant.Name) + errorID := ErrorID{ + ModuleIndex: pallet.Index, + ErrorIndex: [4]types.U8{errorVariant.Index}, + } - registryErrorType, ok := reg[errorName] - assert.True(t, ok, fmt.Sprintf("Error '%s' not found in registry", errorName)) + registryErrorType, ok := reg[errorID] + assert.True(t, ok, fmt.Sprintf("Error '%v' not found in registry", errorID)) testAsserter.assertRegistryItemContainsAllTypes(t, meta, registryErrorType.Fields, errorVariant.Fields) } @@ -516,6 +523,32 @@ func TestFactory_CreateEventRegistry_WithLiveMetadata(t *testing.T) { } } +func TestFactory_CreateEventRegistry_Overrides(t *testing.T) { + var meta types.Metadata + + err := codec.DecodeFromHex(test.CentrifugeMetadataHex, &meta) + assert.NoError(t, err) + + t.Log("Metadata was decoded successfully") + + // Lookup index for DispatchInfo in the test.CentrifugeMetadataHex + targetLookupIndex := int64(21) + + fieldOverride := FieldOverride{ + FieldLookupIndex: targetLookupIndex, + FieldDecoder: &ValueDecoder[types.DispatchInfo]{}, + } + + f := NewFactory(fieldOverride).(*factory) + + assert.Equal(t, f.fieldStorage[targetLookupIndex], &ValueDecoder[types.DispatchInfo]{}) + + _, err = f.CreateEventRegistry(&meta) + assert.NoError(t, err) + + assert.Equal(t, f.fieldStorage[targetLookupIndex], &ValueDecoder[types.DispatchInfo]{}) +} + func TestFactory_CreateEventRegistry_NoPalletWithEvents(t *testing.T) { testMeta := &types.Metadata{ AsMetadataV14: types.MetadataV14{ @@ -2020,6 +2053,474 @@ func Test_getPrimitiveType_UnsupportedTypeError(t *testing.T) { assert.Nil(t, res) } +func TestFactory_Overrides(t *testing.T) { + var meta types.Metadata + + err := codec.DecodeFromHex(test.CentrifugeMetadataHex, &meta) + assert.NoError(t, err) + + t.Log("Metadata was decoded successfully") + + // Lookup index for DispatchInfo in the test.CentrifugeMetadataHex + targetLookupIndex := int64(21) + + fieldOverride := FieldOverride{ + FieldLookupIndex: targetLookupIndex, + FieldDecoder: &ValueDecoder[types.DispatchInfo]{}, + } + + f := NewFactory(fieldOverride).(*factory) + + assert.Equal(t, f.fieldStorage[targetLookupIndex], &ValueDecoder[types.DispatchInfo]{}) + + reg, err := f.CreateEventRegistry(&meta) + assert.NoError(t, err) + + assert.Equal(t, f.fieldStorage[targetLookupIndex], &ValueDecoder[types.DispatchInfo]{}) + + // Event ID for System.ExtrinsicSuccess + extrinsicSuccessEventID := types.EventID{0, 0} + + testDispatchInfo := types.DispatchInfo{ + Weight: types.Weight{ + RefTime: types.NewUCompactFromUInt(1), + ProofSize: types.NewUCompactFromUInt(2), + }, + Class: types.DispatchClass{ + IsNormal: true, + }, + PaysFee: types.Pays{ + IsYes: true, + }, + } + + encodedTestDispatchInfo, err := codec.Encode(testDispatchInfo) + assert.NoError(t, err) + + extrinsicSuccessEventDecoder, ok := reg[extrinsicSuccessEventID] + assert.True(t, ok) + + res, err := extrinsicSuccessEventDecoder.Decode(scale.NewDecoder(bytes.NewReader(encodedTestDispatchInfo))) + assert.NoError(t, err) + assert.NotNil(t, res) + + value, err := ProcessDecodedFieldValue[types.DispatchInfo]( + res, + func(fieldIndex int, field *DecodedField) bool { + return field.LookupIndex == targetLookupIndex + }, + func(value any) (types.DispatchInfo, error) { + dispatchInfo, ok := value.(types.DispatchInfo) + assert.True(t, ok) + + return dispatchInfo, nil + }, + ) + assert.Equal(t, testDispatchInfo, value) +} + +func Test_TypeDecoder(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + typeDecoder := &TypeDecoder{ + Name: "test_decoder_1", + Fields: []*Field{ + { + Name: "field_1", + FieldDecoder: &ValueDecoder[types.U8]{}, + LookupIndex: 0, + }, + { + Name: "field_2", + FieldDecoder: &ValueDecoder[types.U16]{}, + LookupIndex: 1, + }, + { + Name: "field_3", + FieldDecoder: &ValueDecoder[types.U32]{}, + LookupIndex: 2, + }, + }, + } + + encodedTestData, err := encodeTestData(testData) + assert.NoError(t, err) + + decoder := scale.NewDecoder(bytes.NewReader(encodedTestData)) + + res, err := typeDecoder.Decode(decoder) + assert.NoError(t, err) + assert.Len(t, res, len(testData)) + assert.Equal(t, typeDecoder.Fields[0].Name, res[0].Name) + assert.Equal(t, typeDecoder.Fields[0].LookupIndex, res[0].LookupIndex) + assert.Equal(t, testData[0], res[0].Value) +} + +func Test_TypeDecoder_FieldDecodingError(t *testing.T) { + testData := []any{ + types.U32(3), + } + + typeDecoder := &TypeDecoder{ + Name: "test_decoder_1", + Fields: []*Field{ + { + Name: "field_1", + FieldDecoder: &ValueDecoder[[32]types.U8]{}, + LookupIndex: 0, + }, + }, + } + + encodedTestData, err := encodeTestData(testData) + assert.NoError(t, err) + + decoder := scale.NewDecoder(bytes.NewReader(encodedTestData)) + + res, err := typeDecoder.Decode(decoder) + assert.ErrorIs(t, err, ErrTypeFieldDecoding) + assert.Nil(t, res) +} + +func Test_ProcessDecodedFieldValue(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + // Field index match + res, err := ProcessDecodedFieldValue[types.U32]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + func(value any) (types.U32, error) { + res, ok := value.(types.U32) + assert.True(t, ok) + + return res, nil + }, + ) + + assert.NoError(t, err) + assert.Equal(t, testData[2], res) + + // Field name match + res, err = ProcessDecodedFieldValue[types.U32]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return field.Name == "decoded_field_2" + }, + func(value any) (types.U32, error) { + res, ok := value.(types.U32) + assert.True(t, ok) + + return res, nil + }, + ) + + assert.NoError(t, err) + assert.Equal(t, testData[2], res) + + // Field lookup index match + res, err = ProcessDecodedFieldValue[types.U32]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return field.LookupIndex == 2 + }, + func(value any) (types.U32, error) { + res, ok := value.(types.U32) + assert.True(t, ok) + + return res, nil + }, + ) + + assert.NoError(t, err) + assert.Equal(t, testData[2], res) +} + +func Test_ProcessDecodedFieldValue_FieldNotFoundError(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := ProcessDecodedFieldValue[types.U32]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return false + }, + func(value any) (types.U32, error) { + res, ok := value.(types.U32) + assert.True(t, ok) + + return res, nil + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldNotFound) + assert.Equal(t, types.U32(0), res) +} + +func Test_ProcessDecodedFieldValue_FieldValueProcessingError(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := ProcessDecodedFieldValue[types.U32]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + func(value any) (types.U32, error) { + return 0, errors.New("error") + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldValueProcessingError) + assert.Equal(t, types.U32(0), res) +} + +func Test_GetDecodedFieldAsType(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsType[types.U8]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == 0 + }, + ) + + assert.NoError(t, err) + assert.Equal(t, testData[0], res) +} + +func Test_GetDecodedFieldAsType_FieldNotFound(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsType[types.U8]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData) + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldNotFound) + assert.Equal(t, types.U8(0), res) +} + +func Test_GetDecodedFieldAsType_ValueTypeMismatch(t *testing.T) { + testData := []any{ + types.U8(1), + types.U16(2), + types.U32(3), + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsType[types.U8]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldValueTypeMismatch) + assert.Equal(t, types.U8(0), res) +} + +func Test_GetDecodedFieldAsSliceOfType(t *testing.T) { + testData := []any{ + types.U8(1), + []any{ + types.U16(0), + types.U16(1), + types.U16(2), + }, + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsSliceOfType[types.U16]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + ) + + assert.NoError(t, err) + assert.Equal(t, []types.U16{0, 1, 2}, res) +} + +func Test_GetDecodedFieldAsSliceOfType_DecodedFieldNotFound(t *testing.T) { + testData := []any{ + types.U8(1), + []any{ + types.U16(0), + types.U16(1), + types.U16(2), + }, + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsSliceOfType[types.U16]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData) + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldNotFound) + assert.Nil(t, res) +} + +func Test_GetDecodedFieldAsSliceOfType_NotAGenericSlice(t *testing.T) { + testData := []any{ + types.U8(1), + // Slices in decoded fields are expected to be []any + []types.U16{ + 0, + 1, + 2, + }, + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsSliceOfType[types.U16]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldValueNotAGenericSlice) + assert.Nil(t, res) +} + +func Test_GetDecodedFieldAsSliceOfType_SliceItemTypeMismatch(t *testing.T) { + testData := []any{ + types.U8(1), + []any{ + types.U16(0), + types.U16(1), + types.U16(2), + }, + } + + decodedFields := testDataToDecodedFields(testData) + + res, err := GetDecodedFieldAsSliceOfType[types.U8]( + decodedFields, + func(fieldIndex int, field *DecodedField) bool { + return fieldIndex == len(testData)-1 + }, + ) + + assert.ErrorIs(t, err, ErrDecodedFieldValueTypeMismatch) + assert.Nil(t, res) +} + +type errStruct struct{} + +func (e *errStruct) Decode(_ scale.Decoder) error { + return errors.New("error") +} + +type decodedFieldTest struct { + FirstField types.U8 + SecondField types.U16 + ThirdField types.U32 + + Error bool +} + +func (d *decodedFieldTest) Decode(decoder scale.Decoder) error { + if err := decoder.Decode(&d.FirstField); err != nil { + return err + } + + if err := decoder.Decode(&d.SecondField); err != nil { + return err + } + + return decoder.Decode(&d.ThirdField) +} + +func (d decodedFieldTest) Encode(encoder scale.Encoder) error { + if d.Error { + return errors.New("encode error") + } + + if err := encoder.Encode(d.FirstField); err != nil { + return err + } + + if err := encoder.Encode(d.SecondField); err != nil { + return err + } + + return encoder.Encode(d.ThirdField) +} + +func testDataToDecodedFields(data []any) DecodedFields { + var res DecodedFields + + for i, datum := range data { + res = append(res, &DecodedField{ + Name: fmt.Sprintf("decoded_field_%d", i), + Value: datum, + LookupIndex: int64(i), + }) + } + + return res +} + +func encodeTestData(data []any) ([]byte, error) { + var res []byte + + for _, datum := range data { + b, err := codec.Encode(datum) + + if err != nil { + return nil, err + } + + res = append(res, b...) + } + + return res, nil +} + type testAsserter struct { recursiveTypeMap map[int64]struct{} } diff --git a/registry/parser/event_parser.go b/registry/parser/event_parser.go index 5e34fee81..20763b9ce 100644 --- a/registry/parser/event_parser.go +++ b/registry/parser/event_parser.go @@ -12,7 +12,7 @@ import ( // Event holds all the information of a decoded storage event. type Event struct { Name string - Fields map[string]any + Fields registry.DecodedFields EventID types.EventID Phase *types.Phase Topics []types.Hash diff --git a/registry/parser/event_parser_test.go b/registry/parser/event_parser_test.go index 72365dccf..bc816f4fb 100644 --- a/registry/parser/event_parser_test.go +++ b/registry/parser/event_parser_test.go @@ -422,8 +422,8 @@ func TestEventParserFn_ParseEvents_TopicsDecodeError(t *testing.T) { } func assertEventFieldInformationIsCorrect(t *testing.T, testFields []testField, event *Event) { - for _, testField := range testFields { - assert.Equal(t, testField.Value, event.Fields[testField.Name]) + for testFieldIndex, testField := range testFields { + assert.Equal(t, testField.Value, event.Fields[testFieldIndex].Value) } } @@ -468,7 +468,7 @@ func getEncodedEventData(testEvents []testEvent) ([]byte, error) { } func getRegistryForTestEvents(testEvents []testEvent) (registry.EventRegistry, error) { - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) for _, testEvent := range testEvents { regFields, err := getTestRegistryFields(testEvent.EventFields) @@ -477,7 +477,7 @@ func getRegistryForTestEvents(testEvents []testEvent) (registry.EventRegistry, e return nil, err } - eventRegistry[testEvent.EventID] = ®istry.Type{ + eventRegistry[testEvent.EventID] = ®istry.TypeDecoder{ Name: testEvent.Name, Fields: regFields, } diff --git a/registry/parser/extrinsic_parser.go b/registry/parser/extrinsic_parser.go index b89744356..c0c28b99b 100644 --- a/registry/parser/extrinsic_parser.go +++ b/registry/parser/extrinsic_parser.go @@ -27,7 +27,7 @@ type DefaultExtrinsic = Extrinsic[ // information about these generic types. type Extrinsic[A, S, P any] struct { Name string - CallFields map[string]any + CallFields registry.DecodedFields CallIndex types.CallIndex Version byte Signature generic.GenericExtrinsicSignature[A, S, P] diff --git a/registry/parser/extrinsic_parser_test.go b/registry/parser/extrinsic_parser_test.go index 70f699301..f011d7cf8 100644 --- a/registry/parser/extrinsic_parser_test.go +++ b/registry/parser/extrinsic_parser_test.go @@ -264,8 +264,8 @@ func TestExtrinsicParserFn_ParseExtrinsics_DecodeError(t *testing.T) { } func assertExtrinsicFieldInformationIsCorrect[A, S, P any](t *testing.T, testFields []testField, extrinsic *Extrinsic[A, S, P]) { - for _, testField := range testFields { - assert.Equal(t, testField.Value, extrinsic.CallFields[testField.Name]) + for testFieldIndex, testField := range testFields { + assert.Equal(t, testField.Value, extrinsic.CallFields[testFieldIndex].Value) } } @@ -301,7 +301,7 @@ func getExtrinsicParsingTestData[A, S, P any](testExtrinsics []testExtrinsic[A, } func getRegistryForTestExtrinsic[A, S, P any](testExtrinsics []testExtrinsic[A, S, P]) (registry.CallRegistry, error) { - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) for _, testExtrinsic := range testExtrinsics { regFields, err := getTestRegistryFields(testExtrinsic.CallFields) @@ -310,7 +310,7 @@ func getRegistryForTestExtrinsic[A, S, P any](testExtrinsics []testExtrinsic[A, return nil, err } - callRegistry[testExtrinsic.CallIndex] = ®istry.Type{ + callRegistry[testExtrinsic.CallIndex] = ®istry.TypeDecoder{ Name: testExtrinsic.Name, Fields: regFields, } diff --git a/registry/retriever/event_retriever_test.go b/registry/retriever/event_retriever_test.go index 9fb020f50..ee7e07f1d 100644 --- a/registry/retriever/event_retriever_test.go +++ b/registry/retriever/event_retriever_test.go @@ -28,7 +28,7 @@ func TestEventRetriever_New(t *testing.T) { Return(latestMeta, nil). Once() - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) registryFactoryMock.On("CreateEventRegistry", latestMeta). Return(eventRegistry, nil). @@ -139,7 +139,7 @@ func TestEventRetriever_GetEvents(t *testing.T) { eventRetriever.meta = testMeta - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) eventRetriever.eventRegistry = eventRegistry @@ -207,7 +207,7 @@ func TestEventRetriever_GetEvents_StorageRetrievalError(t *testing.T) { eventRetriever.meta = testMeta - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) eventRetriever.eventRegistry = eventRegistry @@ -271,7 +271,7 @@ func TestEventRetriever_GetEvents_EventParsingError(t *testing.T) { eventRetriever.meta = testMeta - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) eventRetriever.eventRegistry = eventRegistry @@ -351,7 +351,7 @@ func TestEventRetriever_updateInternalState(t *testing.T) { testMeta := &types.Metadata{} - eventRegistry := registry.EventRegistry(map[types.EventID]*registry.Type{}) + eventRegistry := registry.EventRegistry(map[types.EventID]*registry.TypeDecoder{}) blockHash := types.NewHash([]byte{0, 1, 2, 3}) diff --git a/registry/retriever/extrinsic_retriever_test.go b/registry/retriever/extrinsic_retriever_test.go index 6b1c50970..8899ef95a 100644 --- a/registry/retriever/extrinsic_retriever_test.go +++ b/registry/retriever/extrinsic_retriever_test.go @@ -51,7 +51,7 @@ func TestExtrinsicRetriever_New(t *testing.T) { Return(latestMeta, nil). Once() - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) registryFactoryMock.On("CreateCallRegistry", latestMeta). Return(callRegistry, nil). @@ -196,7 +196,7 @@ func TestExtrinsicRetriever_NewDefault(t *testing.T) { registryFactoryMock := registry.NewFactoryMock(t) - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) registryFactoryMock.On("CreateCallRegistry", latestMeta). Return(callRegistry, nil). @@ -285,7 +285,7 @@ func TestExtrinsicRetriever_GetExtrinsics(t *testing.T) { extrinsicRetriever.meta = testMeta - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) extrinsicRetriever.callRegistry = callRegistry @@ -401,7 +401,7 @@ func TestExtrinsicRetriever_GetExtrinsics_BlockRetrievalError(t *testing.T) { extrinsicRetriever.meta = testMeta - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) extrinsicRetriever.callRegistry = callRegistry @@ -498,7 +498,7 @@ func TestExtrinsicRetriever_GetExtrinsics_ExtrinsicParsingError(t *testing.T) { extrinsicRetriever.meta = testMeta - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) extrinsicRetriever.callRegistry = callRegistry @@ -626,7 +626,7 @@ func TestExtrinsicRetriever_updateInternalState(t *testing.T) { testMeta := &types.Metadata{} - callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.Type{}) + callRegistry := registry.CallRegistry(map[types.CallIndex]*registry.TypeDecoder{}) blockHash := types.NewHash([]byte{0, 1, 2, 3}) diff --git a/types/account_info_test.go b/types/account_info_test.go index 59c501825..ec9f9404f 100644 --- a/types/account_info_test.go +++ b/types/account_info_test.go @@ -26,7 +26,6 @@ import ( func TestAccountInfoV4_EncodeDecode(t *testing.T) { AssertRoundTripFuzz[AccountInfoV4](t, 100) - AssertDecodeNilData[AccountInfoV4](t) AssertEncodeEmptyObj[AccountInfoV4](t, 9) } diff --git a/types/peer_info_test.go b/types/peer_info_test.go index 59999e729..0ebb8bd94 100644 --- a/types/peer_info_test.go +++ b/types/peer_info_test.go @@ -35,7 +35,6 @@ var testPeerInfo = PeerInfo{ func TestPeerInfo_EncodeDecode(t *testing.T) { AssertRoundtrip(t, testPeerInfo) AssertRoundTripFuzz[PeerInfo](t, 100) - AssertDecodeNilData[PeerInfo](t) AssertEncodeEmptyObj[PeerInfo](t, 42) } diff --git a/types/proxy_test.go b/types/proxy_test.go index fcb167476..f06f3e5b5 100644 --- a/types/proxy_test.go +++ b/types/proxy_test.go @@ -66,7 +66,6 @@ var ( func TestProxyStorageEntry_EncodeDecode(t *testing.T) { AssertRoundTripFuzz[ProxyStorageEntry](t, 1000) - AssertDecodeNilData[ProxyStorageEntry](t) AssertEncodeEmptyObj[ProxyStorageEntry](t, 17) } diff --git a/types/sale_test.go b/types/sale_test.go index 9a76c0dd1..dcf10a88a 100644 --- a/types/sale_test.go +++ b/types/sale_test.go @@ -56,42 +56,48 @@ var ( IsNative: true, } testCurrencyID2 = CurrencyID{ - IsUsd: true, - } - testCurrencyID3 = CurrencyID{ IsTranche: true, Tranche: testTranche, } - testCurrencyID4 = CurrencyID{ + testCurrencyID3 = CurrencyID{ IsKSM: true, } + testCurrencyID4 = CurrencyID{ + IsAUSD: true, + } testCurrencyID5 = CurrencyID{ - IsKUSD: true, + IsForeignAsset: true, + AsForeignAsset: 0, } testCurrencyID6 = CurrencyID{ - IsPermissioned: true, - PermissionedCurrency: PermissionedCurrency{}, + IsStaking: true, + AsStaking: StakingCurrency{ + IsBlockRewards: true, + }, } currencyIDFuzzOpts = []FuzzOpt{ + WithFuzzFuncs(func(stakingCurrency *StakingCurrency, c fuzz.Continue) { + stakingCurrency.IsBlockRewards = true + }), WithFuzzFuncs(func(cID *CurrencyID, c fuzz.Continue) { switch c.Intn(6) { case 0: cID.IsNative = true case 1: - cID.IsUsd = true - case 2: cID.IsTranche = true - c.Fuzz(&cID.Tranche) - case 3: + case 2: cID.IsKSM = true + case 3: + cID.IsAUSD = true case 4: - cID.IsKUSD = true + cID.IsForeignAsset = true + c.Fuzz(&cID.AsForeignAsset) case 5: - cID.IsPermissioned = true + cID.IsStaking = true - c.Fuzz(&cID.PermissionedCurrency) + c.Fuzz(&cID.AsStaking) } }), } @@ -100,28 +106,27 @@ var ( func TestCurrencyID_EncodeDecode(t *testing.T) { AssertRoundTripFuzz[CurrencyID](t, 1000, currencyIDFuzzOpts...) AssertDecodeNilData[CurrencyID](t) - AssertEncodeEmptyObj[CurrencyID](t, 0) } func TestCurrencyID_Encode(t *testing.T) { AssertEncode(t, []EncodingAssert{ {testCurrencyID1, MustHexDecodeString("0x00")}, - {testCurrencyID2, MustHexDecodeString("0x01")}, - {testCurrencyID3, MustHexDecodeString("0x02430100000000000004050603010302040000000000000000")}, + {testCurrencyID2, MustHexDecodeString("0x01430100000000000004050603010302040000000000000000")}, + {testCurrencyID3, MustHexDecodeString("0x02")}, {testCurrencyID4, MustHexDecodeString("0x03")}, - {testCurrencyID5, MustHexDecodeString("0x04")}, - {testCurrencyID6, MustHexDecodeString("0x05")}, + {testCurrencyID5, MustHexDecodeString("0x0400000000")}, + {testCurrencyID6, MustHexDecodeString("0x0500")}, }) } func TestCurrencyID_Decode(t *testing.T) { AssertDecode(t, []DecodingAssert{ {MustHexDecodeString("0x00"), testCurrencyID1}, - {MustHexDecodeString("0x01"), testCurrencyID2}, - {MustHexDecodeString("0x02430100000000000004050603010302040000000000000000"), testCurrencyID3}, + {MustHexDecodeString("0x01430100000000000004050603010302040000000000000000"), testCurrencyID2}, + {MustHexDecodeString("0x02"), testCurrencyID3}, {MustHexDecodeString("0x03"), testCurrencyID4}, - {MustHexDecodeString("0x04"), testCurrencyID5}, - {MustHexDecodeString("0x05"), testCurrencyID6}, + {MustHexDecodeString("0x0400000000"), testCurrencyID5}, + {MustHexDecodeString("0x0500"), testCurrencyID6}, }) } @@ -135,7 +140,6 @@ var ( func TestPrice_EncodeDecode(t *testing.T) { AssertRoundTripFuzz[Price](t, 100, currencyIDFuzzOpts...) AssertDecodeNilData[Price](t) - AssertEncodeEmptyObj[Price](t, 16) } func TestPrice_Encode(t *testing.T) { @@ -160,7 +164,6 @@ var ( func TestSale_EncodeDecode(t *testing.T) { AssertRoundTripFuzz[Sale](t, 100, currencyIDFuzzOpts...) AssertDecodeNilData[Sale](t) - AssertEncodeEmptyObj[Sale](t, 48) } func TestSale_Encode(t *testing.T) {