Skip to content

Commit 98b40a0

Browse files
authored
fix: better comparison for MultiAsset (#945)
Signed-off-by: Aurora Gaffney <[email protected]>
1 parent d9d8273 commit 98b40a0

File tree

6 files changed

+193
-14
lines changed

6 files changed

+193
-14
lines changed

ledger/babbage/rules.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,8 @@ func UtxoValidateCollateralContainsNonAda(
154154
collReturn := tx.CollateralReturn()
155155
if collReturn != nil {
156156
collReturnAssets := collReturn.Assets()
157-
if collReturnAssets != nil {
158-
if collReturnAssets.Compare(&totalAssets) {
159-
return nil
160-
}
157+
if (&totalAssets).Compare(collReturnAssets) {
158+
return nil
161159
}
162160
}
163161
return alonzo.CollateralContainsNonAdaError{

ledger/babbage/rules_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,13 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11181118
},
11191119
},
11201120
)
1121+
tmpZeroMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1122+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1123+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1124+
cbor.NewByteString([]byte("efgh")): 0,
1125+
},
1126+
},
1127+
)
11211128
testLedgerState := test.MockLedgerState{
11221129
MockUtxos: []common.Utxo{
11231130
{
@@ -1135,6 +1142,15 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11351142
},
11361143
},
11371144
},
1145+
{
1146+
Id: shelley.NewShelleyTransactionInput(testInputTxId, 2),
1147+
Output: babbage.BabbageTransactionOutput{
1148+
OutputAmount: mary.MaryTransactionOutputValue{
1149+
Amount: testCollateralAmount,
1150+
Assets: &tmpZeroMultiAsset,
1151+
},
1152+
},
1153+
},
11381154
},
11391155
}
11401156
testSlot := uint64(0)
@@ -1219,6 +1235,32 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
12191235
}
12201236
},
12211237
)
1238+
// Coin and zero assets with return
1239+
t.Run(
1240+
"coin and zero assets with return",
1241+
func(t *testing.T) {
1242+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1243+
shelley.NewShelleyTransactionInput(testInputTxId, 2),
1244+
}
1245+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1246+
OutputAmount: mary.MaryTransactionOutputValue{
1247+
Amount: testCollateralAmount,
1248+
},
1249+
}
1250+
err := babbage.UtxoValidateCollateralContainsNonAda(
1251+
testTx,
1252+
testSlot,
1253+
testLedgerState,
1254+
testProtocolParams,
1255+
)
1256+
if err != nil {
1257+
t.Errorf(
1258+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1259+
err,
1260+
)
1261+
}
1262+
},
1263+
)
12221264
}
12231265

