diff --git a/api_v3.go b/api_v3.go index 1b6a6461b..6a08906d5 100644 --- a/api_v3.go +++ b/api_v3.go @@ -90,7 +90,7 @@ var ( anchorOutputV3FeatBlocksArrRules = &serix.ArrayRules{ Min: 0, // Min: - - Max: 2, // Max: SenderFeature, MetadataFeature + Max: 3, // Max: SenderFeature, MetadataFeature, StateMetadataFeature ValidationMode: serializer.ArrayValidationModeNoDuplicates | serializer.ArrayValidationModeLexicalOrdering | serializer.ArrayValidationModeAtMostOneOfEachTypeByte, @@ -340,9 +340,47 @@ func V3API(protoParams ProtocolParameters) API { must(api.RegisterTypeSettings(IssuerFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureIssuer))), ) + + must(api.RegisterTypeSettings(MetadataFeatureEntriesKey(""), + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte)), + ) + must(api.RegisterValidators(MetadataFeatureEntriesKey(""), nil, func(ctx context.Context, key MetadataFeatureEntriesKey) error { + if err := checkASCIIString(string(key)); err != nil { + return ierrors.Join(ErrInvalidMetadataKey, err) + } + + return nil + })) + must(api.RegisterTypeSettings(MetadataFeatureEntriesValue{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsUint16)), + ) + must(api.RegisterTypeSettings(MetadataFeatureEntries{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxByteSize(8192)), + ) must(api.RegisterTypeSettings(MetadataFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureMetadata))), ) + + must(api.RegisterTypeSettings(StateMetadataFeatureEntriesKey(""), + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte)), + ) + must(api.RegisterValidators(StateMetadataFeatureEntriesKey(""), nil, func(ctx context.Context, key StateMetadataFeatureEntriesKey) error { + if err := checkASCIIString(string(key)); err != nil { + return ierrors.Join(ErrInvalidStateMetadataKey, err) + } + + return nil + })) + must(api.RegisterTypeSettings(StateMetadataFeatureEntriesValue{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsUint16)), + ) + must(api.RegisterTypeSettings(StateMetadataFeatureEntries{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxByteSize(8192)), + ) + must(api.RegisterTypeSettings(StateMetadataFeature{}, + serix.TypeSettings{}.WithObjectType(uint8(FeatureStateMetadata))), + ) + must(api.RegisterTypeSettings(TagFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureTag))), ) @@ -358,6 +396,7 @@ func V3API(protoParams ProtocolParameters) API { must(api.RegisterInterfaceObjects((*Feature)(nil), (*SenderFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*IssuerFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*MetadataFeature)(nil))) + must(api.RegisterInterfaceObjects((*Feature)(nil), (*StateMetadataFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*TagFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*NativeTokenFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*BlockIssuerFeature)(nil))) @@ -496,6 +535,7 @@ func V3API(protoParams ProtocolParameters) API { must(api.RegisterInterfaceObjects((*anchorOutputFeature)(nil), (*SenderFeature)(nil))) must(api.RegisterInterfaceObjects((*anchorOutputFeature)(nil), (*MetadataFeature)(nil))) + must(api.RegisterInterfaceObjects((*anchorOutputFeature)(nil), (*StateMetadataFeature)(nil))) must(api.RegisterTypeSettings(AnchorOutputImmFeatures{}, serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithArrayRules(anchorOutputV3ImmFeatBlocksArrRules), diff --git a/builder/output_builder_account.go b/builder/output_builder_account.go index 643c1c994..dcde01703 100644 --- a/builder/output_builder_account.go +++ b/builder/output_builder_account.go @@ -114,16 +114,16 @@ func (builder *AccountOutputBuilder) ImmutableSender(senderAddr iotago.Address) } // Metadata sets/modifies an iotago.MetadataFeature on the output. -func (builder *AccountOutputBuilder) Metadata(data []byte) *AccountOutputBuilder { - builder.output.Features.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *AccountOutputBuilder) Metadata(entries iotago.MetadataFeatureEntries) *AccountOutputBuilder { + builder.output.Features.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } // ImmutableMetadata sets/modifies an iotago.MetadataFeature as an immutable feature on the output. // Only call this function on a new iotago.AccountOutput. -func (builder *AccountOutputBuilder) ImmutableMetadata(data []byte) *AccountOutputBuilder { - builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *AccountOutputBuilder) ImmutableMetadata(entries iotago.MetadataFeatureEntries) *AccountOutputBuilder { + builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } diff --git a/builder/output_builder_anchor.go b/builder/output_builder_anchor.go index 08a762d4d..978fe4d3a 100644 --- a/builder/output_builder_anchor.go +++ b/builder/output_builder_anchor.go @@ -8,11 +8,10 @@ import ( // NewAnchorOutputBuilder creates a new AnchorOutputBuilder with the required state controller/governor addresses and base token amount. func NewAnchorOutputBuilder(stateCtrl iotago.Address, govAddr iotago.Address, amount iotago.BaseToken) *AnchorOutputBuilder { return &AnchorOutputBuilder{output: &iotago.AnchorOutput{ - Amount: amount, - Mana: 0, - AnchorID: iotago.EmptyAnchorID, - StateIndex: 0, - StateMetadata: []byte{}, + Amount: amount, + Mana: 0, + AnchorID: iotago.EmptyAnchorID, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: stateCtrl}, &iotago.GovernorAddressUnlockCondition{Address: govAddr}, @@ -62,14 +61,6 @@ func (builder *AnchorOutputBuilder) AnchorID(anchorID iotago.AnchorID) *AnchorOu return builder } -// StateMetadata sets the state metadata of the output. -func (builder *AnchorOutputBuilder) StateMetadata(data []byte) *AnchorOutputBuilder { - builder.output.StateMetadata = data - builder.stateCtrlReq = true - - return builder -} - // StateController sets the iotago.StateControllerAddressUnlockCondition of the output. func (builder *AnchorOutputBuilder) StateController(stateCtrl iotago.Address) *AnchorOutputBuilder { builder.output.UnlockConditions.Upsert(&iotago.StateControllerAddressUnlockCondition{Address: stateCtrl}) @@ -103,17 +94,25 @@ func (builder *AnchorOutputBuilder) ImmutableSender(senderAddr iotago.Address) * } // Metadata sets/modifies an iotago.MetadataFeature on the output. -func (builder *AnchorOutputBuilder) Metadata(data []byte) *AnchorOutputBuilder { - builder.output.Features.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *AnchorOutputBuilder) Metadata(entries iotago.MetadataFeatureEntries) *AnchorOutputBuilder { + builder.output.Features.Upsert(&iotago.MetadataFeature{Entries: entries}) builder.govCtrlReq = true return builder } +// StateMetadata sets/modifies an iotago.StateMetadataFeature on the output. +func (builder *AnchorOutputBuilder) StateMetadata(entries iotago.StateMetadataFeatureEntries) *AnchorOutputBuilder { + builder.output.Features.Upsert(&iotago.StateMetadataFeature{Entries: entries}) + builder.stateCtrlReq = true + + return builder +} + // ImmutableMetadata sets/modifies an iotago.MetadataFeature as an immutable feature on the output. // Only call this function on a new iotago.AnchorOutput. -func (builder *AnchorOutputBuilder) ImmutableMetadata(data []byte) *AnchorOutputBuilder { - builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *AnchorOutputBuilder) ImmutableMetadata(entries iotago.MetadataFeatureEntries) *AnchorOutputBuilder { + builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } @@ -171,9 +170,9 @@ func (trans *AnchorStateTransition) Mana(mana iotago.Mana) *AnchorStateTransitio return trans.builder.Mana(mana).StateTransition() } -// StateMetadata sets the state metadata of the output. -func (trans *AnchorStateTransition) StateMetadata(data []byte) *AnchorStateTransition { - return trans.builder.StateMetadata(data).StateTransition() +// StateMetadata sets/modifies an iotago.StateMetadataFeature on the output. +func (trans *AnchorStateTransition) StateMetadata(entries iotago.StateMetadataFeatureEntries) *AnchorStateTransition { + return trans.builder.StateMetadata(entries).StateTransition() } // Sender sets/modifies an iotago.SenderFeature as a mutable feature on the output. @@ -213,8 +212,8 @@ func (trans *AnchorGovernanceTransition) Sender(senderAddr iotago.Address) *Anch } // Metadata sets/modifies an iotago.MetadataFeature as a mutable feature on the output. -func (trans *AnchorGovernanceTransition) Metadata(data []byte) *AnchorGovernanceTransition { - return trans.builder.Metadata(data).GovernanceTransition() +func (trans *AnchorGovernanceTransition) Metadata(entries iotago.MetadataFeatureEntries) *AnchorGovernanceTransition { + return trans.builder.Metadata(entries).GovernanceTransition() } // Builder returns the AnchorOutputBuilder. diff --git a/builder/output_builder_basic.go b/builder/output_builder_basic.go index f1dcd2927..1acae41e0 100644 --- a/builder/output_builder_basic.go +++ b/builder/output_builder_basic.go @@ -84,8 +84,8 @@ func (builder *BasicOutputBuilder) Sender(senderAddr iotago.Address) *BasicOutpu } // Metadata sets/modifies an iotago.MetadataFeature on the output. -func (builder *BasicOutputBuilder) Metadata(data []byte) *BasicOutputBuilder { - builder.output.Features.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *BasicOutputBuilder) Metadata(entries iotago.MetadataFeatureEntries) *BasicOutputBuilder { + builder.output.Features.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } diff --git a/builder/output_builder_foundry.go b/builder/output_builder_foundry.go index 02f00cc4b..9873000a2 100644 --- a/builder/output_builder_foundry.go +++ b/builder/output_builder_foundry.go @@ -49,16 +49,16 @@ func (builder *FoundryOutputBuilder) NativeToken(nt *iotago.NativeTokenFeature) } // Metadata sets/modifies an iotago.MetadataFeature on the output. -func (builder *FoundryOutputBuilder) Metadata(data []byte) *FoundryOutputBuilder { - builder.output.Features.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *FoundryOutputBuilder) Metadata(entries iotago.MetadataFeatureEntries) *FoundryOutputBuilder { + builder.output.Features.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } // ImmutableMetadata sets/modifies an iotago.MetadataFeature as an immutable feature on the output. -// Only call this function on a new iotago.AccountOutput. -func (builder *FoundryOutputBuilder) ImmutableMetadata(data []byte) *FoundryOutputBuilder { - builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Data: data}) +// Only call this function on a new iotago.FoundryOutput. +func (builder *FoundryOutputBuilder) ImmutableMetadata(entries iotago.MetadataFeatureEntries) *FoundryOutputBuilder { + builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } diff --git a/builder/output_builder_nft.go b/builder/output_builder_nft.go index f81944ec0..a6650b846 100644 --- a/builder/output_builder_nft.go +++ b/builder/output_builder_nft.go @@ -92,16 +92,16 @@ func (builder *NFTOutputBuilder) Sender(senderAddr iotago.Address) *NFTOutputBui } // Metadata sets/modifies an iotago.MetadataFeature on the output. -func (builder *NFTOutputBuilder) Metadata(data []byte) *NFTOutputBuilder { - builder.output.Features.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *NFTOutputBuilder) Metadata(entries iotago.MetadataFeatureEntries) *NFTOutputBuilder { + builder.output.Features.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } // ImmutableMetadata sets/modifies an iotago.MetadataFeature as an immutable feature on the output. // Only call this function on a new iotago.NFTOutput. -func (builder *NFTOutputBuilder) ImmutableMetadata(data []byte) *NFTOutputBuilder { - builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Data: data}) +func (builder *NFTOutputBuilder) ImmutableMetadata(entries iotago.MetadataFeatureEntries) *NFTOutputBuilder { + builder.output.ImmutableFeatures.Upsert(&iotago.MetadataFeature{Entries: entries}) return builder } diff --git a/builder/output_builder_test.go b/builder/output_builder_test.go index 0f93f689f..03928d5cf 100644 --- a/builder/output_builder_test.go +++ b/builder/output_builder_test.go @@ -18,7 +18,7 @@ func TestBasicOutputBuilder(t *testing.T) { amount iotago.BaseToken = 1337 nativeTokenFeature = tpkg.RandNativeTokenFeature() expirationTarget = tpkg.RandEd25519Address() - metadata = []byte("123456") + metadataEntries = iotago.MetadataFeatureEntries{"data": []byte("123456")} slotTimeProvider = iotago.NewTimeProvider(0, time.Now().Unix(), 10, 10) ) timelock := slotTimeProvider.SlotFromTime(time.Now().Add(5 * time.Minute)) @@ -28,7 +28,7 @@ func TestBasicOutputBuilder(t *testing.T) { NativeToken(nativeTokenFeature). Timelock(timelock). Expiration(expirationTarget, expiration). - Metadata(metadata). + Metadata(metadataEntries). Build() require.NoError(t, err) @@ -40,7 +40,7 @@ func TestBasicOutputBuilder(t *testing.T) { &iotago.ExpirationUnlockCondition{ReturnAddress: expirationTarget, Slot: expiration}, }, Features: iotago.BasicOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.MetadataFeature{Entries: metadataEntries}, nativeTokenFeature, }, }, basicOutput) @@ -48,11 +48,11 @@ func TestBasicOutputBuilder(t *testing.T) { func TestAccountOutputBuilder(t *testing.T) { var ( - addr = tpkg.RandEd25519Address() - amount iotago.BaseToken = 1337 - metadata = []byte("123456") - immMetadata = []byte("654321") - immSender = tpkg.RandEd25519Address() + addr = tpkg.RandEd25519Address() + amount iotago.BaseToken = 1337 + metadataEntries = iotago.MetadataFeatureEntries{"data": []byte("123456")} + immMetadataEntries = iotago.MetadataFeatureEntries{"data": []byte("654321")} + immSender = tpkg.RandEd25519Address() blockIssuerKey1 = iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(tpkg.Rand32ByteArray()) blockIssuerKey2 = iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(tpkg.Rand32ByteArray()) @@ -62,10 +62,10 @@ func TestAccountOutputBuilder(t *testing.T) { ) accountOutput, err := builder.NewAccountOutputBuilder(addr, amount). - Metadata(metadata). + Metadata(metadataEntries). Staking(amount, 1, 1000). BlockIssuer(iotago.NewBlockIssuerKeys(blockIssuerKey1, blockIssuerKey2, blockIssuerKey3), 100000). - ImmutableMetadata(immMetadata). + ImmutableMetadata(immMetadataEntries). ImmutableSender(immSender). FoundriesToGenerate(5). Build() @@ -80,7 +80,7 @@ func TestAccountOutputBuilder(t *testing.T) { &iotago.AddressUnlockCondition{Address: addr}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.MetadataFeature{Entries: metadataEntries}, &iotago.BlockIssuerFeature{ BlockIssuerKeys: expectedBlockIssuerKeys, ExpirySlot: 100000, @@ -94,7 +94,7 @@ func TestAccountOutputBuilder(t *testing.T) { }, ImmutableFeatures: iotago.AccountOutputImmFeatures{ &iotago.SenderFeature{Address: immSender}, - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, } require.True(t, expected.Equal(accountOutput), "account output should be equal") @@ -130,7 +130,7 @@ func TestAccountOutputBuilder(t *testing.T) { &iotago.AddressUnlockCondition{Address: addr}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.MetadataFeature{Entries: metadataEntries}, &iotago.BlockIssuerFeature{ BlockIssuerKeys: expectedUpdatedBlockIssuerKeys, ExpirySlot: 1500, @@ -144,7 +144,7 @@ func TestAccountOutputBuilder(t *testing.T) { }, ImmutableFeatures: iotago.AccountOutputImmFeatures{ &iotago.SenderFeature{Address: immSender}, - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, } require.True(t, expectedFeatures.Equal(updatedFeatures), "features should be equal") @@ -152,50 +152,51 @@ func TestAccountOutputBuilder(t *testing.T) { func TestAnchorOutputBuilder(t *testing.T) { var ( - stateCtrl = tpkg.RandEd25519Address() - stateCtrlNew = tpkg.RandEd25519Address() - gov = tpkg.RandEd25519Address() - amount iotago.BaseToken = 1337 - metadata = []byte("123456") - immMetadata = []byte("654321") - immSender = tpkg.RandEd25519Address() + stateCtrl = tpkg.RandEd25519Address() + stateCtrlNew = tpkg.RandEd25519Address() + gov = tpkg.RandEd25519Address() + amount iotago.BaseToken = 1337 + stateMetadataEntries = iotago.StateMetadataFeatureEntries{"data": []byte("123456")} + immMetadataEntries = iotago.MetadataFeatureEntries{"data": []byte("654321")} + immSender = tpkg.RandEd25519Address() ) anchorOutput, err := builder.NewAnchorOutputBuilder(stateCtrl, gov, amount). - Metadata(metadata). - StateMetadata(metadata). - ImmutableMetadata(immMetadata). + StateMetadata(stateMetadataEntries). + ImmutableMetadata(immMetadataEntries). ImmutableSender(immSender). Build() require.NoError(t, err) expected := &iotago.AnchorOutput{ - Amount: amount, - StateIndex: 0, - StateMetadata: metadata, + Amount: amount, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: stateCtrl}, &iotago.GovernorAddressUnlockCondition{Address: gov}, }, Features: iotago.AnchorOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.StateMetadataFeature{Entries: stateMetadataEntries}, }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ &iotago.SenderFeature{Address: immSender}, - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, } require.True(t, expected.Equal(anchorOutput), "anchor output should be equal") const newAmount iotago.BaseToken = 7331 + newStateMetadataEntries := iotago.StateMetadataFeatureEntries{"newData": []byte("newState")} + //nolint:forcetypeassert // we can safely assume that this is an AnchorOutput expectedCpy := expected.Clone().(*iotago.AnchorOutput) expectedCpy.Amount = newAmount expectedCpy.StateIndex++ - expectedCpy.StateMetadata = []byte("newState") + expectedCpy.Features.Upsert(&iotago.StateMetadataFeature{Entries: newStateMetadataEntries}) + updatedOutput, err := builder.NewAnchorOutputBuilderFromPrevious(anchorOutput).StateTransition(). Amount(newAmount). - StateMetadata([]byte("newState")). + StateMetadata(newStateMetadataEntries). Builder().Build() require.NoError(t, err) require.Equal(t, expectedCpy, updatedOutput) @@ -206,19 +207,18 @@ func TestAnchorOutputBuilder(t *testing.T) { require.NoError(t, err) expectedOutput2 := &iotago.AnchorOutput{ - Amount: amount, - StateIndex: 0, - StateMetadata: metadata, + Amount: amount, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: stateCtrlNew}, &iotago.GovernorAddressUnlockCondition{Address: gov}, }, Features: iotago.AnchorOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.StateMetadataFeature{Entries: stateMetadataEntries}, }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ &iotago.SenderFeature{Address: immSender}, - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, } require.True(t, expectedOutput2.Equal(updatedOutput2), "outputs should be equal") @@ -286,14 +286,14 @@ func TestFoundryOutputBuilder(t *testing.T) { MaximumSupply: big.NewInt(1000), } nativeTokenFeature = tpkg.RandNativeTokenFeature() - metadata = []byte("123456") - immMetadata = []byte("654321") + metadataEntries = iotago.MetadataFeatureEntries{"data": []byte("123456")} + immMetadataEntries = iotago.MetadataFeatureEntries{"data": []byte("654321")} ) foundryOutput, err := builder.NewFoundryOutputBuilder(accountAddr, tokenScheme, amount). NativeToken(nativeTokenFeature). - Metadata(metadata). - ImmutableMetadata(immMetadata). + Metadata(metadataEntries). + ImmutableMetadata(immMetadataEntries). Build() require.NoError(t, err) @@ -304,26 +304,26 @@ func TestFoundryOutputBuilder(t *testing.T) { &iotago.ImmutableAccountUnlockCondition{Address: accountAddr}, }, Features: iotago.FoundryOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.MetadataFeature{Entries: metadataEntries}, nativeTokenFeature, }, ImmutableFeatures: iotago.FoundryOutputImmFeatures{ - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, }, foundryOutput) } func TestNFTOutputBuilder(t *testing.T) { var ( - targetAddr = tpkg.RandAccountAddress() - amount iotago.BaseToken = 1337 - metadata = []byte("123456") - immMetadata = []byte("654321") + targetAddr = tpkg.RandAccountAddress() + amount iotago.BaseToken = 1337 + metadataEntries = iotago.MetadataFeatureEntries{"data": []byte("123456")} + immMetadataEntries = iotago.MetadataFeatureEntries{"data": []byte("654321")} ) nftOutput, err := builder.NewNFTOutputBuilder(targetAddr, amount). - Metadata(metadata). - ImmutableMetadata(immMetadata). + Metadata(metadataEntries). + ImmutableMetadata(immMetadataEntries). Build() require.NoError(t, err) @@ -333,10 +333,10 @@ func TestNFTOutputBuilder(t *testing.T) { &iotago.AddressUnlockCondition{Address: targetAddr}, }, Features: iotago.NFTOutputFeatures{ - &iotago.MetadataFeature{Data: metadata}, + &iotago.MetadataFeature{Entries: metadataEntries}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: immMetadata}, + &iotago.MetadataFeature{Entries: immMetadataEntries}, }, }, nftOutput) } diff --git a/feat.go b/feat.go index 8bb9a77aa..405148af8 100644 --- a/feat.go +++ b/feat.go @@ -3,6 +3,7 @@ package iotago import ( "fmt" "sort" + "unicode" "github.com/iotaledger/hive.go/constraints" "github.com/iotaledger/hive.go/ierrors" @@ -15,6 +16,10 @@ var ( ErrNonUniqueFeatures = ierrors.New("non unique features within outputs") // ErrInvalidFeatureTransition gets returned when a Feature's transition within a ChainOutput is invalid. ErrInvalidFeatureTransition = ierrors.New("invalid feature transition") + // ErrInvalidMetadataKey gets returned when a MetadataFeature's key is invalid. + ErrInvalidMetadataKey = ierrors.New("invalid metadata key") + // ErrInvalidStateMetadataKey gets returned when a StateMetadataFeature's key is invalid. + ErrInvalidStateMetadataKey = ierrors.New("invalid state metadata key") ) // Feature is an abstract building block extending the features of an Output. @@ -39,6 +44,8 @@ const ( FeatureIssuer // FeatureMetadata denotes a MetadataFeature. FeatureMetadata + // FeatureStateMetadata denotes a StateMetadataFeature. + FeatureStateMetadata // FeatureTag denotes a TagFeature. FeatureTag // NativeTokenFeature denotes a NativeTokenFeature. @@ -58,7 +65,14 @@ func (featType FeatureType) String() string { } var featNames = [FeatureStaking + 1]string{ - "SenderFeature", "IssuerFeature", "MetadataFeature", "TagFeature", "NativeTokenFeature", "BlockIssuerFeature", "StakingFeature", + "SenderFeature", + "IssuerFeature", + "MetadataFeature", + "StateMetadataFeature", + "TagFeature", + "NativeTokenFeature", + "BlockIssuerFeature", + "StakingFeature", } // Features is a slice of Feature(s). @@ -192,37 +206,26 @@ func (f FeatureSet) Issuer() *IssuerFeature { return b.(*IssuerFeature) } -// BlockIssuer returns the BlockIssuerFeature in the set or nil. -func (f FeatureSet) BlockIssuer() *BlockIssuerFeature { - b, has := f[FeatureBlockIssuer] - if !has { - return nil - } - - //nolint:forcetypeassert // we can safely assume that this is a BlockIssuerFeature - return b.(*BlockIssuerFeature) -} - -// Staking returns the StakingFeature in the set or nil. -func (f FeatureSet) Staking() *StakingFeature { - b, has := f[FeatureStaking] +// Metadata returns the MetadataFeature in the set or nil. +func (f FeatureSet) Metadata() *MetadataFeature { + b, has := f[FeatureMetadata] if !has { return nil } - //nolint:forcetypeassert // we can safely assume that this is a StakingFeature - return b.(*StakingFeature) + //nolint:forcetypeassert // we can safely assume that this is a MetadataFeature + return b.(*MetadataFeature) } -// Metadata returns the MetadataFeature in the set or nil. -func (f FeatureSet) Metadata() *MetadataFeature { - b, has := f[FeatureMetadata] +// StateMetadata returns the StateMetadataFeature in the set or nil. +func (f FeatureSet) StateMetadata() *StateMetadataFeature { + b, has := f[FeatureStateMetadata] if !has { return nil } - //nolint:forcetypeassert // we can safely assume that this is a MetadataFeature - return b.(*MetadataFeature) + //nolint:forcetypeassert // we can safely assume that this is a StateMetadataFeature + return b.(*StateMetadataFeature) } // Tag returns the TagFeature in the set or nil. @@ -253,6 +256,28 @@ func (f FeatureSet) NativeToken() *NativeTokenFeature { return b.(*NativeTokenFeature) } +// BlockIssuer returns the BlockIssuerFeature in the set or nil. +func (f FeatureSet) BlockIssuer() *BlockIssuerFeature { + b, has := f[FeatureBlockIssuer] + if !has { + return nil + } + + //nolint:forcetypeassert // we can safely assume that this is a BlockIssuerFeature + return b.(*BlockIssuerFeature) +} + +// Staking returns the StakingFeature in the set or nil. +func (f FeatureSet) Staking() *StakingFeature { + b, has := f[FeatureStaking] + if !has { + return nil + } + + //nolint:forcetypeassert // we can safely assume that this is a StakingFeature + return b.(*StakingFeature) +} + // EveryTuple runs f for every key which exists in both this set and other. // Returns a bool indicating whether all element of this set existed on the other set. func (f FeatureSet) EveryTuple(other FeatureSet, fun func(a Feature, b Feature) error) (bool, error) { @@ -296,3 +321,13 @@ func FeatureUnchanged(featType FeatureType, inFeatSet FeatureSet, outFeatSet Fea return nil } + +func checkASCIIString(s string) error { + for i := 0; i < len(s); i++ { + if s[i] > unicode.MaxASCII { + return ierrors.Errorf("string contains non-ASCII character at index %d", i) + } + } + + return nil +} diff --git a/feat_metadata.gen.go b/feat_metadata.gen.go new file mode 100644 index 000000000..ec6fd4118 --- /dev/null +++ b/feat_metadata.gen.go @@ -0,0 +1,86 @@ +package iotago + +// Code generated by go generate; DO NOT EDIT. Check gen/ directory instead. + +import ( + "bytes" + + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/serializer/v2" +) + +type MetadataFeatureEntriesKey string +type MetadataFeatureEntriesValue []byte +type MetadataFeatureEntries map[MetadataFeatureEntriesKey]MetadataFeatureEntriesValue + +// MetadataFeature is a feature which holds a map of key-value pairs. +// The keys must consist of ASCII characters only. +// The values are arbitrary byte slices. +type MetadataFeature struct { + Entries MetadataFeatureEntries `serix:""` +} + +func (m MetadataFeature) Clone() Feature { + copiedMap := make(MetadataFeatureEntries) + for key, value := range m.Entries { + copiedMap[key] = lo.CopySlice(value) + } + + return &MetadataFeature{ + Entries: copiedMap, + } +} + +func (m MetadataFeature) StorageScore(_ *StorageScoreStructure, _ StorageScoreFunc) StorageScore { + return 0 +} + +func (m MetadataFeature) WorkScore(_ *WorkScoreParameters) (WorkScore, error) { + return 0, nil +} + +func (m MetadataFeature) Equal(other Feature) bool { + otherFeat, is := other.(*MetadataFeature) + if !is { + return false + } + + if len(m.Entries) != len(otherFeat.Entries) { + return false + } + + for key, value := range m.Entries { + otherValue, exists := otherFeat.Entries[key] + if !exists { + return false + } + + if !bytes.Equal(value, otherValue) { + return false + } + } + + return true +} + +func (m MetadataFeature) Type() FeatureType { + return FeatureMetadata +} + +func (m MetadataFeature) mapSize() int { + var size int + + // Map Length + size += serializer.SmallTypeDenotationByteSize + for key, value := range m.Entries { + // Key Length + Key + Value Length + Value + size += serializer.SmallTypeDenotationByteSize + len(key) + serializer.UInt16ByteSize + len(value) + } + + return size +} + +func (m MetadataFeature) Size() int { + // FeatureType + Entries + return serializer.SmallTypeDenotationByteSize + m.mapSize() +} diff --git a/feat_metadata.go b/feat_metadata.go deleted file mode 100644 index 39a40d39b..000000000 --- a/feat_metadata.go +++ /dev/null @@ -1,43 +0,0 @@ -package iotago - -import ( - "bytes" - - "github.com/iotaledger/hive.go/serializer/v2" -) - -// MetadataFeature is a feature which simply holds binary data to be freely -// interpreted by higher layer applications. -type MetadataFeature struct { - Data []byte `serix:",lenPrefix=uint16,minLen=1,maxLen=8192"` -} - -func (s *MetadataFeature) Clone() Feature { - return &MetadataFeature{Data: append([]byte(nil), s.Data...)} -} - -func (s *MetadataFeature) StorageScore(_ *StorageScoreStructure, _ StorageScoreFunc) StorageScore { - return 0 -} - -func (s *MetadataFeature) WorkScore(_ *WorkScoreParameters) (WorkScore, error) { - return 0, nil -} - -func (s *MetadataFeature) Equal(other Feature) bool { - otherFeat, is := other.(*MetadataFeature) - if !is { - return false - } - - return bytes.Equal(s.Data, otherFeat.Data) -} - -func (s *MetadataFeature) Type() FeatureType { - return FeatureMetadata -} - -func (s *MetadataFeature) Size() int { - // FeatureType + Data - return serializer.SmallTypeDenotationByteSize + serializer.UInt16ByteSize + len(s.Data) -} diff --git a/feat_metadata_state.gen.go b/feat_metadata_state.gen.go new file mode 100644 index 000000000..60e186dff --- /dev/null +++ b/feat_metadata_state.gen.go @@ -0,0 +1,86 @@ +package iotago + +// Code generated by go generate; DO NOT EDIT. Check gen/ directory instead. + +import ( + "bytes" + + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/serializer/v2" +) + +type StateMetadataFeatureEntriesKey string +type StateMetadataFeatureEntriesValue []byte +type StateMetadataFeatureEntries map[StateMetadataFeatureEntriesKey]StateMetadataFeatureEntriesValue + +// StateMetadataFeature is a feature which holds a map of key-value pairs. +// The keys must consist of ASCII characters only. +// The values are arbitrary byte slices. +type StateMetadataFeature struct { + Entries StateMetadataFeatureEntries `serix:""` +} + +func (m StateMetadataFeature) Clone() Feature { + copiedMap := make(StateMetadataFeatureEntries) + for key, value := range m.Entries { + copiedMap[key] = lo.CopySlice(value) + } + + return &StateMetadataFeature{ + Entries: copiedMap, + } +} + +func (m StateMetadataFeature) StorageScore(_ *StorageScoreStructure, _ StorageScoreFunc) StorageScore { + return 0 +} + +func (m StateMetadataFeature) WorkScore(_ *WorkScoreParameters) (WorkScore, error) { + return 0, nil +} + +func (m StateMetadataFeature) Equal(other Feature) bool { + otherFeat, is := other.(*StateMetadataFeature) + if !is { + return false + } + + if len(m.Entries) != len(otherFeat.Entries) { + return false + } + + for key, value := range m.Entries { + otherValue, exists := otherFeat.Entries[key] + if !exists { + return false + } + + if !bytes.Equal(value, otherValue) { + return false + } + } + + return true +} + +func (m StateMetadataFeature) Type() FeatureType { + return FeatureStateMetadata +} + +func (m StateMetadataFeature) mapSize() int { + var size int + + // Map Length + size += serializer.SmallTypeDenotationByteSize + for key, value := range m.Entries { + // Key Length + Key + Value Length + Value + size += serializer.SmallTypeDenotationByteSize + len(key) + serializer.UInt16ByteSize + len(value) + } + + return size +} + +func (m StateMetadataFeature) Size() int { + // FeatureType + Entries + return serializer.SmallTypeDenotationByteSize + m.mapSize() +} diff --git a/feat_test.go b/feat_test.go index a057029de..917149cf0 100644 --- a/feat_test.go +++ b/feat_test.go @@ -42,10 +42,25 @@ func TestFeaturesDeSerialize(t *testing.T) { { name: "ok - MetadataFeature", source: &iotago.MetadataFeature{ - Data: []byte("hello world"), + Entries: iotago.MetadataFeatureEntries{ + "hello": []byte("world"), + "did:iota": []byte("hello digital autonomy"), + "": []byte(""), + }, }, target: &iotago.MetadataFeature{}, }, + { + name: "ok - StateMetadataFeature", + source: &iotago.StateMetadataFeature{ + Entries: iotago.StateMetadataFeatureEntries{ + "hello": []byte("world"), + "did:iota": []byte("hello digital autonomy"), + "": []byte(""), + }, + }, + target: &iotago.StateMetadataFeature{}, + }, { name: "ok - TagFeature", source: &iotago.TagFeature{ @@ -59,3 +74,54 @@ func TestFeaturesDeSerialize(t *testing.T) { t.Run(tt.name, tt.deSerialize) } } + +func TestFeaturesMetadata(t *testing.T) { + tests := []deSerializeTest{ + { + name: "ok - MetadataFeature", + source: &iotago.MetadataFeature{ + Entries: iotago.MetadataFeatureEntries{ + "hello": []byte("world"), + "did:iota": []byte("hello digital autonomy"), + "empty": []byte(""), + }, + }, + target: &iotago.MetadataFeature{}, + }, + { + name: "fail - MetadataFeature - non ASCII char in key", + source: &iotago.MetadataFeature{ + Entries: iotago.MetadataFeatureEntries{ + "hellö": []byte("world"), + }, + }, + seriErr: iotago.ErrInvalidMetadataKey, + target: &iotago.MetadataFeature{}, + }, + { + name: "ok - StateMetadataFeature", + source: &iotago.StateMetadataFeature{ + Entries: iotago.StateMetadataFeatureEntries{ + "hello": []byte("world"), + "did:iota": []byte("hello digital autonomy"), + "empty": []byte(""), + }, + }, + target: &iotago.StateMetadataFeature{}, + }, + { + name: "fail - StateMetadataFeature - non ASCII char in key", + source: &iotago.StateMetadataFeature{ + Entries: iotago.StateMetadataFeatureEntries{ + "hellö": []byte("world"), + }, + }, + seriErr: iotago.ErrInvalidStateMetadataKey, + target: &iotago.StateMetadataFeature{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, tt.deSerialize) + } +} diff --git a/gen/feat_metadata.tmpl b/gen/feat_metadata.tmpl new file mode 100644 index 000000000..e58b74744 --- /dev/null +++ b/gen/feat_metadata.tmpl @@ -0,0 +1,86 @@ +package iotago + +// Code generated by go generate; DO NOT EDIT. Check gen/ directory instead. + +import ( + "bytes" + + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/serializer/v2" +) + +type {{.Name}}EntriesKey string +type {{.Name}}EntriesValue []byte +type {{.Name}}Entries map[{{.Name}}EntriesKey]{{.Name}}EntriesValue + +// {{.Name}} is a feature which holds a map of key-value pairs. +// The keys must consist of ASCII characters only. +// The values are arbitrary byte slices. +type {{.Name}} struct { + Entries {{.Name}}Entries `serix:""` +} + +func ({{.Receiver}} {{.Name}}) Clone() Feature { + copiedMap := make({{.Name}}Entries) + for key, value := range {{.Receiver}}.Entries { + copiedMap[key] = lo.CopySlice(value) + } + + return &{{.Name}}{ + Entries: copiedMap, + } +} + +func ({{.Receiver}} {{.Name}}) StorageScore(_ *StorageScoreStructure, _ StorageScoreFunc) StorageScore { + return 0 +} + +func ({{.Receiver}} {{.Name}}) WorkScore(_ *WorkScoreParameters) (WorkScore, error) { + return 0, nil +} + +func ({{.Receiver}} {{.Name}}) Equal(other Feature) bool { + otherFeat, is := other.(*{{.Name}}) + if !is { + return false + } + + if len({{.Receiver}}.Entries) != len(otherFeat.Entries) { + return false + } + + for key, value := range {{.Receiver}}.Entries { + otherValue, exists := otherFeat.Entries[key] + if !exists { + return false + } + + if !bytes.Equal(value, otherValue) { + return false + } + } + + return true +} + +func ({{.Receiver}} {{.Name}}) Type() FeatureType { + return {{.FeatureType}} +} + +func ({{.Receiver}} {{.Name}}) mapSize() int { + var size int + + // Map Length + size += serializer.SmallTypeDenotationByteSize + for key, value := range {{.Receiver}}.Entries { + // Key Length + Key + Value Length + Value + size += serializer.SmallTypeDenotationByteSize + len(key) + serializer.UInt16ByteSize + len(value) + } + + return size +} + +func ({{.Receiver}} {{.Name}}) Size() int { + // FeatureType + Entries + return serializer.SmallTypeDenotationByteSize + {{.Receiver}}.mapSize() +} diff --git a/gen/feat_metadata_gen.go b/gen/feat_metadata_gen.go new file mode 100644 index 000000000..fbc87886d --- /dev/null +++ b/gen/feat_metadata_gen.go @@ -0,0 +1,6 @@ +//go:build ignore + +package gen + +//go:generate go run github.com/iotaledger/hive.go/codegen/features/cmd@13da292 feat_metadata.tmpl ../feat_metadata.gen.go MetadataFeature m "" "FeatureType=FeatureMetadata," +//go:generate go run github.com/iotaledger/hive.go/codegen/features/cmd@13da292 feat_metadata.tmpl ../feat_metadata_state.gen.go StateMetadataFeature m "" "FeatureType=FeatureStateMetadata," diff --git a/gen/run_gen.sh b/gen/run_gen.sh index 8b71d683f..c08ad3239 100755 --- a/gen/run_gen.sh +++ b/gen/run_gen.sh @@ -1,6 +1,6 @@ #!/bin/bash -files=(address_gen.go identifier_gen.go index_gen.go slot_identifier_gen.go unlock_ref_gen.go) +files=(address_gen.go feat_metadata_gen.go identifier_gen.go index_gen.go slot_identifier_gen.go unlock_ref_gen.go) for file in "${files[@]}" do diff --git a/go.mod b/go.mod index 929ed75b1..cfc35fa40 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,14 @@ go 1.21 require ( github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/ethereum/go-ethereum v1.13.4 - github.com/iotaledger/hive.go/constraints v0.0.0-20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/crypto v0.0.0-20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/ierrors v0.0.0-20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/lo v0.0.0-20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/runtime v0.0.0-20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231108162616-bab25251edc4 - github.com/iotaledger/hive.go/stringify v0.0.0-20231108162616-bab25251edc4 + github.com/iotaledger/hive.go/constraints v0.0.0-20231110115447-7605386261d2 + github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110115447-7605386261d2 + github.com/iotaledger/hive.go/crypto v0.0.0-20231110115447-7605386261d2 + github.com/iotaledger/hive.go/ierrors v0.0.0-20231110115447-7605386261d2 + github.com/iotaledger/hive.go/lo v0.0.0-20231110115447-7605386261d2 + github.com/iotaledger/hive.go/runtime v0.0.0-20231110115447-7605386261d2 + github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110115447-7605386261d2 + github.com/iotaledger/hive.go/stringify v0.0.0-20231110115447-7605386261d2 github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c github.com/samber/lo v1.38.1 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 52e972adf..3c3a623a7 100644 --- a/go.sum +++ b/go.sum @@ -23,22 +23,22 @@ github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iotaledger/hive.go/constraints v0.0.0-20231108162616-bab25251edc4 h1:v9fYr6bAPzunA1FQwYa0SthpUCc4+b5oFivpLg3quQk= -github.com/iotaledger/hive.go/constraints v0.0.0-20231108162616-bab25251edc4/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s= -github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231108162616-bab25251edc4 h1:AaMdcUpeAragQXoiw/JUeqOpoZHF6AwO5SchkDz5/1E= -github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231108162616-bab25251edc4/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko= -github.com/iotaledger/hive.go/crypto v0.0.0-20231108162616-bab25251edc4 h1:FfzAlmgc+ugZlcE/JWO2TtL/uEFT/V7+h3lz2WAzSWI= -github.com/iotaledger/hive.go/crypto v0.0.0-20231108162616-bab25251edc4/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI= -github.com/iotaledger/hive.go/ierrors v0.0.0-20231108162616-bab25251edc4 h1:gcDpRANXwAUhMG7mV9+HZ1v+xOAa1JX9iTC1Li6VFI0= -github.com/iotaledger/hive.go/ierrors v0.0.0-20231108162616-bab25251edc4/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8= -github.com/iotaledger/hive.go/lo v0.0.0-20231108162616-bab25251edc4 h1:coSKLUOZkhYmdB5ifiYGnYhVnt0oJfLgTWz79kM2hys= -github.com/iotaledger/hive.go/lo v0.0.0-20231108162616-bab25251edc4/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg= -github.com/iotaledger/hive.go/runtime v0.0.0-20231108162616-bab25251edc4 h1:m5BtU3Qjls+bW3D+3wOwJPqM2AXehbbp3Pc+nkCBvoc= -github.com/iotaledger/hive.go/runtime v0.0.0-20231108162616-bab25251edc4/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q= -github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231108162616-bab25251edc4 h1:KsLHCoGHUA3Gjs8Qh+4cbAJbsO2Bfgm/Hqwx3gJXFfc= -github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231108162616-bab25251edc4/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8= -github.com/iotaledger/hive.go/stringify v0.0.0-20231108162616-bab25251edc4 h1:xl2Og80WiBt90QjKaZxOflXHPcuikV0HVFy/wSB0l6E= -github.com/iotaledger/hive.go/stringify v0.0.0-20231108162616-bab25251edc4/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= +github.com/iotaledger/hive.go/constraints v0.0.0-20231110115447-7605386261d2 h1:3z31epbYZqwpCR/hL3Cyii7qgvIMewDmzvow6wRTFmY= +github.com/iotaledger/hive.go/constraints v0.0.0-20231110115447-7605386261d2/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s= +github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110115447-7605386261d2 h1:Z1a15SKzbEu1hhLk1xefTYw3zri0vxiLYaSVuS6CdoQ= +github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110115447-7605386261d2/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko= +github.com/iotaledger/hive.go/crypto v0.0.0-20231110115447-7605386261d2 h1:pbxrXM070GXymKySNHdFXnlEnjXzvJMczHRCh4niX9g= +github.com/iotaledger/hive.go/crypto v0.0.0-20231110115447-7605386261d2/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI= +github.com/iotaledger/hive.go/ierrors v0.0.0-20231110115447-7605386261d2 h1:68ujGKPVftxFYA3yIO1CkmVISFSDx8aPrJb20oWx1nM= +github.com/iotaledger/hive.go/ierrors v0.0.0-20231110115447-7605386261d2/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8= +github.com/iotaledger/hive.go/lo v0.0.0-20231110115447-7605386261d2 h1:aYwno4uVrsvJvUMWXQNHavYBclCSNlEtKMvWiWitOiw= +github.com/iotaledger/hive.go/lo v0.0.0-20231110115447-7605386261d2/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg= +github.com/iotaledger/hive.go/runtime v0.0.0-20231110115447-7605386261d2 h1:q4Mywi7KNBEOS5DX9Jkj/8ECWhua7WLHUmgXAP9mLvs= +github.com/iotaledger/hive.go/runtime v0.0.0-20231110115447-7605386261d2/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q= +github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110115447-7605386261d2 h1:5/HJa5W0l/6zTWaI3YvywVDxH4r4Eu0ww20xrk74OUs= +github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110115447-7605386261d2/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8= +github.com/iotaledger/hive.go/stringify v0.0.0-20231110115447-7605386261d2 h1:vni0ehemFi/al5MJgbpgTt5OX8zcXFI5Ms4lGqd5gGQ= +github.com/iotaledger/hive.go/stringify v0.0.0-20231110115447-7605386261d2/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/output_anchor.go b/output_anchor.go index 6dc1839f3..a81164cc3 100644 --- a/output_anchor.go +++ b/output_anchor.go @@ -1,10 +1,7 @@ package iotago import ( - "bytes" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/serializer/v2" ) @@ -99,8 +96,6 @@ type AnchorOutput struct { AnchorID AnchorID `serix:""` // The index of the state. StateIndex uint32 `serix:""` - // The state of the anchor which can only be mutated by the state controller. - StateMetadata []byte `serix:",omitempty,lenPrefix=uint16,maxLen=8192"` // The unlock conditions on this output. UnlockConditions AnchorOutputUnlockConditions `serix:",omitempty"` // The features on the output. @@ -123,7 +118,6 @@ func (a *AnchorOutput) Clone() Output { Mana: a.Mana, AnchorID: a.AnchorID, StateIndex: a.StateIndex, - StateMetadata: lo.CopySlice(a.StateMetadata), UnlockConditions: a.UnlockConditions.Clone(), Features: a.Features.Clone(), ImmutableFeatures: a.ImmutableFeatures.Clone(), @@ -152,10 +146,6 @@ func (a *AnchorOutput) Equal(other Output) bool { return false } - if !bytes.Equal(a.StateMetadata, otherOutput.StateMetadata) { - return false - } - if !a.UnlockConditions.Equal(otherOutput.UnlockConditions) { return false } @@ -268,8 +258,6 @@ func (a *AnchorOutput) Size() int { AnchorIDLength + // StateIndex serializer.UInt32ByteSize + - serializer.UInt16ByteSize + - len(a.StateMetadata) + a.UnlockConditions.Size() + a.Features.Size() + a.ImmutableFeatures.Size() diff --git a/output_test.go b/output_test.go index 0f0f82e03..def1ccb72 100644 --- a/output_test.go +++ b/output_test.go @@ -49,7 +49,7 @@ func TestOutputsDeSerialize(t *testing.T) { }, Features: iotago.BasicOutputFeatures{ &iotago.SenderFeature{Address: tpkg.RandEd25519Address()}, - &iotago.MetadataFeature{Data: tpkg.RandBytes(100)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(100)}}, &iotago.TagFeature{Tag: tpkg.RandBytes(32)}, tpkg.RandNativeTokenFeature(), }, @@ -68,7 +68,7 @@ func TestOutputsDeSerialize(t *testing.T) { }, Features: iotago.AccountOutputFeatures{ &iotago.SenderFeature{Address: tpkg.RandEd25519Address()}, - &iotago.MetadataFeature{Data: tpkg.RandBytes(100)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(100)}}, }, ImmutableFeatures: iotago.AccountOutputImmFeatures{ &iotago.IssuerFeature{Address: tpkg.RandEd25519Address()}, @@ -79,18 +79,17 @@ func TestOutputsDeSerialize(t *testing.T) { { name: "ok - AnchorOutput", source: &iotago.AnchorOutput{ - Amount: 1337, - Mana: 500, - AnchorID: tpkg.RandAnchorAddress().AnchorID(), - StateIndex: 10, - StateMetadata: []byte("hello world"), + Amount: 1337, + Mana: 500, + AnchorID: tpkg.RandAnchorAddress().AnchorID(), + StateIndex: 10, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: tpkg.RandEd25519Address()}, &iotago.GovernorAddressUnlockCondition{Address: tpkg.RandEd25519Address()}, }, Features: iotago.AnchorOutputFeatures{ &iotago.SenderFeature{Address: tpkg.RandEd25519Address()}, - &iotago.MetadataFeature{Data: tpkg.RandBytes(100)}, + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": tpkg.RandBytes(100)}}, }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ &iotago.IssuerFeature{Address: tpkg.RandEd25519Address()}, @@ -112,7 +111,7 @@ func TestOutputsDeSerialize(t *testing.T) { &iotago.ImmutableAccountUnlockCondition{Address: tpkg.RandAccountAddress()}, }, Features: iotago.FoundryOutputFeatures{ - &iotago.MetadataFeature{Data: tpkg.RandBytes(100)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(100)}}, }, ImmutableFeatures: iotago.FoundryOutputImmFeatures{}, }, @@ -138,12 +137,12 @@ func TestOutputsDeSerialize(t *testing.T) { }, Features: iotago.NFTOutputFeatures{ &iotago.SenderFeature{Address: tpkg.RandEd25519Address()}, - &iotago.MetadataFeature{Data: tpkg.RandBytes(100)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(100)}}, &iotago.TagFeature{Tag: tpkg.RandBytes(32)}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ &iotago.IssuerFeature{Address: tpkg.RandEd25519Address()}, - &iotago.MetadataFeature{Data: tpkg.RandBytes(10)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(10)}}, }, }, target: &iotago.NFTOutput{}, diff --git a/vm/nova/stvf_test.go b/vm/nova/stvf_test.go index 2f73bf5ae..324c692f5 100644 --- a/vm/nova/stvf_test.go +++ b/vm/nova/stvf_test.go @@ -17,7 +17,7 @@ import ( type fieldMutations map[string]interface{} //nolint:thelper -func copyObject(t *testing.T, source any, mutations fieldMutations) any { +func copyObjectAndMutate(t *testing.T, source any, mutations fieldMutations) any { srcBytes, err := tpkg.TestAPI.Encode(source) require.NoError(t, err) @@ -1354,7 +1354,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { FoundryCounter: 7, Features: iotago.AccountOutputFeatures{ &iotago.SenderFeature{Address: exampleAddress}, - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, &iotago.BlockIssuerFeature{ BlockIssuerKeys: tpkg.RandomBlockIssuerKeysEd25519(1), ExpirySlot: 1015, @@ -1652,7 +1652,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { &iotago.AddressUnlockCondition{Address: exampleAddress}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, &iotago.SenderFeature{Address: exampleAddress}, &iotago.BlockIssuerFeature{ BlockIssuerKeys: tpkg.RandomBlockIssuerKeysEd25519(1), @@ -1764,7 +1764,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { &iotago.AddressUnlockCondition{Address: exampleAddress}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, &iotago.SenderFeature{Address: exampleAddress}, &iotago.BlockIssuerFeature{ BlockIssuerKeys: tpkg.RandomBlockIssuerKeysEd25519(1), @@ -1927,7 +1927,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { if tt.nextMut != nil { for mutName, muts := range tt.nextMut { t.Run(fmt.Sprintf("%s_%s", tt.name, mutName), func(t *testing.T) { - cpy := copyObject(t, tt.input.Output, muts).(*iotago.AccountOutput) + cpy := copyObjectAndMutate(t, tt.input.Output, muts).(*iotago.AccountOutput) createWorkingSet(t, tt.input, tt.svCtx.WorkingSet) @@ -2044,6 +2044,9 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, StateIndex: 10, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("1337")}}, + }, }, }, next: &iotago.AnchorOutput{ @@ -2057,7 +2060,9 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { }, Features: iotago.AnchorOutputFeatures{ &iotago.SenderFeature{Address: exampleGovCtrl}, - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("1337")}}, + // adding metadata feature + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1338")}}, }, }, transType: iotago.ChainTransitionTypeStateChange, @@ -2093,6 +2098,9 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, StateIndex: 10, + Features: iotago.AnchorOutputFeatures{ + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1338")}}, + }, }, }, next: &iotago.AnchorOutput{ @@ -2102,10 +2110,12 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.StateControllerAddressUnlockCondition{Address: exampleStateCtrl}, &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, - StateIndex: 11, - StateMetadata: []byte("1337"), + StateIndex: 11, Features: iotago.AnchorOutputFeatures{ &iotago.SenderFeature{Address: exampleStateCtrl}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1338")}}, + // adding state metadata feature + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("1337")}}, }, }, transType: iotago.ChainTransitionTypeStateChange, @@ -2138,7 +2148,7 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, }, StateIndex: 10, }, @@ -2152,7 +2162,7 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { }, StateIndex: 10, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("1338")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1338")}}, }, }, transType: iotago.ChainTransitionTypeStateChange, @@ -2183,7 +2193,7 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("1337")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, }, StateIndex: 10, }, @@ -2197,7 +2207,7 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { }, StateIndex: 11, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("1338")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1338")}}, }, }, transType: iotago.ChainTransitionTypeStateChange, @@ -2228,14 +2238,27 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.StateControllerAddressUnlockCondition{Address: exampleStateCtrl}, &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{ + Entries: iotago.StateMetadataFeatureEntries{ + "data": []byte("foo"), + }, + }, + }, }, }, nextMut: map[string]fieldMutations{ "amount": { "Amount": iotago.BaseToken(1337), }, - "state_metadata": { - "StateMetadata": []byte("7331"), + "state_metadata_feature_changed": { + "Features": iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{ + Entries: iotago.StateMetadataFeatureEntries{ + "data": []byte("bar"), + }, + }, + }, }, }, transType: iotago.ChainTransitionTypeStateChange, @@ -2264,6 +2287,9 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { &iotago.StateControllerAddressUnlockCondition{Address: exampleStateCtrl}, &iotago.GovernorAddressUnlockCondition{Address: exampleGovCtrl}, }, + Features: iotago.AnchorOutputFeatures{ + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("foo")}}, + }, ImmutableFeatures: iotago.AnchorOutputImmFeatures{ &iotago.IssuerFeature{Address: exampleIssuer}, }, @@ -2290,10 +2316,10 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { "state_index_bigger_more_than_1": { "StateIndex": uint32(7), }, - "metadata_feature_added": { + "metadata_feature_changed": { "StateIndex": uint32(11), "Features": iotago.AnchorOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("foo")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("bar")}}, }, }, }, @@ -2321,7 +2347,7 @@ func TestAnchorOutput_ValidateStateTransition(t *testing.T) { if tt.nextMut != nil { for mutName, muts := range tt.nextMut { t.Run(fmt.Sprintf("%s_%s", tt.name, mutName), func(t *testing.T) { - cpy := copyObject(t, tt.input.Output, muts).(*iotago.AnchorOutput) + cpy := copyObjectAndMutate(t, tt.input.Output, muts).(*iotago.AnchorOutput) createWorkingSet(t, tt.input, tt.svCtx.WorkingSet) @@ -2540,7 +2566,7 @@ func TestFoundryOutput_ValidateStateTransition(t *testing.T) { nextMut: map[string]fieldMutations{ "change_metadata": { "Features": iotago.FoundryOutputFeatures{ - &iotago.MetadataFeature{Data: tpkg.RandBytes(20)}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": tpkg.RandBytes(20)}}, }, }, }, @@ -2850,7 +2876,7 @@ func TestFoundryOutput_ValidateStateTransition(t *testing.T) { if tt.nextMut != nil { for mutName, muts := range tt.nextMut { t.Run(fmt.Sprintf("%s_%s", tt.name, mutName), func(t *testing.T) { - cpy := copyObject(t, tt.input.Output, muts).(*iotago.FoundryOutput) + cpy := copyObjectAndMutate(t, tt.input.Output, muts).(*iotago.FoundryOutput) createWorkingSet(t, tt.input, tt.svCtx.WorkingSet) @@ -2891,7 +2917,7 @@ func TestNFTOutput_ValidateStateTransition(t *testing.T) { }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ &iotago.IssuerFeature{Address: exampleIssuer}, - &iotago.MetadataFeature{Data: []byte("some-ipfs-link")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("some-ipfs-link")}}, }, } @@ -2986,7 +3012,7 @@ func TestNFTOutput_ValidateStateTransition(t *testing.T) { nextMut: map[string]fieldMutations{ "immutable_metadata": { "ImmutableFeatures": iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("link-to-cat.gif")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("link-to-cat.gif")}}, }, }, "issuer": { @@ -3016,7 +3042,7 @@ func TestNFTOutput_ValidateStateTransition(t *testing.T) { if tt.nextMut != nil { for mutName, muts := range tt.nextMut { t.Run(fmt.Sprintf("%s_%s", tt.name, mutName), func(t *testing.T) { - cpy := copyObject(t, tt.input.Output, muts).(*iotago.NFTOutput) + cpy := copyObjectAndMutate(t, tt.input.Output, muts).(*iotago.NFTOutput) createWorkingSet(t, tt.input, tt.svCtx.WorkingSet) @@ -3546,7 +3572,7 @@ func TestDelegationOutput_ValidateStateTransition(t *testing.T) { if tt.nextMut != nil { for mutName, muts := range tt.nextMut { t.Run(fmt.Sprintf("%s_%s", tt.name, mutName), func(t *testing.T) { - cpy := copyObject(t, tt.input.Output, muts).(*iotago.DelegationOutput) + cpy := copyObjectAndMutate(t, tt.input.Output, muts).(*iotago.DelegationOutput) createWorkingSet(t, tt.input, tt.svCtx.WorkingSet) diff --git a/vm/nova/vm.go b/vm/nova/vm.go index d6939da0b..d2bd2e377 100644 --- a/vm/nova/vm.go +++ b/vm/nova/vm.go @@ -1,7 +1,6 @@ package nova import ( - "bytes" "fmt" "github.com/iotaledger/hive.go/core/safemath" @@ -734,8 +733,10 @@ func anchorGovernanceSTVF(input *vm.ChainOutputWithIDs, next *iotago.AnchorOutpu return ierrors.Wrapf(iotago.ErrInvalidAnchorGovernanceTransition, "amount changed, in %d / out %d ", current.Amount, next.Amount) case current.StateIndex != next.StateIndex: return ierrors.Wrapf(iotago.ErrInvalidAnchorGovernanceTransition, "state index changed, in %d / out %d", current.StateIndex, next.StateIndex) - case !bytes.Equal(current.StateMetadata, next.StateMetadata): - return ierrors.Wrapf(iotago.ErrInvalidAnchorGovernanceTransition, "state metadata changed, in %v / out %v", current.StateMetadata, next.StateMetadata) + } + + if err := iotago.FeatureUnchanged(iotago.FeatureStateMetadata, current.Features.MustSet(), next.Features.MustSet()); err != nil { + return ierrors.Wrapf(iotago.ErrInvalidAnchorGovernanceTransition, "%w", err) } return nil diff --git a/vm/nova/vm_test.go b/vm/nova/vm_test.go index d265ea004..843f37040 100644 --- a/vm/nova/vm_test.go +++ b/vm/nova/vm_test.go @@ -360,43 +360,46 @@ func TestNovaTransactionExecution(t *testing.T) { // anchor output with no features - state index 0 [defaultAmount] (owned by - state: ident3, gov: ident4) => going to be governance transitioned // => output 8: governance transition (added metadata) anchor1InputID: &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: anchor1AnchorID, - StateIndex: 0, - StateMetadata: []byte("gov transitioning"), + Amount: defaultAmount, + AnchorID: anchor1AnchorID, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident4}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("gov transitioning")}}, + }, }, // anchor output with no features - state index 5 [defaultAmount] (owned by - state: ident3, gov: ident4) => going to be state transitioned // => output 9: state transition (state index 5 => 6, changed state metadata) anchor2InputID: &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: anchor2AnchorID, - StateIndex: 5, - StateMetadata: []byte("state transitioning"), + Amount: defaultAmount, + AnchorID: anchor2AnchorID, + StateIndex: 5, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident4}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("state transitioning")}}, + }, }, // anchor output with no features - state index 0 [defaultAmount] (owned by - state: ident3, gov: ident3) => going to be destroyed // => output 10: destroyed and new anchor output created inputIDs[10]: &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: iotago.AnchorID{}, - StateIndex: 0, - StateMetadata: []byte("going to be destroyed"), + Amount: defaultAmount, + AnchorID: iotago.AnchorID{}, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident3}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("going to be destroyed")}}, + }, }, // foundry output - serialNumber: 1, minted: 100, melted: 0, max: 1000 [defaultAmount] (owned by account1AccountAddress) @@ -479,7 +482,7 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.IssuerFeature{Address: ident3}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("transfer to 4")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("transfer to 4")}}, }, }, @@ -495,7 +498,7 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.IssuerFeature{Address: ident3}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("going to be destroyed")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("going to be destroyed")}}, }, }, @@ -623,7 +626,7 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.AddressUnlockCondition{Address: ident3}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("transitioned")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("transitioned")}}, }, }, @@ -637,52 +640,57 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.AddressUnlockCondition{Address: ident3}, }, Features: iotago.AccountOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("new")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("new")}}, }, }, // governance transitioned anchor output [defaultAmount] (owned by - state: ident3, gov: ident4) // => input 8 &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: anchor1AnchorID, - StateIndex: 0, - StateMetadata: []byte("gov transitioning"), + Amount: defaultAmount, + AnchorID: anchor1AnchorID, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident4}, }, Features: iotago.AnchorOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("the gov mutation on this output")}, + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("gov transitioning")}}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("the gov mutation on this output")}}, }, }, // state transitioned anchor output [defaultAmount] (owned by - state: ident3, gov: ident4) // => input 9 &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: anchor2AnchorID, - StateIndex: 6, - StateMetadata: []byte("next state"), + Amount: defaultAmount, + AnchorID: anchor2AnchorID, + StateIndex: 6, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident4}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{ + "data": []byte("state transitioning"), + "added": []byte("next state"), + }}, + }, }, // new anchor output [defaultAmount] (owned by - state: ident3, gov: ident4) // => input 10 &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: iotago.AnchorID{}, - StateIndex: 0, - StateMetadata: []byte("a new anchor output"), + Amount: defaultAmount, + AnchorID: iotago.AnchorID{}, + StateIndex: 0, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ident3}, &iotago.GovernorAddressUnlockCondition{Address: ident4}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("a new anchor output")}}, + }, }, // foundry output - serialNumber: 1, minted: 200, melted: 0, max: 1000 [defaultAmount] (owned by account1AccountAddress) @@ -743,7 +751,7 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.ImmutableAccountUnlockCondition{Address: account1AccountAddress}, }, Features: iotago.FoundryOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("interesting metadata")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("interesting metadata")}}, }, }, @@ -773,7 +781,7 @@ func TestNovaTransactionExecution(t *testing.T) { &iotago.IssuerFeature{Address: ident3}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("transfer to 4")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("transfer to 4")}}, }, }, @@ -787,7 +795,7 @@ func TestNovaTransactionExecution(t *testing.T) { }, Features: nil, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable metadata")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable metadata")}}, }, }, @@ -1630,15 +1638,16 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { return []iotago.Output{ // we add an output with a Ed25519 address to be able to check the AnchorUnlock in the RestrictedAddress &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 1, - StateMetadata: []byte("current state"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 1, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("current state")}}, + }, }, // owned by restricted anchor address &iotago.BasicOutput{ @@ -1653,15 +1662,16 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { return iotago.TxEssenceOutputs{ // the anchor unlock needs to be a state transition (governor doesn't work for anchor reference unlocks) &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 2, - StateMetadata: []byte("next state"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 2, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("next state")}}, + }, }, &iotago.BasicOutput{ Amount: totalInputAmount - defaultAmount, @@ -1711,7 +1721,7 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { &iotago.IssuerFeature{Address: ed25519Addresses[1]}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, // owned by restricted NFT address @@ -1733,10 +1743,10 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { }, Features: iotago.NFTOutputFeatures{ &iotago.IssuerFeature{Address: ed25519Addresses[1]}, - &iotago.MetadataFeature{Data: []byte("some new metadata")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("some new metadata")}}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, &iotago.BasicOutput{ @@ -1799,7 +1809,7 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { &iotago.IssuerFeature{Address: ed25519Addresses[0]}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, // owned by restricted multi address @@ -1821,10 +1831,10 @@ func TestNovaTransactionExecution_RestrictedAddress(t *testing.T) { }, Features: iotago.NFTOutputFeatures{ &iotago.IssuerFeature{Address: ed25519Addresses[0]}, - &iotago.MetadataFeature{Data: []byte("some new metadata")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("some new metadata")}}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, &iotago.BasicOutput{ @@ -2398,15 +2408,16 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { return []iotago.Output{ // we add an output with a Ed25519 address to be able to check the AnchorUnlock in the MultiAddress &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 1, - StateMetadata: []byte("current state"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 1, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("current state")}}, + }, }, &iotago.BasicOutput{ Amount: defaultAmount, @@ -2427,15 +2438,16 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { return iotago.TxEssenceOutputs{ // the anchor unlock needs to be a state transition (governor doesn't work for anchor reference unlocks) &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 2, - StateMetadata: []byte("next state"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 2, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("next state")}}, + }, }, &iotago.BasicOutput{ Amount: totalInputAmount - defaultAmount, @@ -2533,15 +2545,16 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { return []iotago.Output{ // we add an output with a Ed25519 address to be able to check the AnchorUnlock in the MultiAddress &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 1, - StateMetadata: []byte("governance transition"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 1, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("governance transition")}}, + }, }, &iotago.BasicOutput{ Amount: defaultAmount, @@ -2562,15 +2575,16 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { return iotago.TxEssenceOutputs{ // the anchor unlock needs to be a state transition (governor doesn't work for anchor reference unlocks) &iotago.AnchorOutput{ - Amount: defaultAmount, - AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), - StateIndex: 1, - StateMetadata: []byte("governance transition"), + Amount: defaultAmount, + AnchorID: testAddresses[0].(*iotago.AnchorAddress).AnchorID(), + StateIndex: 1, UnlockConditions: iotago.AnchorOutputUnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: ed25519Addresses[0]}, &iotago.GovernorAddressUnlockCondition{Address: ed25519Addresses[1]}, }, - Features: nil, + Features: iotago.AnchorOutputFeatures{ + &iotago.StateMetadataFeature{Entries: iotago.StateMetadataFeatureEntries{"data": []byte("governance transition")}}, + }, }, &iotago.BasicOutput{ Amount: totalInputAmount - defaultAmount, @@ -2677,7 +2691,7 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { &iotago.IssuerFeature{Address: ed25519Addresses[1]}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, &iotago.BasicOutput{ @@ -2705,10 +2719,10 @@ func TestNovaTransactionExecution_MultiAddress(t *testing.T) { }, Features: iotago.NFTOutputFeatures{ &iotago.IssuerFeature{Address: ed25519Addresses[1]}, - &iotago.MetadataFeature{Data: []byte("some new metadata")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("some new metadata")}}, }, ImmutableFeatures: iotago.NFTOutputImmFeatures{ - &iotago.MetadataFeature{Data: []byte("immutable")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("immutable")}}, }, }, &iotago.BasicOutput{ @@ -4962,7 +4976,7 @@ func TestTxSemanticDeposit(t *testing.T) { &iotago.AddressUnlockCondition{Address: ident2}, }, Features: iotago.BasicOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("foo")}, + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("foo")}}, }, }, },