Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion ledger/babbage/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,29 @@ func UtxoValidateCollateralContainsNonAda(tx common.Transaction, slot uint64, ls
}
badOutputs := []common.TransactionOutput{}
var totalCollateral uint64
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
for _, collateralInput := range tx.Collateral() {
utxo, err := ls.UtxoById(collateralInput)
if err != nil {
return err
}
totalCollateral += utxo.Output.Amount()
if utxo.Output.Assets() == nil {
totalAssets.Add(utxo.Output.Assets())
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
continue
}
badOutputs = append(badOutputs, utxo.Output)
}
if len(badOutputs) == 0 {
return nil
}
// Check if all collateral assets are accounted for in the collateral return
collReturn := tx.CollateralReturn()
if collReturn != nil {
if collReturn.Assets().Compare(&totalAssets) {
return nil
}
}
return alonzo.CollateralContainsNonAdaError{
Provided: totalCollateral,
}
Expand Down
38 changes: 36 additions & 2 deletions ledger/babbage/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,9 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
var testCollateralAmount uint64 = 100000
testTx := &babbage.BabbageTransaction{
Body: babbage.BabbageTransactionBody{},
Body: babbage.BabbageTransactionBody{
TxTotalCollateral: testCollateralAmount,
},
WitnessSet: babbage.BabbageTransactionWitnessSet{
AlonzoTransactionWitnessSet: alonzo.AlonzoTransactionWitnessSet{
WsRedeemers: []alonzo.AlonzoRedeemer{
Expand All @@ -1095,7 +1097,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
},
}
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
map[common.Blake2b224]map[cbor.ByteString]uint64{},
map[common.Blake2b224]map[cbor.ByteString]uint64{
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
cbor.NewByteString([]byte("efgh")): 123,
},
},
)
testLedgerState := test.MockLedgerState{
MockUtxos: []common.Utxo{
Expand Down Expand Up @@ -1170,6 +1176,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
}
},
)
// Coin and assets with return
t.Run(
"coin and assets with return",
func(t *testing.T) {
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
shelley.NewShelleyTransactionInput(testInputTxId, 0),
shelley.NewShelleyTransactionInput(testInputTxId, 1),
}
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
OutputAmount: mary.MaryTransactionOutputValue{
Amount: testCollateralAmount,
Assets: &tmpMultiAsset,
},
}
err := babbage.UtxoValidateCollateralContainsNonAda(
testTx,
testSlot,
testLedgerState,
testProtocolParams,
)
if err != nil {
t.Errorf(
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
err,
)
}
},
)
}

func TestUtxoValidateNoCollateralInputs(t *testing.T) {
Expand Down
38 changes: 38 additions & 0 deletions ledger/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ type MultiAsset[T MultiAssetTypeOutput | MultiAssetTypeMint] struct {
func NewMultiAsset[T MultiAssetTypeOutput | MultiAssetTypeMint](
data map[Blake2b224]map[cbor.ByteString]T,
) MultiAsset[T] {
if data == nil {
data = make(map[Blake2b224]map[cbor.ByteString]T)
}
return MultiAsset[T]{data: data}
}

Expand Down Expand Up @@ -210,6 +213,41 @@ func (m *MultiAsset[T]) Asset(policyId Blake2b224, assetName []byte) T {
return policy[cbor.NewByteString(assetName)]
}

func (m *MultiAsset[T]) Add(assets *MultiAsset[T]) {
if assets == nil {
return
}
for policy, assets := range assets.data {
for asset, amount := range assets {
newAmount := m.Asset(policy, asset.Bytes()) + amount
if _, ok := m.data[policy]; !ok {
m.data[policy] = make(map[cbor.ByteString]T)
}
m.data[policy][asset] = newAmount
}
}
}

func (m *MultiAsset[T]) Compare(assets *MultiAsset[T]) bool {
if assets == nil {
return false
}
if len(assets.data) != len(m.data) {
return false
}
for policy, assets := range assets.data {
if len(assets) != len(m.data[policy]) {
return false
}
for asset, amount := range assets {
if amount != m.Asset(policy, asset.Bytes()) {
return false
}
}
}
return true
}

type AssetFingerprint struct {
policyId []byte
assetName []byte
Expand Down
11 changes: 10 additions & 1 deletion ledger/conway/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,29 @@ func UtxoValidateCollateralContainsNonAda(tx common.Transaction, slot uint64, ls
}
badOutputs := []common.TransactionOutput{}
var totalCollateral uint64
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
for _, collateralInput := range tx.Collateral() {
utxo, err := ls.UtxoById(collateralInput)
if err != nil {
return err
}
totalCollateral += utxo.Output.Amount()
if utxo.Output.Assets() == nil {
totalAssets.Add(utxo.Output.Assets())
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
continue
}
badOutputs = append(badOutputs, utxo.Output)
}
if len(badOutputs) == 0 {
return nil
}
// Check if all collateral assets are accounted for in the collateral return
collReturn := tx.CollateralReturn()
if collReturn != nil {
if collReturn.Assets().Compare(&totalAssets) {
return nil
}
}
return alonzo.CollateralContainsNonAdaError{
Provided: totalCollateral,
}
Expand Down
40 changes: 38 additions & 2 deletions ledger/conway/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
var testCollateralAmount uint64 = 100000
testTx := &conway.ConwayTransaction{
Body: conway.ConwayTransactionBody{},
Body: conway.ConwayTransactionBody{
BabbageTransactionBody: babbage.BabbageTransactionBody{
TxTotalCollateral: testCollateralAmount,
},
},
WitnessSet: conway.ConwayTransactionWitnessSet{
WsRedeemers: conway.ConwayRedeemers{
Redeemers: map[conway.ConwayRedeemerKey]conway.ConwayRedeemerValue{
Expand All @@ -1106,7 +1110,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
},
}
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
map[common.Blake2b224]map[cbor.ByteString]uint64{},
map[common.Blake2b224]map[cbor.ByteString]uint64{
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
cbor.NewByteString([]byte("efgh")): 123,
},
},
)
testLedgerState := test.MockLedgerState{
MockUtxos: []common.Utxo{
Expand Down Expand Up @@ -1181,6 +1189,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
}
},
)
// Coin and assets with return
t.Run(
"coin and assets with return",
func(t *testing.T) {
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
shelley.NewShelleyTransactionInput(testInputTxId, 0),
shelley.NewShelleyTransactionInput(testInputTxId, 1),
}
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
OutputAmount: mary.MaryTransactionOutputValue{
Amount: testCollateralAmount,
Assets: &tmpMultiAsset,
},
}
err := conway.UtxoValidateCollateralContainsNonAda(
testTx,
testSlot,
testLedgerState,
testProtocolParams,
)
if err != nil {
t.Errorf(
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
err,
)
}
},
)
}

func TestUtxoValidateNoCollateralInputs(t *testing.T) {
Expand Down
Loading