12241266
func TestUtxoValidateNoCollateralInputs(t *testing.T) {

ledger/common/common.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,20 @@ func (m *MultiAsset[T]) Add(assets *MultiAsset[T]) {
229229
}
230230

231231
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) {
232+
// Normalize data for easier comparison
233+
tmpData := m.normalize()
234+
otherData := assets.normalize()
235+
// Compare policy counts
236+
if len(otherData) != len(tmpData) {
236237
return false
237238
}
238-
for policy, assets := range assets.data {
239-
if len(assets) != len(m.data[policy]) {
239+
for policy, assets := range otherData {
240+
// Compare asset counts for policy
241+
if len(assets) != len(tmpData[policy]) {
240242
return false
241243
}
242244
for asset, amount := range assets {
245+
// Compare quantity of specific asset
243246
if amount != m.Asset(policy, asset.Bytes()) {
244247
return false
245248
}
@@ -248,6 +251,24 @@ func (m *MultiAsset[T]) Compare(assets *MultiAsset[T]) bool {
248251
return true
249252
}
250253

254+
func (m *MultiAsset[T]) normalize() map[Blake2b224]map[cbor.ByteString]T {
255+
ret := map[Blake2b224]map[cbor.ByteString]T{}
256+
if m == nil || m.data == nil {
257+
return ret
258+
}
259+
for policy, assets := range m.data {
260+
for asset, amount := range assets {
261+
if amount != 0 {
262+
if _, ok := ret[policy]; !ok {
263+
ret[policy] = make(map[cbor.ByteString]T)
264+
}
265+
ret[policy][asset] = amount
266+
}
267+
}
268+
}
269+
return ret
270+
}
271+
251272
type AssetFingerprint struct {
252273
policyId []byte
253274
assetName []byte

ledger/common/common_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,84 @@ func TestMultiAssetJson(t *testing.T) {
126126
}
127127
}
128128

129+
func TestMultiAssetCompare(t *testing.T) {
130+
testDefs := []struct {
131+
asset1 *MultiAsset[MultiAssetTypeOutput]
132+
asset2 *MultiAsset[MultiAssetTypeOutput]
133+
expectedResult bool
134+
}{
135+
{
136+
asset1: &MultiAsset[MultiAssetTypeOutput]{
137+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
138+
NewBlake2b224([]byte("abcd")): {
139+
cbor.NewByteString([]byte("cdef")): 123,
140+
},
141+
},
142+
},
143+
asset2: &MultiAsset[MultiAssetTypeOutput]{
144+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
145+
NewBlake2b224([]byte("abcd")): {
146+
cbor.NewByteString([]byte("cdef")): 123,
147+
},
148+
},
149+
},
150+
expectedResult: true,
151+
},
152+
{
153+
asset1: &MultiAsset[MultiAssetTypeOutput]{
154+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
155+
NewBlake2b224([]byte("abcd")): {
156+
cbor.NewByteString([]byte("cdef")): 123,
157+
},
158+
},
159+
},
160+
asset2: &MultiAsset[MultiAssetTypeOutput]{
161+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
162+
NewBlake2b224([]byte("abcd")): {
163+
cbor.NewByteString([]byte("cdef")): 124,
164+
},
165+
},
166+
},
167+
expectedResult: false,
168+
},
169+
{
170+
asset1: &MultiAsset[MultiAssetTypeOutput]{
171+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
172+
NewBlake2b224([]byte("abcd")): {
173+
cbor.NewByteString([]byte("cdef")): 0,
174+
},
175+
},
176+
},
177+
asset2: nil,
178+
expectedResult: true,
179+
},
180+
{
181+
asset1: &MultiAsset[MultiAssetTypeOutput]{
182+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
183+
NewBlake2b224([]byte("abcd")): {
184+
cbor.NewByteString([]byte("cdef")): 123,
185+
},
186+
},
187+
},
188+
asset2: &MultiAsset[MultiAssetTypeOutput]{
189+
data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{
190+
NewBlake2b224([]byte("abcd")): {
191+
cbor.NewByteString([]byte("cdef")): 123,
192+
cbor.NewByteString([]byte("efgh")): 123,
193+
},
194+
},
195+
},
196+
expectedResult: false,
197+
},
198+
}
199+
for _, testDef := range testDefs {
200+
tmpResult := testDef.asset1.Compare(testDef.asset2)
201+
if tmpResult != testDef.expectedResult {
202+
t.Errorf("did not get expected result: got %v, wanted %v", tmpResult, testDef.expectedResult)
203+
}
204+
}
205+
}
206+
129207
// Test the MarshalJSON method for Blake2b224 to ensure it properly converts to JSON.
130208
func TestBlake2b224_MarshalJSON(t *testing.T) {
131209
// Example data to represent Blake2b224 hash

ledger/conway/rules.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,8 @@ func UtxoValidateCollateralContainsNonAda(
179179
collReturn := tx.CollateralReturn()
180180
if collReturn != nil {
181181
collReturnAssets := collReturn.Assets()
182-
if collReturnAssets != nil {
183-
if collReturnAssets.Compare(&totalAssets) {
184-
return nil
185-
}
182+
if (&totalAssets).Compare(collReturnAssets) {
183+
return nil
186184
}
187185
}
188186
return alonzo.CollateralContainsNonAdaError{

ledger/conway/rules_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,13 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11281128
},
11291129
},
11301130
)
1131+
tmpZeroMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput](
1132+
map[common.Blake2b224]map[cbor.ByteString]uint64{
1133+
common.Blake2b224Hash([]byte("abcd")): map[cbor.ByteString]uint64{
1134+
cbor.NewByteString([]byte("efgh")): 0,
1135+
},
1136+
},
1137+
)
11311138
testLedgerState := test.MockLedgerState{
11321139
MockUtxos: []common.Utxo{
11331140
{
@@ -1145,6 +1152,15 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
11451152
},
11461153
},
11471154
},
1155+
{
1156+
Id: shelley.NewShelleyTransactionInput(testInputTxId, 2),
1157+
Output: babbage.BabbageTransactionOutput{
1158+
OutputAmount: mary.MaryTransactionOutputValue{
1159+
Amount: testCollateralAmount,
1160+
Assets: &tmpZeroMultiAsset,
1161+
},
1162+
},
1163+
},
11481164
},
11491165
}
11501166
testSlot := uint64(0)
@@ -1229,6 +1245,32 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) {
12291245
}
12301246
},
12311247
)
1248+
// Coin and zero assets with return
1249+
t.Run(
1250+
"coin and zero assets with return",
1251+
func(t *testing.T) {
1252+
testTx.Body.TxCollateral = []shelley.ShelleyTransactionInput{
1253+
shelley.NewShelleyTransactionInput(testInputTxId, 2),
1254+
}
1255+
testTx.Body.TxCollateralReturn = &babbage.BabbageTransactionOutput{
1256+
OutputAmount: mary.MaryTransactionOutputValue{
1257+
Amount: testCollateralAmount,
1258+
},
1259+
}
1260+
err := conway.UtxoValidateCollateralContainsNonAda(
1261+
testTx,
1262+
testSlot,
1263+
testLedgerState,
1264+
testProtocolParams,
1265+
)
1266+
if err != nil {
1267+
t.Errorf(
1268+
"UtxoValidateCollateralContainsNonAda should succeed when collateral with only coin is provided\n got error: %v",
1269+
err,
1270+
)
1271+
}
1272+
},
1273+
)
12321274
}
12331275

12341276
func TestUtxoValidateNoCollateralInputs(t *testing.T) {

0 commit comments

Comments
 (0)