Skip to content

Commit ca2b4ef

Browse files
committed
feat: allow collateral assets in Babbage if all are returned
Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 6d54ea3 commit ca2b4ef

File tree

5 files changed

+132
-6
lines changed

5 files changed

+132
-6
lines changed

ledger/babbage/rules.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,29 @@ func UtxoValidateCollateralContainsNonAda(tx common.Transaction, slot uint64, ls
109109
}
110110
badOutputs := []common.TransactionOutput{}
111111
var totalCollateral uint64
112+
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
112113
for _, collateralInput := range tx.Collateral() {
113114
utxo, err := ls.UtxoById(collateralInput)
114115
if err != nil {
115116
return err
116117
}
117118
totalCollateral += utxo.Output.Amount()
118-
if utxo.Output.Assets() == nil {
119+
totalAssets.Add(utxo.Output.Assets())
120+
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
119121
continue
120122
}
121123
badOutputs = append(badOutputs, utxo.Output)
122124
}
123125
if len(badOutputs) == 0 {
124126
return nil
125127
}
128+
// Check if all collateral assets are accounted for in the collateral return
129+
collReturn := tx.CollateralReturn()
130+
if collReturn != nil {
131+
if collReturn.Assets().Compare(&totalAssets) {
132+
return nil
133+
}
134+
}
126135
return alonzo.CollateralContainsNonAdaError{
127136
Provided: totalCollateral,
128137
}

ledger/babbage/rules_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,9 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
10841084
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
10851085
var testCollateralAmount uint64 = 100000
10861086
testTx := &babbage.BabbageTransaction{
1087-
Body: babbage.BabbageTransactionBody{},
1087+
Body: babbage.BabbageTransactionBody{
1088+
TxTotalCollateral: testCollateralAmount,
1089+
},
10881090
WitnessSet: babbage.BabbageTransactionWitnessSet{
10891091
AlonzoTransactionWitnessSet: alonzo.AlonzoTransactionWitnessSet{
10901092
WsRedeemers: []alonzo.AlonzoRedeemer{
@@ -1095,7 +1097,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
10951097
},
10961098
}
10971099
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1098-
map[common.Blake2b224]map[cbor.ByteString]uint64{},
1100+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1101+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1102+
cbor.NewByteString([]byte("efgh")): 123,
1103+
},
1104+
},
10991105
)
11001106
testLedgerState := test.MockLedgerState{
11011107
MockUtxos: []common.Utxo{
@@ -1170,6 +1176,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11701176
}
11711177
},
11721178
)
1179+
// Coin and assets with return
1180+
t.Run(
1181+
"coin and assets with return",
1182+
func(t *testing.T) {
1183+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1184+
shelley.NewShelleyTransactionInput(testInputTxId, 0),
1185+
shelley.NewShelleyTransactionInput(testInputTxId, 1),
1186+
}
1187+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1188+
OutputAmount: mary.MaryTransactionOutputValue{
1189+
Amount: testCollateralAmount,
1190+
Assets: &tmpMultiAsset,
1191+
},
1192+
}
1193+
err := babbage.UtxoValidateCollateralContainsNonAda(
1194+
testTx,
1195+
testSlot,
1196+
testLedgerState,
1197+
testProtocolParams,
1198+
)
1199+
if err != nil {
1200+
t.Errorf(
1201+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1202+
err,
1203+
)
1204+
}
1205+
},
1206+
)
11731207
}
11741208

