diff --git a/api_v3.go b/api_v3.go index 1b6a6461b..0a7dbd06e 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, GovernorMetadataFeature ValidationMode: serializer.ArrayValidationModeNoDuplicates | serializer.ArrayValidationModeLexicalOrdering | serializer.ArrayValidationModeAtMostOneOfEachTypeByte, @@ -340,9 +340,33 @@ func V3API(protoParams ProtocolParameters) API { must(api.RegisterTypeSettings(IssuerFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureIssuer))), ) + + must(api.RegisterTypeSettings(MetadataFeatureEntriesKey(""), + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxLen(64)), + ) + must(api.RegisterTypeSettings(MetadataFeatureEntriesValue{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsUint16).WithMinLen(0).WithMaxLen(1000)), + ) + must(api.RegisterTypeSettings(MetadataFeatureEntries{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxLen(64).WithMaxByteSize(8192)), + ) must(api.RegisterTypeSettings(MetadataFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureMetadata))), ) + + must(api.RegisterTypeSettings(GovernorMetadataFeatureEntriesKey(""), + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxLen(64)), + ) + must(api.RegisterTypeSettings(GovernorMetadataFeatureEntriesValue{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsUint16).WithMinLen(0).WithMaxLen(1000)), + ) + must(api.RegisterTypeSettings(GovernorMetadataFeatureEntries{}, + serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsByte).WithMinLen(1).WithMaxLen(64).WithMaxByteSize(8192)), + ) + must(api.RegisterTypeSettings(GovernorMetadataFeature{}, + serix.TypeSettings{}.WithObjectType(uint8(FeatureMetadataGovernor))), + ) + must(api.RegisterTypeSettings(TagFeature{}, serix.TypeSettings{}.WithObjectType(uint8(FeatureTag))), ) @@ -358,6 +382,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), (*GovernorMetadataFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*TagFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*NativeTokenFeature)(nil))) must(api.RegisterInterfaceObjects((*Feature)(nil), (*BlockIssuerFeature)(nil))) @@ -496,6 +521,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), (*GovernorMetadataFeature)(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..34323d7aa 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,8 +94,16 @@ 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.stateCtrlReq = true + + return builder +} + +// GovernorMetadata sets/modifies an iotago.GovernorMetadataFeature on the output. +func (builder *AnchorOutputBuilder) GovernorMetadata(entries iotago.GovernorMetadataFeatureEntries) *AnchorOutputBuilder { + builder.output.Features.Upsert(&iotago.GovernorMetadataFeature{Entries: entries}) builder.govCtrlReq = true return builder @@ -112,8 +111,8 @@ func (builder *AnchorOutputBuilder) Metadata(data []byte) *AnchorOutputBuilder { // 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() +// Metadata sets/modifies an iotago.MetadataFeature as a mutable feature on the output. +func (trans *AnchorStateTransition) Metadata(entries iotago.MetadataFeatureEntries) *AnchorStateTransition { + return trans.builder.Metadata(entries).StateTransition() } // Sender sets/modifies an iotago.SenderFeature as a mutable feature on the output. @@ -212,9 +211,9 @@ func (trans *AnchorGovernanceTransition) Sender(senderAddr iotago.Address) *Anch return trans.builder.Sender(senderAddr).GovernanceTransition() } -// 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() +// GovernorMetadata sets/modifies an iotago.GovernorMetadataFeature on the output. +func (trans *AnchorGovernanceTransition) GovernorMetadata(entries iotago.GovernorMetadataFeatureEntries) *AnchorGovernanceTransition { + return trans.builder.GovernorMetadata(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..6f756ffab 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 + metadataEntries = iotago.MetadataFeatureEntries{"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). + Metadata(metadataEntries). + 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.MetadataFeature{Entries: metadataEntries}, }, 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 + newMetadataEntries := iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: newMetadataEntries}) + updatedOutput, err := builder.NewAnchorOutputBuilderFromPrevious(anchorOutput).StateTransition(). Amount(newAmount). - StateMetadata([]byte("newState")). + Metadata(newMetadataEntries). 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.MetadataFeature{Entries: metadataEntries}, }, 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..2a538359d 100644 --- a/feat.go +++ b/feat.go @@ -39,6 +39,8 @@ const ( FeatureIssuer // FeatureMetadata denotes a MetadataFeature. FeatureMetadata + // FeatureMetadataGovernor denotes a GovernorMetadataFeature. + FeatureMetadataGovernor // FeatureTag denotes a TagFeature. FeatureTag // NativeTokenFeature denotes a NativeTokenFeature. @@ -58,7 +60,14 @@ func (featType FeatureType) String() string { } var featNames = [FeatureStaking + 1]string{ - "SenderFeature", "IssuerFeature", "MetadataFeature", "TagFeature", "NativeTokenFeature", "BlockIssuerFeature", "StakingFeature", + "SenderFeature", + "IssuerFeature", + "MetadataFeature", + "GovernorMetadataFeature", + "TagFeature", + "NativeTokenFeature", + "BlockIssuerFeature", + "StakingFeature", } // Features is a slice of Feature(s). @@ -192,37 +201,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] +// GovernorMetadata returns the GovernorMetadataFeature in the set or nil. +func (f FeatureSet) GovernorMetadata() *GovernorMetadataFeature { + b, has := f[FeatureMetadataGovernor] 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 GovernorMetadataFeature + return b.(*GovernorMetadataFeature) } // Tag returns the TagFeature in the set or nil. @@ -253,6 +251,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) { diff --git a/feat_metadata.gen.go b/feat_metadata.gen.go new file mode 100644 index 000000000..b553a055e --- /dev/null +++ b/feat_metadata.gen.go @@ -0,0 +1,85 @@ +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 simply holds binary data to be freely +// interpreted by higher layer applications. +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_governor.gen.go b/feat_metadata_governor.gen.go new file mode 100644 index 000000000..43df76776 --- /dev/null +++ b/feat_metadata_governor.gen.go @@ -0,0 +1,85 @@ +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 GovernorMetadataFeatureEntriesKey string +type GovernorMetadataFeatureEntriesValue []byte +type GovernorMetadataFeatureEntries map[GovernorMetadataFeatureEntriesKey]GovernorMetadataFeatureEntriesValue + +// GovernorMetadataFeature is a feature which simply holds binary data to be freely +// interpreted by higher layer applications. +type GovernorMetadataFeature struct { + Entries GovernorMetadataFeatureEntries `serix:""` +} + +func (m GovernorMetadataFeature) Clone() Feature { + copiedMap := make(GovernorMetadataFeatureEntries) + for key, value := range m.Entries { + copiedMap[key] = lo.CopySlice(value) + } + + return &GovernorMetadataFeature{ + Entries: copiedMap, + } +} + +func (m GovernorMetadataFeature) StorageScore(_ *StorageScoreStructure, _ StorageScoreFunc) StorageScore { + return 0 +} + +func (m GovernorMetadataFeature) WorkScore(_ *WorkScoreParameters) (WorkScore, error) { + return 0, nil +} + +func (m GovernorMetadataFeature) Equal(other Feature) bool { + otherFeat, is := other.(*GovernorMetadataFeature) + 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 GovernorMetadataFeature) Type() FeatureType { + return FeatureMetadataGovernor +} + +func (m GovernorMetadataFeature) 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 GovernorMetadataFeature) Size() int { + // FeatureType + Entries + return serializer.SmallTypeDenotationByteSize + m.mapSize() +} diff --git a/feat_test.go b/feat_test.go index a057029de..3c93f117c 100644 --- a/feat_test.go +++ b/feat_test.go @@ -42,7 +42,11 @@ 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"), + "empty": []byte(""), + }, }, target: &iotago.MetadataFeature{}, }, diff --git a/gen/feat_metadata.tmpl b/gen/feat_metadata.tmpl new file mode 100644 index 000000000..edc314a74 --- /dev/null +++ b/gen/feat_metadata.tmpl @@ -0,0 +1,85 @@ +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 simply holds binary data to be freely +// interpreted by higher layer applications. +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..e6a0e75fe --- /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_governor.gen.go GovernorMetadataFeature m "" "FeatureType=FeatureMetadataGovernor," 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/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..850761d50 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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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..1b4a5719b 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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, + // adding governor metadata feature + &iotago.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"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.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"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}, + // adding metadata feature + &iotago.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("1337")}}, + &iotago.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"data": []byte("1338")}}, }, }, 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.MetadataFeature{ + Entries: iotago.MetadataFeatureEntries{ + "data": []byte("foo"), + }, + }, + }, }, }, nextMut: map[string]fieldMutations{ "amount": { "Amount": iotago.BaseToken(1337), }, - "state_metadata": { - "StateMetadata": []byte("7331"), + "metadata_feature_changed": { + "Features": iotago.AnchorOutputFeatures{ + &iotago.MetadataFeature{ + Entries: iotago.MetadataFeatureEntries{ + "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.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"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": { + "governance_metadata_feature_changed": { "StateIndex": uint32(11), "Features": iotago.AnchorOutputFeatures{ - &iotago.MetadataFeature{Data: []byte("foo")}, + &iotago.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"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..2020cf61d 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.FeatureMetadata, current.Features.MustSet(), next.Features.MustSet()); err != nil { + return ierrors.Wrapf(iotago.ErrInvalidAnchorGovernanceTransition, "%w", err) } return nil @@ -753,7 +754,7 @@ func anchorStateSTVF(input *vm.ChainOutputWithIDs, next *iotago.AnchorOutput) er return ierrors.Wrapf(iotago.ErrInvalidAnchorStateTransition, "state index %d on the input side but %d on the output side", current.StateIndex, next.StateIndex) } - if err := iotago.FeatureUnchanged(iotago.FeatureMetadata, current.Features.MustSet(), next.Features.MustSet()); err != nil { + if err := iotago.FeatureUnchanged(iotago.FeatureMetadataGovernor, current.Features.MustSet(), next.Features.MustSet()); err != nil { return ierrors.Wrapf(iotago.ErrInvalidAnchorStateTransition, "%w", err) } diff --git a/vm/nova/vm_test.go b/vm/nova/vm_test.go index d265ea004..3372f618e 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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"data": []byte("gov transitioning")}}, + &iotago.GovernorMetadataFeature{Entries: iotago.GovernorMetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{ + "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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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.MetadataFeature{Entries: iotago.MetadataFeatureEntries{"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")}}, }, }, },