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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

- Running plutip servers attaches on SIGINT handlers and therefore node will not exit by default. ([#1231](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1231)).
- `TestPlanM`, `interpret` and `interpretWithConfig` are now public in `Contract.Test.Mote` and our custom `consoleReporter` in `Contract.Test.Mote.ConsoleReporter`. ([#1261](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1261)).
- CIP-25 `policy_id` and `asset_name` metadata keys no longer include a `0x` prefix for compatibility with Blockfrost ([#1309](https://github.com/Plutonomicon/cardano-transaction-lib/issues/1309 "CTL's CIP25 metadata encoding is considered invalid by Blockfrost #1309")).

### Removed

Expand Down Expand Up @@ -139,7 +140,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Runtime Dependencies

- [Ogmios](https://github.com/mlabs-haskell/ogmios) - v5.5.7
- [Ogmios](https://github.com/mlabs-haskell/ogmios) - v5.5.7
- [Kupo](https://github.com/CardanoSolutions/kupo) - v2.2.0
- [Cardano-Node](https://github.com/input-output-hk/cardano-node/) - v1.35.3
- [Ogmios-Datum-Cache](https://github.com/mlabs-haskell/ogmios-datum-cache) - commit 862c6bfcb6110b8fe816e26b3bba105dfb492b24
Expand Down
50 changes: 39 additions & 11 deletions src/Internal/Metadata/Cip25/V2.purs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ import Ctl.Internal.Metadata.ToMetadata
, toMetadata
)
import Ctl.Internal.Plutus.Types.AssocMap (Map(Map), singleton) as AssocMap
import Ctl.Internal.Serialization.Hash (scriptHashFromBytes)
import Ctl.Internal.Serialization.Hash (scriptHashFromBytes, scriptHashToBytes)
import Ctl.Internal.ToData (class ToData, toData)
import Ctl.Internal.Types.ByteArray (byteArrayToHex, hexToByteArray)
import Ctl.Internal.Types.Int as Int
import Ctl.Internal.Types.PlutusData (PlutusData(Map, Integer))
import Ctl.Internal.Types.RawBytes (hexToRawBytes)
import Ctl.Internal.Types.RawBytes (hexToRawBytes, rawBytesToHex)
import Ctl.Internal.Types.Scripts (MintingPolicyHash)
import Ctl.Internal.Types.TokenName (mkTokenName)
import Ctl.Internal.Types.TokenName (getTokenName, mkTokenName)
import Ctl.Internal.Types.TransactionMetadata
( TransactionMetadatum(Int, MetadataMap)
)
Expand Down Expand Up @@ -142,6 +143,10 @@ instance DecodeAeson Cip25V2 where
2 -> pure Cip25V2
_ -> Left $ TypeMismatch "Cip25V2"

-- Note: this is not an instance of `ToMetadata` to prevent confusion
-- between `Cip25Metadata` and `Cip25MetadataEntry` (the users do not
-- need to deal with data-representations of a single entry, because
-- the standard only specifies the encoding for Cip25Metadata).
metadataEntryToMetadata :: Cip25MetadataEntry -> TransactionMetadatum
metadataEntryToMetadata (Cip25MetadataEntry entry) = toMetadata $
[ "name" /\ anyToMetadata entry.name
Expand Down Expand Up @@ -222,6 +227,26 @@ metadataEntryDecodeAeson policyId assetName =
pure $
wrap { policyId, assetName, name, image, mediaType, description, files }

-- | Encode the entry's policy id to the string used as the metadata
-- | key
encodePolicyIdKey :: Cip25MetadataEntry -> String
encodePolicyIdKey (Cip25MetadataEntry { policyId }) =
rawBytesToHex $ scriptHashToBytes $ unwrap policyId

-- | Decode the CIP25 policy id key
decodePolicyIdKey :: String -> Maybe MintingPolicyHash
decodePolicyIdKey = map wrap <<< scriptHashFromBytes <=< hexToRawBytes

-- | Encode the entry's asset name to the string used as the metadata
-- | key
encodeAssetNameKey :: Cip25MetadataEntry -> String
encodeAssetNameKey (Cip25MetadataEntry { assetName }) =
byteArrayToHex $ getTokenName $ unwrap assetName

-- | Decode the CIP25 asset name key
decodeAssetNameKey :: String -> Maybe Cip25TokenName
decodeAssetNameKey = map wrap <<< mkTokenName <=< hexToByteArray

newtype Cip25Metadata = Cip25Metadata (Array Cip25MetadataEntry)

derive instance Generic Cip25Metadata _
Expand All @@ -244,9 +269,10 @@ instance ToMetadata Cip25Metadata where
dataEntries =
groupEntries entries <#>
\group ->
(toMetadata <<< _.policyId <<< unwrap $ NonEmpty.head group) /\
( toMetadata $ encodePolicyIdKey $ NonEmpty.head group
) /\
(toMetadata <<< toArray <<< flip map group) \entry ->
(unwrap entry).assetName /\ metadataEntryToMetadata entry
(encodeAssetNameKey entry) /\ metadataEntryToMetadata entry
versionEntry = [ toMetadata "version" /\ toMetadata Cip25V2 ]
in
dataEntries <> versionEntry
Expand All @@ -267,8 +293,9 @@ instance FromMetadata Cip25Metadata where
Just case assets of
MetadataMap mp2 ->
for (Map.toUnfoldable mp2) \(assetName /\ contents) ->
metadataEntryFromMetadata <$> fromMetadata key
<*> fromMetadata assetName
metadataEntryFromMetadata
<$> (decodePolicyIdKey =<< fromMetadata key)
<*> (decodeAssetNameKey =<< fromMetadata assetName)
<*> pure contents
_ -> Nothing
wrap <$> sequence entries
Expand All @@ -282,9 +309,9 @@ instance ToData Cip25Metadata where
dataEntries =
groupEntries entries <#>
\group ->
toData (_.policyId <<< unwrap $ NonEmpty.head group) /\ toData
toData (encodePolicyIdKey $ NonEmpty.head group) /\ toData
( (AssocMap.Map <<< toArray <<< flip map group) \entry ->
toData ((unwrap entry).assetName) /\ metadataEntryToData
toData (encodeAssetNameKey entry) /\ metadataEntryToData
entry
)
versionEntry = [ toData "version" /\ toData Cip25V2 ]
Expand All @@ -302,8 +329,9 @@ instance FromData Cip25Metadata where
case assets of
Map mp2 ->
for mp2 \(assetName /\ contents) ->
metadataEntryFromData <$> fromData policyId
<*> fromData assetName
metadataEntryFromData
<$> (decodePolicyIdKey =<< fromData policyId)
<*> (decodeAssetNameKey =<< fromData assetName)
<*> pure contents
_ -> Nothing
fromDataVersion =
Expand Down
65 changes: 47 additions & 18 deletions test/Fixtures.purs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Test.Ctl.Fixtures
, cip25MetadataFixture1
, cip25MetadataFixture2
, cip25MetadataFixture3
, cip25MetadataFixture4
, cip25MetadataJsonFixture1
, cip25MetadataJsonFixture2
, cip25MetadataJsonFixture3
Expand Down Expand Up @@ -257,13 +258,18 @@ currencySymbol1 = unsafePartial $ fromJust $ mkCurrencySymbol $
hexToByteArrayUnsafe
"1d6445ddeda578117f393848e685128f1e78ad0c4e48129c5964dc2e"

tokenNameFromString :: String -> TokenName
tokenNameFromString s = unsafePartial $ fromJust $ mkTokenName $
hexToByteArrayUnsafe s

tokenName1 :: TokenName
tokenName1 = unsafePartial $ fromJust $ mkTokenName $
hexToByteArrayUnsafe "4974657374546f6b656e"
tokenName1 = tokenNameFromString "4974657374546f6b656e"

tokenName2 :: TokenName
tokenName2 = unsafePartial $ fromJust $ mkTokenName $
hexToByteArrayUnsafe "54657374546f6b656e32"
tokenName2 = tokenNameFromString "54657374546f6b656e32"

tokenName4 :: TokenName
tokenName4 = tokenNameFromString "abcdef"

txOutputBinaryFixture1 :: String
txOutputBinaryFixture1 =
Expand Down Expand Up @@ -1305,14 +1311,24 @@ plutusDataFixture8Bytes' = hexToByteArrayUnsafe
\206f6620736b7920581cda13ed22b9294f1d86bbd530e99b1456884c7364bf16c90edc1ae41e\
\182d"

scriptHashFromString :: String -> ScriptHash
scriptHashFromString s = unsafePartial $ fromJust $ scriptHashFromBytes $
hexToRawBytesUnsafe s

scriptHash1 :: ScriptHash
scriptHash1 = unsafePartial $ fromJust $ scriptHashFromBytes $
hexToRawBytesUnsafe
"5d677265fa5bb21ce6d8c7502aca70b9316d10e958611f3c6b758f65"
scriptHash1 = scriptHashFromString
"5d677265fa5bb21ce6d8c7502aca70b9316d10e958611f3c6b758f65"

scriptHash4 :: ScriptHash
scriptHash4 = scriptHashFromString
"1e4409ba69fb38887c23d15a766476384085b66a755530934938abfe"

policyId :: MintingPolicyHash
policyId = MintingPolicyHash scriptHash1

policyId4 :: MintingPolicyHash
policyId4 = MintingPolicyHash scriptHash4

cip25MetadataFilesFixture1 :: Array Cip25MetadataFile
cip25MetadataFilesFixture1 = Cip25MetadataFile <$>
[ { name: unsafeMkCip25String "file_name_1"
Expand Down Expand Up @@ -1379,34 +1395,47 @@ cip25MetadataFixture3 = Cip25Metadata
}
]

cip25MetadataFixture4 :: Cip25Metadata
cip25MetadataFixture4 = Cip25Metadata
[ Cip25MetadataEntry
{ policyId: policyId4
, assetName: Cip25TokenName tokenName4
, name: unsafeMkCip25String "Allium"
, image: "ipfs://k2cwuee3arxg398hwxx6c0iferxitu126xntuzg8t765oo020h5y6npn"
, mediaType: Nothing
, description: Just "From pixabay"
, files: []
}
]

unsafeMkCip25String :: String -> Cip25String
unsafeMkCip25String str = unsafePartial $ fromJust $ mkCip25String str

readJsonFixtureFile :: String -> Effect Aeson
readJsonFixtureFile path =
readTextFile UTF8 path >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson

cip25MetadataJsonFixture1 :: Effect Aeson
cip25MetadataJsonFixture1 =
readTextFile UTF8 "test/Fixtures/cip25MetadataJsonFixture1.json" >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson
readJsonFixtureFile "test/Fixtures/cip25MetadataJsonFixture1.json"

cip25MetadataJsonFixture2 :: Effect Aeson
cip25MetadataJsonFixture2 =
readTextFile UTF8 "test/Fixtures/cip25MetadataJsonFixture2.json" >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson
readJsonFixtureFile "test/Fixtures/cip25MetadataJsonFixture2.json"

cip25MetadataJsonFixture3 :: Effect Aeson
cip25MetadataJsonFixture3 =
readTextFile UTF8 "test/Fixtures/cip25MetadataJsonFixture3.json" >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson
readJsonFixtureFile "test/Fixtures/cip25MetadataJsonFixture3.json"

ogmiosEvaluateTxValidRespFixture :: Effect Aeson
ogmiosEvaluateTxValidRespFixture =
readTextFile UTF8 "test/Fixtures/OgmiosEvaluateTxValidRespFixture.json" >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson
readJsonFixtureFile "test/Fixtures/OgmiosEvaluateTxValidRespFixture.json"

ogmiosEvaluateTxInvalidPointerFormatFixture :: Effect Aeson
ogmiosEvaluateTxInvalidPointerFormatFixture =
readTextFile UTF8
"test/Fixtures/OgmiosEvaluateTxInvalidPointerFormatFixture.json" >>=
pure <<< fromRight aesonNull <<< parseJsonStringToAeson
readJsonFixtureFile
"test/Fixtures/OgmiosEvaluateTxInvalidPointerFormatFixture.json"

redeemerFixture1 :: Redeemer
redeemerFixture1 = Redeemer
Expand Down
7 changes: 7 additions & 0 deletions test/Metadata/Cip25.purs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Test.Ctl.Fixtures
( cip25MetadataFixture1
, cip25MetadataFixture2
, cip25MetadataFixture3
, cip25MetadataFixture4
, cip25MetadataJsonFixture1
, cip25MetadataJsonFixture2
, unsafeMkCip25String
Expand Down Expand Up @@ -84,6 +85,9 @@ suite = do
test "MetadataType instance #3" do
fromGeneralTxMetadata (toGeneralTxMetadata cip25MetadataFixture3)
`shouldEqual` Just cip25MetadataFixture3
test "MetadataType instance #4" do
fromGeneralTxMetadata (toGeneralTxMetadata cip25MetadataFixture4)
`shouldEqual` Just cip25MetadataFixture4
test "FromData / ToData instances #1" do
fromData (toData cip25MetadataFixture1) `shouldEqual`
Just cip25MetadataFixture1
Expand All @@ -93,6 +97,9 @@ suite = do
test "FromData / ToData instances #3" do
fromData (toData cip25MetadataFixture3) `shouldEqual`
Just cip25MetadataFixture3
test "FromData / ToData instances #4" do
fromData (toData cip25MetadataFixture4) `shouldEqual`
Just cip25MetadataFixture4
test "DecodeJson instance #1" do
jsonFixture <- liftEffect cip25MetadataJsonFixture1
decodeAeson jsonFixture `shouldEqual`
Expand Down