Skip to content

Commit

Permalink
Merge pull request #423 from iotaledger/fix/chained-outputs
Browse files Browse the repository at this point in the history
Fix chained outputs
  • Loading branch information
luca-moser authored Mar 7, 2023
2 parents ef33ea7 + 52162de commit 4f1f458
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 5 deletions.
24 changes: 24 additions & 0 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,30 @@ func SyntacticallyValidateOutputs(outputs Outputs, funcs ...OutputsSyntacticalVa
return nil
}

func OutputsSyntacticalChainConstrainedOutputUniqueness() OutputsSyntacticalValidationFunc {
chainConstrainedOutputs := make(ChainConstrainedOutputsSet)

return func(index int, output Output) error {
chainConstrainedOutput, is := output.(ChainConstrainedOutput)
if !is {
return nil
}

chainID := chainConstrainedOutput.Chain()
if chainID.Empty() {
// we can ignore newly minted chainConstrainedOutputs
return nil
}

if _, has := chainConstrainedOutputs[chainID]; has {
return fmt.Errorf("%w: output with chainID %s already exist on the output side", ErrNonUniqueChainConstrainedOutputs, chainID.ToHex())
}

chainConstrainedOutputs[chainID] = chainConstrainedOutput
return nil
}
}

// JsonOutputSelector selects the json output implementation for the given type.
func JsonOutputSelector(ty int) (JSONSerializable, error) {
var obj JSONSerializable
Expand Down
9 changes: 4 additions & 5 deletions protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/iotaledger/hive.go/serializer/v2"
Expand All @@ -30,19 +29,19 @@ func (test *deSerializeTest) deSerialize(t *testing.T) {
require.Error(t, err, test.seriErr)
return
}
assert.NoError(t, err)
require.NoError(t, err)
if src, ok := test.source.(serializer.SerializableWithSize); ok {
assert.Equal(t, len(data), src.Size())
require.Equal(t, len(data), src.Size())
}

bytesRead, err := test.target.Deserialize(data, serializer.DeSeriModePerformValidation, tpkg.TestProtoParas)
if test.deSeriErr != nil {
require.Error(t, err, test.deSeriErr)
return
}
assert.NoError(t, err)
require.NoError(t, err)
require.Len(t, data, bytesRead)
assert.EqualValues(t, test.source, test.target)
require.EqualValues(t, test.source, test.target)
}

func TestProtocolParameters_DeSerialize(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions transaction_essence.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ func (u *TransactionEssence) syntacticallyValidate(protoParas *ProtocolParameter
OutputsSyntacticalDepositAmount(protoParas),
OutputsSyntacticalExpirationAndTimelock(),
OutputsSyntacticalNativeTokens(),
OutputsSyntacticalChainConstrainedOutputUniqueness(),
OutputsSyntacticalFoundry(),
OutputsSyntacticalAlias(),
OutputsSyntacticalNFT(),
Expand Down
129 changes: 129 additions & 0 deletions transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,135 @@ func TestNFTTransition(t *testing.T) {
}, inputs))
}

func TestChainConstrainedOutputUniqueness(t *testing.T) {
ident1 := tpkg.RandEd25519Address()

inputIDs := tpkg.RandOutputIDs(1)

aliasAddress := iotago.AliasAddressFromOutputID(inputIDs[0])
aliasID := aliasAddress.AliasID()

nftAddress := iotago.NFTAddressFromOutputID(inputIDs[0])
nftID := nftAddress.NFTID()

tests := []deSerializeTest{
{
// we transition the same Alias twice
name: "transition the same Alias twice",
source: tpkg.RandTransactionWithEssence(&iotago.TransactionEssence{
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Outputs: iotago.Outputs{
&iotago.AliasOutput{
Amount: OneMi,
AliasID: aliasID,
Conditions: iotago.UnlockConditions{
&iotago.StateControllerAddressUnlockCondition{Address: ident1},
&iotago.GovernorAddressUnlockCondition{Address: ident1},
},
Features: nil,
},
&iotago.AliasOutput{
Amount: OneMi,
AliasID: aliasID,
Conditions: iotago.UnlockConditions{
&iotago.StateControllerAddressUnlockCondition{Address: ident1},
&iotago.GovernorAddressUnlockCondition{Address: ident1},
},
Features: nil,
},
},
}),
target: &iotago.Transaction{},
seriErr: iotago.ErrNonUniqueChainConstrainedOutputs,
deSeriErr: nil,
},
{
// we transition the same NFT twice
name: "transition the same NFT twice",
source: tpkg.RandTransactionWithEssence(&iotago.TransactionEssence{
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Outputs: iotago.Outputs{
&iotago.NFTOutput{
Amount: OneMi,
NFTID: nftID,
Conditions: iotago.UnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
Features: nil,
},
&iotago.NFTOutput{
Amount: OneMi,
NFTID: nftID,
Conditions: iotago.UnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
Features: nil,
},
},
}),
target: &iotago.Transaction{},
seriErr: iotago.ErrNonUniqueChainConstrainedOutputs,
deSeriErr: nil,
},
{
// we transition the same Foundry twice
name: "transition the same Foundry twice",
source: tpkg.RandTransactionWithEssence(&iotago.TransactionEssence{
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Outputs: iotago.Outputs{
&iotago.AliasOutput{
Amount: OneMi,
AliasID: aliasID,
Conditions: iotago.UnlockConditions{
&iotago.StateControllerAddressUnlockCondition{Address: ident1},
&iotago.GovernorAddressUnlockCondition{Address: ident1},
},
Features: nil,
},
&iotago.FoundryOutput{
Amount: OneMi,
NativeTokens: nil,
SerialNumber: 1,
TokenScheme: &iotago.SimpleTokenScheme{
MintedTokens: big.NewInt(50),
MeltedTokens: big.NewInt(0),
MaximumSupply: big.NewInt(50),
},
Conditions: iotago.UnlockConditions{
&iotago.ImmutableAliasUnlockCondition{Address: &aliasAddress},
},
Features: nil,
},
&iotago.FoundryOutput{
Amount: OneMi,
NativeTokens: nil,
SerialNumber: 1,
TokenScheme: &iotago.SimpleTokenScheme{
MintedTokens: big.NewInt(50),
MeltedTokens: big.NewInt(0),
MaximumSupply: big.NewInt(50),
},
Conditions: iotago.UnlockConditions{
&iotago.ImmutableAliasUnlockCondition{Address: &aliasAddress},
},
Features: nil,
},
},
}),
target: &iotago.Transaction{},
seriErr: iotago.ErrNonUniqueChainConstrainedOutputs,
deSeriErr: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, tt.deSerialize)
}
}

func TestCirculatingSupplyMelting(t *testing.T) {
_, ident1, ident1AddrKeys := tpkg.RandEd25519Identity()
aliasIdent1 := tpkg.RandAliasAddress()
Expand Down

0 comments on commit 4f1f458

Please sign in to comment.