From 9420e5bbc5ab8a986d86c24d30e87f79c1df5e9a Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Wed, 23 Jul 2025 17:36:19 -0400 Subject: [PATCH] feat: helper function to convert MultiAsset to PlutusData This also disables the 'noctx' linter Fixes #1101 Signed-off-by: Aurora Gaffney --- .golangci.yml | 2 +- ledger/common/common.go | 36 +++++++++++++++ ledger/common/common_test.go | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 1b3d3e70..bf116e9f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,7 +24,6 @@ linters: - musttag - nilerr - nilnesserr - - noctx - perfsprint - prealloc - protogetter @@ -39,6 +38,7 @@ linters: disable: - asasalint - depguard + - noctx - recvcheck - unparam exclusions: diff --git a/ledger/common/common.go b/ledger/common/common.go index 6755f9bc..87a6b60d 100644 --- a/ledger/common/common.go +++ b/ledger/common/common.go @@ -15,9 +15,13 @@ package common import ( + "bytes" "encoding/hex" "encoding/json" "fmt" + "maps" + "math/big" + "slices" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/plutigo/pkg/data" @@ -199,6 +203,38 @@ func (m MultiAsset[T]) MarshalJSON() ([]byte, error) { return json.Marshal(&tmpAssets) } +func (m *MultiAsset[T]) ToPlutusData() data.PlutusData { + tmpData := make([][2]data.PlutusData, 0, len(m.data)) + // Sort policy IDs + policyKeys := slices.Collect(maps.Keys(m.data)) + slices.SortFunc(policyKeys, func(a, b Blake2b224) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + for _, policyId := range policyKeys { + policyData := m.data[policyId] + tmpPolicyData := make([][2]data.PlutusData, 0, len(policyData)) + // Sort asset names + assetKeys := slices.Collect(maps.Keys(policyData)) + slices.SortFunc(assetKeys, func(a, b cbor.ByteString) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + for _, assetName := range assetKeys { + amount := policyData[assetName] + tmpPolicyData = append( + tmpPolicyData, + [2]data.PlutusData{ + data.NewByteString(assetName.Bytes()), + data.NewInteger(big.NewInt(int64(amount))), + }, + ) + } + tmpData = append( + tmpData, + [2]data.PlutusData{ + data.NewByteString(policyId.Bytes()), + data.NewMap(tmpPolicyData), + }, + ) + } + return data.NewMap(tmpData) +} + func (m *MultiAsset[T]) Policies() []Blake2b224 { ret := []Blake2b224{} for policyId := range m.data { diff --git a/ledger/common/common_test.go b/ledger/common/common_test.go index 946d7a4a..12a099e8 100644 --- a/ledger/common/common_test.go +++ b/ledger/common/common_test.go @@ -17,6 +17,7 @@ package common import ( "encoding/hex" "encoding/json" + "math/big" "reflect" "testing" @@ -128,6 +129,90 @@ func TestMultiAssetJson(t *testing.T) { } } +func TestMultiAssetToPlutusData(t *testing.T) { + testDefs := []struct { + multiAssetObj any + expectedData data.PlutusData + }{ + { + multiAssetObj: MultiAsset[MultiAssetTypeOutput]{ + data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{ + NewBlake2b224(test.DecodeHexString("29a8fb8318718bd756124f0c144f56d4b4579dc5edf2dd42d669ac61")): { + cbor.NewByteString(test.DecodeHexString("6675726e697368613239686e")): 123456, + }, + }, + }, + expectedData: data.NewMap( + [][2]data.PlutusData{ + { + data.NewByteString(test.DecodeHexString("29a8fb8318718bd756124f0c144f56d4b4579dc5edf2dd42d669ac61")), + data.NewMap( + [][2]data.PlutusData{ + { + data.NewByteString(test.DecodeHexString("6675726e697368613239686e")), + data.NewInteger(big.NewInt(123456)), + }, + }, + ), + }, + }, + ), + }, + { + multiAssetObj: MultiAsset[MultiAssetTypeOutput]{ + data: map[Blake2b224]map[cbor.ByteString]MultiAssetTypeOutput{ + NewBlake2b224(test.DecodeHexString("29a8fb8318718bd756124f0c144f56d4b4579dc5edf2dd42d669ac61")): { + cbor.NewByteString(test.DecodeHexString("6675726e697368613239686e")): 123456, + }, + NewBlake2b224(test.DecodeHexString("eaf8042c1d8203b1c585822f54ec32c4c1bb4d3914603e2cca20bbd5")): { + cbor.NewByteString(test.DecodeHexString("426f7764757261436f6e63657074733638")): 234567, + }, + }, + }, + expectedData: data.NewMap( + [][2]data.PlutusData{ + { + data.NewByteString(test.DecodeHexString("29a8fb8318718bd756124f0c144f56d4b4579dc5edf2dd42d669ac61")), + data.NewMap( + [][2]data.PlutusData{ + { + data.NewByteString(test.DecodeHexString("6675726e697368613239686e")), + data.NewInteger(big.NewInt(123456)), + }, + }, + ), + }, + { + data.NewByteString(test.DecodeHexString("eaf8042c1d8203b1c585822f54ec32c4c1bb4d3914603e2cca20bbd5")), + data.NewMap( + [][2]data.PlutusData{ + { + data.NewByteString(test.DecodeHexString("426f7764757261436f6e63657074733638")), + data.NewInteger(big.NewInt(234567)), + }, + }, + ), + }, + }, + ), + }, + } + for _, testDef := range testDefs { + var tmpData data.PlutusData + switch v := testDef.multiAssetObj.(type) { + case MultiAsset[MultiAssetTypeOutput]: + tmpData = v.ToPlutusData() + case MultiAsset[MultiAssetTypeMint]: + tmpData = v.ToPlutusData() + default: + t.Fatalf("test def multi-asset object was not expected type: %T", v) + } + if !reflect.DeepEqual(tmpData, testDef.expectedData) { + t.Fatalf("did not get expected PlutusData\n got: %#v\n wanted: %#v", tmpData, testDef.expectedData) + } + } +} + func TestMultiAssetCompare(t *testing.T) { testDefs := []struct { asset1 *MultiAsset[MultiAssetTypeOutput]