11751209
func TestUtxoValidateNoCollateralInputs(t *testing.T) {

ledger/common/common.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ type MultiAsset[T MultiAssetTypeOutput | MultiAssetTypeMint] struct {
141141
func NewMultiAsset[T MultiAssetTypeOutput | MultiAssetTypeMint](
142142
data map[Blake2b224]map[cbor.ByteString]T,
143143
) MultiAsset[T] {
144+
if data == nil {
145+
data = make(map[Blake2b224]map[cbor.ByteString]T)
146+
}
144147
return MultiAsset[T]{data: data}
145148
}
146149

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

216+
func (m *MultiAsset[T]) Add(assets *MultiAsset[T]) {
217+
if assets == nil {
218+
return
219+
}
220+
for policy, assets := range assets.data {
221+
for asset, amount := range assets {
222+
newAmount := m.Asset(policy, asset.Bytes()) + amount
223+
if _, ok := m.data[policy]; !ok {
224+
m.data[policy] = make(map[cbor.ByteString]T)
225+
}
226+
m.data[policy][asset] = newAmount
227+
}
228+
}
229+
}
230+
231+
func (m *MultiAsset[T]) Compare(assets *MultiAsset[T]) bool {
232+
if assets == nil {
233+
return false
234+
}
235+
if len(assets.data) != len(m.data) {
236+
return false
237+
}
238+
for policy, assets := range assets.data {
239+
if len(assets) != len(m.data[policy]) {
240+
return false
241+
}
242+
for asset, amount := range assets {
243+
if amount != m.Asset(policy, asset.Bytes()) {
244+
return false
245+
}
246+
}
247+
}
248+
return true
249+
}
250+
213251
type AssetFingerprint struct {
214252
policyId []byte
215253
assetName []byte

ledger/conway/rules.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,29 @@ func UtxoValidateCollateralContainsNonAda(tx common.Transaction, slot uint64, ls
129129
}
130130
badOutputs := []common.TransactionOutput{}
131131
var totalCollateral uint64
132+
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
132133
for _, collateralInput := range tx.Collateral() {
133134
utxo, err := ls.UtxoById(collateralInput)
134135
if err != nil {
135136
return err
136137
}
137138
totalCollateral += utxo.Output.Amount()
138-
if utxo.Output.Assets() == nil {
139+
totalAssets.Add(utxo.Output.Assets())
140+
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
139141
continue
140142
}
141143
badOutputs = append(badOutputs, utxo.Output)
142144
}
143145
if len(badOutputs) == 0 {
144146
return nil
145147
}
148+
// Check if all collateral assets are accounted for in the collateral return
149+
collReturn := tx.CollateralReturn()
150+
if collReturn != nil {
151+
if collReturn.Assets().Compare(&totalAssets) {
152+
return nil
153+
}
154+
}
146155
return alonzo.CollateralContainsNonAdaError{
147156
Provided: totalCollateral,
148157
}

ledger/conway/rules_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
10951095
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
10961096
var testCollateralAmount uint64 = 100000
10971097
testTx := &conway.ConwayTransaction{
1098-
Body: conway.ConwayTransactionBody{},
1098+
Body: conway.ConwayTransactionBody{
1099+
BabbageTransactionBody: babbage.BabbageTransactionBody{
1100+
TxTotalCollateral: testCollateralAmount,
1101+
},
1102+
},
10991103
WitnessSet: conway.ConwayTransactionWitnessSet{
11001104
WsRedeemers: conway.ConwayRedeemers{
11011105
Redeemers: map[conway.ConwayRedeemerKey]conway.ConwayRedeemerValue{
@@ -1106,7 +1110,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11061110
},
11071111
}
11081112
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1109-
map[common.Blake2b224]map[cbor.ByteString]uint64{},
1113+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1114+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1115+
cbor.NewByteString([]byte("efgh")): 123,
1116+
},
1117+
},
11101118
)
11111119
testLedgerState := test.MockLedgerState{
11121120
MockUtxos: []common.Utxo{
@@ -1181,6 +1189,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11811189
}
11821190
},
11831191
)
1192+
// Coin and assets with return
1193+
t.Run(
1194+
"coin and assets with return",
1195+
func(t *testing.T) {
1196+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1197+
shelley.NewShelleyTransactionInput(testInputTxId, 0),
1198+
shelley.NewShelleyTransactionInput(testInputTxId, 1),
1199+
}
1200+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1201+
OutputAmount: mary.MaryTransactionOutputValue{
1202+
Amount: testCollateralAmount,
1203+
Assets: &tmpMultiAsset,
1204+
},
1205+
}
1206+
err := conway.UtxoValidateCollateralContainsNonAda(
1207+
testTx,
1208+
testSlot,
1209+
testLedgerState,
1210+
testProtocolParams,
1211+
)
1212+
if err != nil {
1213+
t.Errorf(
1214+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1215+
err,
1216+
)
1217+
}
1218+
},
1219+
)
11841220
}
11851221

11861222
func TestUtxoValidateNoCollateralInputs(t *testing.T) {

0 commit comments

Comments
 (0)