Skip to content

Commit ce98f6f

Browse files
authored
feat: allow collateral assets in Babbage if all are returned (#943)
Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 429968d commit ce98f6f

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
@@ -134,20 +134,29 @@ func UtxoValidateCollateralContainsNonAda(
134134
}
135135
badOutputs := []common.TransactionOutput{}
136136
var totalCollateral uint64
137+
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
137138
for _, collateralInput := range tx.Collateral() {
138139
utxo, err := ls.UtxoById(collateralInput)
139140
if err != nil {
140141
return err
141142
}
142143
totalCollateral += utxo.Output.Amount()
143-
if utxo.Output.Assets() == nil {
144+
totalAssets.Add(utxo.Output.Assets())
145+
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
144146
continue
145147
}
146148
badOutputs = append(badOutputs, utxo.Output)
147149
}
148150
if len(badOutputs) == 0 {
149151
return nil
150152
}
153+
// Check if all collateral assets are accounted for in the collateral return
154+
collReturn := tx.CollateralReturn()
155+
if collReturn != nil {
156+
if collReturn.Assets().Compare(&totalAssets) {
157+
return nil
158+
}
159+
}
151160
return alonzo.CollateralContainsNonAdaError{
152161
Provided: totalCollateral,
153162
}

ledger/babbage/rules_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,9 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
10991099
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
11001100
var testCollateralAmount uint64 = 100000
11011101
testTx := &babbage.BabbageTransaction{
1102-
Body: babbage.BabbageTransactionBody{},
1102+
Body: babbage.BabbageTransactionBody{
1103+
TxTotalCollateral: testCollateralAmount,
1104+
},
11031105
WitnessSet: babbage.BabbageTransactionWitnessSet{
11041106
AlonzoTransactionWitnessSet: alonzo.AlonzoTransactionWitnessSet{
11051107
WsRedeemers: []alonzo.AlonzoRedeemer{
@@ -1110,7 +1112,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11101112
},
11111113
}
11121114
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1113-
map[common.Blake2b224]map[cbor.ByteString]uint64{},
1115+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1116+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1117+
cbor.NewByteString([]byte("efgh")): 123,
1118+
},
1119+
},
11141120
)
11151121
testLedgerState := test.MockLedgerState{
11161122
MockUtxos: []common.Utxo{
@@ -1185,6 +1191,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11851191
}
11861192
},
11871193
)
1194+
// Coin and assets with return
1195+
t.Run(
1196+
"coin and assets with return",
1197+
func(t *testing.T) {
1198+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1199+
shelley.NewShelleyTransactionInput(testInputTxId, 0),
1200+
shelley.NewShelleyTransactionInput(testInputTxId, 1),
1201+
}
1202+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1203+
OutputAmount: mary.MaryTransactionOutputValue{
1204+
Amount: testCollateralAmount,
1205+
Assets: &tmpMultiAsset,
1206+
},
1207+
}
1208+
err := babbage.UtxoValidateCollateralContainsNonAda(
1209+
testTx,
1210+
testSlot,
1211+
testLedgerState,
1212+
testProtocolParams,
1213+
)
1214+
if err != nil {
1215+
t.Errorf(
1216+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1217+
err,
1218+
)
1219+
}
1220+
},
1221+
)
11881222
}
11891223

11901224
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
@@ -159,20 +159,29 @@ func UtxoValidateCollateralContainsNonAda(
159159
}
160160
badOutputs := []common.TransactionOutput{}
161161
var totalCollateral uint64
162+
totalAssets := common.NewMultiAsset[common.MultiAssetTypeOutput](nil)
162163
for _, collateralInput := range tx.Collateral() {
163164
utxo, err := ls.UtxoById(collateralInput)
164165
if err != nil {
165166
return err
166167
}
167168
totalCollateral += utxo.Output.Amount()
168-
if utxo.Output.Assets() == nil {
169+
totalAssets.Add(utxo.Output.Assets())
170+
if utxo.Output.Assets() == nil || len(utxo.Output.Assets().Policies()) == 0 {
169171
continue
170172
}
171173
badOutputs = append(badOutputs, utxo.Output)
172174
}
173175
if len(badOutputs) == 0 {
174176
return nil
175177
}
178+
// Check if all collateral assets are accounted for in the collateral return
179+
collReturn := tx.CollateralReturn()
180+
if collReturn != nil {
181+
if collReturn.Assets().Compare(&totalAssets) {
182+
return nil
183+
}
184+
}
176185
return alonzo.CollateralContainsNonAdaError{
177186
Provided: totalCollateral,
178187
}

ledger/conway/rules_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11071107
testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22"
11081108
var testCollateralAmount uint64 = 100000
11091109
testTx := &conway.ConwayTransaction{
1110-
Body: conway.ConwayTransactionBody{},
1110+
Body: conway.ConwayTransactionBody{
1111+
BabbageTransactionBody: babbage.BabbageTransactionBody{
1112+
TxTotalCollateral: testCollateralAmount,
1113+
},
1114+
},
11111115
WitnessSet: conway.ConwayTransactionWitnessSet{
11121116
WsRedeemers: conway.ConwayRedeemers{
11131117
Redeemers: map[conway.ConwayRedeemerKey]conway.ConwayRedeemerValue{
@@ -1118,7 +1122,11 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11181122
},
11191123
}
11201124
tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1121-
map[common.Blake2b224]map[cbor.ByteString]uint64{},
1125+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1126+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1127+
cbor.NewByteString([]byte("efgh")): 123,
1128+
},
1129+
},
11221130
)
11231131
testLedgerState := test.MockLedgerState{
11241132
MockUtxos: []common.Utxo{
@@ -1193,6 +1201,34 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11931201
}
11941202
},
11951203
)
1204+
// Coin and assets with return
1205+
t.Run(
1206+
"coin and assets with return",
1207+
func(t *testing.T) {
1208+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1209+
shelley.NewShelleyTransactionInput(testInputTxId, 0),
1210+
shelley.NewShelleyTransactionInput(testInputTxId, 1),
1211+
}
1212+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1213+
OutputAmount: mary.MaryTransactionOutputValue{
1214+
Amount: testCollateralAmount,
1215+
Assets: &tmpMultiAsset,
1216+
},
1217+
}
1218+
err := conway.UtxoValidateCollateralContainsNonAda(
1219+
testTx,
1220+
testSlot,
1221+
testLedgerState,
1222+
testProtocolParams,
1223+
)
1224+
if err != nil {
1225+
t.Errorf(
1226+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1227+
err,
1228+
)
1229+
}
1230+
},
1231+
)
11961232
}
11971233

11981234
func TestUtxoValidateNoCollateralInputs(t *testing.T) {

0 commit comments

Comments
 (0)