Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce hashScriptWithPrefix which produces the ScriptHash corresponding to a SerialisedScript #6527

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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: 1 addition & 2 deletions plutus-ledger-api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<a id='changelog-1.34.0.0'></a>
# 1.34.0.0 — 2024-09-09

Expand Down Expand Up @@ -29,7 +28,7 @@
+ `ExMemory (..)`
+ `SatInt (unSatInt)`
+ `fromSatInt`
+ `toOpaque,
Unisay marked this conversation as resolved.
Show resolved Hide resolved
+ `toOpaque`
+ `fromOpaque`
+ `BuiltinData (..)`
+ `ToData (..)`
Expand Down
47 changes: 47 additions & 0 deletions plutus-ledger-api/changelog.d/20240930_154550_philipdisarro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
-->

<!--
### Removed

- A bullet item for the Removed category.

-->
<!--
### Added

- A bullet item for the Added category.

-->
<!--
### Changed

- A bullet item for the Changed category.

-->
<!--
### Deprecated

- A bullet item for the Deprecated category.

-->
<!--
### Fixed

- A bullet item for the Fixed category.

-->
<!--
### Security

- A bullet item for the Security category.

-->
## Added
colll78 marked this conversation as resolved.
Show resolved Hide resolved

- `hashScriptWithPrefix` to `PlutusLedgerApi.Common.SerialisedScript`.

- Exported `hashScriptWithPrefix` from `PlutusLedgerApi.Common`
4 changes: 4 additions & 0 deletions plutus-ledger-api/plutus-ledger-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ test-suite plutus-ledger-api-test
hs-source-dirs: test
ghc-options: -threaded -rtsopts -with-rtsopts=-N
other-modules:
Spec.ApiUtils
Spec.CBOR.DeserialiseFailureInfo
Spec.ContextDecoding
Spec.CostModelParams
Expand All @@ -172,10 +173,12 @@ test-suite plutus-ledger-api-test

build-depends:
, base >=4.9 && <5
, base16-bytestring
, bytestring
, cborg
, containers
, extra
, filepath
, hedgehog
, lens
, mtl
Expand All @@ -189,6 +192,7 @@ test-suite plutus-ledger-api-test
, tasty-hedgehog
, tasty-hunit
, tasty-quickcheck
, text

-- A suite for tests that use the Plutus Tx plugin. We don't merge those into
-- @plutus-ledger-api-test@, because @plutus-ledger-api@ has to be buildable for older versions of
Expand Down
1 change: 1 addition & 0 deletions plutus-ledger-api/src/PlutusLedgerApi/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module PlutusLedgerApi.Common (
SerialisedScript.uncheckedDeserialiseUPLC,
SerialisedScript.ScriptDecodeError (..),
SerialisedScript.ScriptNamedDeBruijn (..),
SerialisedScript.hashScriptWithPrefix,

-- * Script evaluation
Eval.evaluateScriptCounting,
Expand Down
15 changes: 15 additions & 0 deletions plutus-ledger-api/src/PlutusLedgerApi/Common/SerialisedScript.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ module PlutusLedgerApi.Common.SerialisedScript (
deserialiseScript,
serialisedScript,
deserialisedScript,
hashScriptWithPrefix,
) where

import PlutusCore
import PlutusLedgerApi.Common.Versions
import PlutusTx.Code
import UntypedPlutusCore qualified as UPLC

import PlutusCore.Crypto.Hash qualified as Hash
-- this allows us to safe, 0-cost coerce from FND->ND. Unfortunately, since Coercible is symmetric,
-- we cannot expose this safe Coercible FND ND w.o. also allowing the unsafe Coercible ND FND.
import PlutusCore.DeBruijn.Internal (FakeNamedDeBruijn (FakeNamedDeBruijn))
Expand All @@ -42,10 +44,12 @@ import Control.Lens
import Control.Monad (unless, when)
import Control.Monad.Error.Lens
import Control.Monad.Except (MonadError)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Data.ByteString.Short
import Data.Coerce
import Data.Set as Set
import Data.Word (Word8)
import GHC.Generics
import NoThunks.Class
import Prettyprinter
Expand Down Expand Up @@ -155,6 +159,17 @@ serialiseUPLC =
-- need to be careful about introducing a working version
toShort . BSL.toStrict . serialise . SerialiseViaFlat . UPLC.UnrestrictedProgram

{- | Hash a 'SerialisedScript' with the given version prefix. Each prefix corresponds
to a specific version of the Plutus language.

As of PlutusV3, the Plutus language versions and corresponding prefixes are as follows:
PlutusV1 -> 0x1
PlutusV2 -> 0x2
PlutusV3 -> 0x3
-}
hashScriptWithPrefix :: Word8 -> SerialisedScript -> BS.ByteString
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hashScriptWithPrefix :: Word8 -> SerialisedScript -> BS.ByteString
hashScriptWithPrefix :: PlutusLedgerLanguage -> SerialisedScript -> BS.ByteString

This way one can't pass invalid prefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

-- | Hash a 'SerialisedScript' with the Plutus language version 1 prefix.
hashScriptV1 :: SerialisedScript -> BS.ByteString
hashScriptV1 = hashScriptWithPrefix 0x1 

-- | Hash a 'SerialisedScript' with the Plutus language version 2 prefix.
hashScriptV2 :: SerialisedScript -> BS.ByteString
hashScriptV2 = hashScriptWithPrefix 0x2

-- | Hash a 'SerialisedScript' with the Plutus language version 3 prefix.
hashScriptV3 :: SerialisedScript -> BS.ByteString
hashScriptV3 = hashScriptWithPrefix 0x3

Copy link
Contributor

@Unisay Unisay Oct 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach also makes invalid prefixes un-representable. I wouldn't stop here though:
PlutusLedgerApi.Common.hashScriptV1 move to PlutusLedgerApi.V1.hashScript
PlutusLedgerApi.Common.hashScriptV2 move to PlutusLedgerApi.V2.hashScript
PlutusLedgerApi.Common.hashScriptV3 move to PlutusLedgerApi.V3.hashScript
wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also replace this with the new functions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach also makes invalid prefixes un-representable. I wouldn't stop here though:
PlutusLedgerApi.Common.hashScriptV1 move to PlutusLedgerApi.V1.hashScript
PlutusLedgerApi.Common.hashScriptV2 move to PlutusLedgerApi.V2.hashScript
PlutusLedgerApi.Common.hashScriptV3 move to PlutusLedgerApi.V3.hashScript
wdyt?

Yes and we probably don't need hashScriptWithPrefix at all, you can just copy-paste the body of that definition within each V* file. I'm just nitpicking though, feel free to ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also replace this with the new functions.

This introduces a cyclic dependency between plutus-tx and plutus-ledger-api.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'd do what @effectfully suggests: inline the hasScriptWithPrefix into V1/V2/V3 hashScripts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I want to keep it is because hashScriptWithPrefix as an export is useful to me in creating and testing new language versions that I use in Midgard (optimistic rollup building on Cardano), and it will also be useful for the Hydra team for the same reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a cyclic dependency between plutus-tx and plutus-ledger-api.

Right, so this should really exist in a plutus-script-utils package, which users should depend on, rather than plutus-ledger-api which is intended to be stuff the ledger needs. This is the reason why a number of functions similar to this one was removed from plutus-ledger-api in the first place. This nonetheless can be future work.

hashScriptWithPrefix prefix script = Hash.blake2b_224 $ BS.singleton prefix <> (fromShort script)
colll78 marked this conversation as resolved.
Show resolved Hide resolved

{- | Deserialises a 'SerialisedScript' back into an AST. Does *not* do
ledger-language-version-specific checks like for allowable builtins.
-}
Expand Down
2 changes: 2 additions & 0 deletions plutus-ledger-api/test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import PlutusLedgerApi.Test.V3.EvaluationContext qualified as V3
import PlutusLedgerApi.V1 as V1
import PlutusLedgerApi.V3 as V3
import PlutusPrelude
import Spec.ApiUtils qualified
import Spec.CBOR.DeserialiseFailureInfo qualified
import Spec.ContextDecoding qualified
import Spec.CostModelParams qualified
Expand Down Expand Up @@ -128,6 +129,7 @@ tests = testGroup "plutus-ledger-api"
[ Spec.Interval.tests
, Spec.CBOR.DeserialiseFailureInfo.tests
, Spec.ScriptDecodeError.tests
, Spec.ApiUtils.tests
]
, testGroup "Context-dependent tests"
[ testGroup "Original"
Expand Down
41 changes: 41 additions & 0 deletions plutus-ledger-api/test/Spec/ApiUtils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{-# LANGUAGE OverloadedStrings #-}

module Spec.ApiUtils (tests) where

import Data.ByteString.Base16 qualified as Base16
import Data.ByteString.Short qualified as SBS
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Text.IO qualified as Text
import PlutusLedgerApi.Common
import System.FilePath (joinPath, (</>))
import Test.Tasty
import Test.Tasty.HUnit

path :: [FilePath]
path = ["test", "Spec", "CompiledScripts"]

-- Script bytes and hashes used for the following tests are obtained from real mainnet scripts.
tests :: TestTree
tests = testGroup "Plutus Core language versions"
[ testCase "ScriptHash correct" $ do
let scriptV1Path = joinPath path </> "CompiledValidatorV1.hex"
scriptV2Path = joinPath path </> "CompiledValidatorV2.hex"

compiledV1ScriptInHex <- Text.readFile scriptV1Path
compiledV2ScriptInHex <- Text.readFile scriptV2Path

assertBool "Plutus Language V1 ScriptHash match expected" $
toHex (hashScriptWithPrefix 0x1 (SBS.toShort $ fromHex compiledV1ScriptInHex))
== "232c10966ce4d8e67c2eae17bb57c2cf4854a57509a752fbb3c02bd1"

assertBool "Plutus Language V2 ScriptHash match expected" $
toHex (hashScriptWithPrefix 0x2 (SBS.toShort $ fromHex compiledV2ScriptInHex))
== "fa6a58bbe2d0ff05534431c8e2f0ef2cbdc1602a8456e4b13c8f3077"
]
where
fromHex script =
case (Base16.decode . Text.encodeUtf8 . Text.strip $ script) of
Left _ -> Prelude.error "Failed to decode hex"
Right x -> x
toHex = Text.decodeUtf8 . Base16.encode
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
590a19010000332323232322232323223223232533533300a3333573466e1cd55cea804240004646464246660020080060046eb4d5d09aba25009375a6ae854020dd69aba1500823263533573802001e01c01a6666ae68cdc3a80224004424400446666ae68cdc3a802a40004244002464c6a66ae7004404003c038034cccd5cd19b8735573aa004900011991091980080180119191919191919191919191999ab9a3370e6aae754029200023333333333222222222212333333333300100b00a00900800700600500400300235742a01466a03040026ae854024d5d0a8041aba1500735742a00c6ae854014d5d0a80219a80c3ae35742a0066ae854008d5d09aba2500223263533573803803603403226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226aae7940044dd50009aba150023232323333573466e1d400520062321222230040053232323232323333573466e1d4005200c21222222200323333573466e1d4009200a21222222200423333573466e1d400d2008233221222222233001009008375c6ae854014dd69aba135744a00a46666ae68cdc3a8022400c4664424444444660040120106eb8d5d0a8039bae357426ae89401c8cccd5cd19b875005480108cc8848888888cc018024020c070d5d0a8049bae357426ae8940248cccd5cd19b875006480088c848888888c01c020c074d5d09aab9e500b23333573466e1d401d2000232122222223005008301e357426aae7940308c98d4cd5ce01081000f80f00e80e00d80d00c80c09aab9d5004135573ca00626aae7940084d55cf280089baa001357426aae79400c8cccd5cd19b875002480108c848888c008014c048d5d09aab9e500423333573466e1d400d20022321222230010053232323333573466e1cd55cea8012400046644246600200600464646666ae68cdc39aab9d5001480008dd71aba135573ca004464c6a66ae7007407006c0684dd50009aba15002375a6ae84d5d1280111931a99ab9c01a019018017135573ca00226ea8004d5d09aab9e500523333573466e1d40112000232122223003005375c6ae84d55cf280311931a99ab9c017016015014013012011135573aa00226ea8004d5d09aba2500223263533573802001e01c01a201c264c6a66ae71241035054350000e00d135573ca00226ea80044d55ce9baa001135744a00226aae7940044dd50008919118011bac00132001323001001223233335573e00442440044664424466002008006600a6ae8400cc008d5d100180398010011191919191999ab9a3370ea002900111999110911998008028020019bad35742a0086eb4d5d0a8019bad357426ae89400c8cccd5cd19b875002480008c8488c00800cc8c8c8cccd5cd19b875001480088c8488c00400cdd71aba135573ca00646666ae68cdc3a8012400046424460040066eb8d5d09aab9e500423263533573801e01c01a01801626aae7540044dd50009aba135573ca00c464c6a66ae7002802402001c0184d55cea80189aba25001135573ca00226ea8005261200149010350543100332332233223232323232323232323222232322300235323232323253353333333574800a46666ae68cdc39aab9d5005480008cccd55cfa8029280d91999aab9f50052501c233335573ea00a4a03a46666aae7cd5d128031299a991919191999999aba400423333573466e1cd55cea8022400046666aae7d4010940948cccd55cfa8021281311999aab9f35744a00a4a66a603e6ae85401c854cd4cd407c8c8c8c8c8c8ccccccd5d200311999ab9a3370ea004900111999aab9f500625031233335573ea00c4a06446666aae7d4018940cc8cccd55cf9aba2500725335302a35742a01442a66a60566ae854028854cd4c0b0d5d0a805109a81c0911998008028020018a81b0a81a8a81a1281a01801781701691999ab9a3370ea006900011999aab9f500725032233335573e6ae89402094cd4c0acd5d0a804909a81a89118010018a81992819817817128188160159281792817928179281781589aab9d5004135744a00226ae8940044d55cf280089baa00135742a00e426a05424660020060042a0502a04e4a04e0460440424a04803e4a0464a0464a0464a04603e26ae8940044d55cf280089baa00135742a01242a66a666aa02603066aa02603002a6ae854024854cd4cd405c064d5d0a804909a811091998008020018010a8100a80f8a80f1280f00d00c80c00b9280d00a9280c9280c9280c9280c80a9080089ab1309aba25001135744a00226aae7940044dd500099a9806890009a800911a8011111111111004a4004444004640026aa02644a66a00220224426a00444a66a666ae68cdc780100380b00a880b098030019a80191111111a8039100108911911999999aba4001550052533530033756004426a0260022a022aa00aaa00aaa00a01a640026aa02044646666aae7c0088d404c48800894cd4c018d55cea80110a99a98031aab9e500321533530063574400a426a02c24466002246600200c00a0062a0282a0262a02401c26ae8400444940308ccccccd5d200092806128061280611a8069bad0022500c0081223232323333333574800846666ae68cdc3a8012400046666aae7d4010940448cccd55cf9aba2500525335300935742a00c426a0286a0280022a0244a02401c01a46666ae68cdc3a801a400446666aae7d40148d404d4048940480389404403002c9403c9403c9403c9403c02c4d55cea80109aab9e50011375400246464646666666ae900108cccd5cd19b875002480088cccd55cfa8021280791999aab9f35744a00a4a66a60126ae85401884d4048488c00400c540409404003002c8cccd5cd19b875003480008cccd55cfa8029280811999aab9f35744a00c4a66a60146ae85401c84d404c488c00800c54044940440340309403c028024940349403494034940340244d55cea80109aab9e50011375400246666666ae90004940249402494024940248d4028dd7001002990009aa80411091299a999ab9a33710002900000480409a802a481035054360015335002135005490103505437002215335333573466e1c00d200000b00a10021335300612001001337020069001091931a99ab9c0010030024984800448800848800448488c00800c4488004448c8c00400488cc00cc008008004cd4488ccd44888cd4488cd4488ccccccc008cd540112211c4d07e0ceae00e6c53598cea00a53c54a94c6b6aa071482244cc0adb50048810f567946695f43726564656e7469616c0033550044891c96c31772282e6ae5c629120471c5bbcdef538226b31b97d74c50ca3c00488100488110567946695f4144412f534e454b5f4c500033300948303df9c052014480a0cd540112210048810033550044891c279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f00488104534e454b00350074891ce023d4ef74967fc841a5e77fe82e41a1b20f0c94de4e2d9d33a2aabe0022222221233333330010080070060050040030022001112212330010030021120011212230020031122001120012221233300100400300220011
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its unfortunate that we have to include an opaque test fixture: I'd rather prefer to include sources of the scripts and compile them with plugin together with tests. However, tests for the plutus-ledger-api dont use plugin, so, maybe, you can consider placing tests in the:

-- A suite for tests that use the Plutus Tx plugin. We don't merge those into
-- @plutus-ledger-api-test@, because @plutus-ledger-api@ has to be buildable for older versions of
-- GHC (a requirement imposed by @cardano-node@) and while its tests don't have to, we don't want to
-- give up on all @plutus-ledger-api@ tests for older versions of GHC.
test-suite plutus-ledger-api-plugin-test

?

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5909a201000033232323232323223222323232253330093232533300b3005300c375400e264646464646466664444646600200200a4464a6660306026002264646600200201044a66603c00229404c94ccc070cdc79bae302100200414a226600600600260420026eb8c074c068dd50010a99980c1809000899198008009bac301e301b375400644a66603a00229444c94ccc06ccc018018c0800084cc00c00c00452818100008a99980c1806800899198008009bac301e301b375400644a66603a00229404c94ccc06ccc018018c08000852889980180180098100008a99980c180600089919b89375a603c002646660020026eb0c07cc0800092000222533301f002100113330030033022002533301c33007007302100213370000290010800980d1baa00215333018300b00113232533301a3014301b3754002264a66603664a66603e603c0022a666038602c603a002294454ccc070c05cc0740045280b0b1baa300b301d37546016603a6ea80204cdc4800801899b88001003375a603e60386ea80045281807980d9baa3009301b375400c6eb4c074c068dd50010a99980c180500089919299980d180a180d9baa001132533301b32533301f301e0011533301c3016301d00114a22a666038602e603a00229405858dd51805980e9baa3011301d3754010266e2400c0044cdc40018009bad301f301c37540022940c03cc06cdd51807980d9baa006375a603a60346ea80084c8c8cc004004018894ccc078004528099299980e19baf004301d302100214a2266006006002604200266e9520003301c3374a90011980e180e980d1baa0024bd7025eb80c060dd5000980098099baa00e3758602c602e602e602e602e602e602e602e602e60266ea8c01cc04cdd5004980b180b980b980b980b980b980b980b98099baa3007301337540126eacc020c04cdd5180398099baa009230163017001323232325333013300e301437540202646464646464646464646464a666044604a00426464646493192999811980f000899192999814181580109924c64a66604c604200226464a666056605c0042930b1bae302c001302837540042a66604c604000226464a666056605c0042930b1bae302c001302837540042c604c6ea800458c0a4004c094dd50038a999811980e800899191919299981518168010991924c6464646464a66606060660042930b1bad30310013031002375c605e002605e0066eb8c0b4008c8c8c8c8c94ccc0bcc0c800852616375a606000260600046eb8c0b8004c0b8010dd718160018b1bac302b001302b00237586052002604a6ea801c54ccc08cc0600044c8c94ccc0a0c0ac0084c926323232323232323253330303033002149858dd6981880098188011bae302f001302f003375c605a0046464646464a66605e60640042930b1bad30300013030002375c605c002605c0066eb8c0b0008dd618140011bac302600116325333028302b302b0011337606054002605460560022c6eb0c0a4004c094dd50038a999811980b800899192999814181580109924c6464646464a66605a60600042930b1bad302e001302e002375c605800260580046eb8c0a800458dd6181480098129baa007153330233016001132325333028302b002132498c8c8c8c8c8c8c8c94ccc0c0c0cc00852616375a606200260620046eb8c0bc004c0bc00cdd718168011919191919299981798190010a4c2c6eb4c0c0004c0c0008dd7181700098170019bae302c002375860500046eb0c09800458c94ccc0a0c0acc0ac0044cdd81815000981518158008b1bac30290013025375400e2a666046602a00226464a666050605600426493191bae3028002375c604c0022c64a66605060566056002266ec0c0a8004c0a8c0ac00458dd6181480098129baa007163023375400c64a666044603a002264646464a6660526058004264649319299981418118008a99981598151baa00314985854ccc0a0c0880044c8c94ccc0b4c0c000852616375c605c00260546ea800c54ccc0a0c0740044c8c94ccc0b4c0c000852616302e001302a37540062c60506ea80094ccc098c084c09cdd5001899191919299981698180010991924c64a666058604e00226464a666062606800426493192999817981500089919299981a181b80109924c60440022c606a00260626ea800854ccc0bcc0a40044c8c8c8c8c8c94ccc0e0c0ec00852616375a607200260720046eb4c0dc004c0dc008dd6981a80098189baa00216302f37540022c6064002605c6ea800c54ccc0b0c09800454ccc0bcc0b8dd50018a4c2c2c60586ea8008c06c00c58c0b8004c0b8008c0b0004c0a0dd50018b0b18150009815001181400098121baa00815333022301c00115333025302437540102930b0b18111baa007300e00a325333020301b0011323253330253028002149858dd7181300098111baa00c15333020301a00115333023302237540182930b0b18101baa00b163023001302300230210013021002301f001301f002375a603a002603a004603600260360046032002602a6ea804058c00400488c94ccc050c03c0044c8c94ccc064c07000852616375c6034002602c6ea800854ccc050c0380044c8c94ccc064c0700084c926330060012330060060011637586034002602c6ea800854ccc050c0240044c8c94ccc064c0700084c926330060012330060060011637586034002602c6ea800854ccc050c0200044c8c8c8c94ccc06cc0780084c92633008001233008008001163758603800260380046eb4c068004c058dd50010a99980a180380089919299980c980e0010a4c2c6eb4c068004c058dd50010a99980a180300089919299980c980e0010a4c2c6eb4c068004c058dd50010a99980a19b87480300044c8c94ccc064c07000852616375c6034002602c6ea800858c050dd500091191980080080191299980b8008a4c26466006006603600460066032002464a666022601800226464a66602c60320042930b1bae3017001301337540042a666022601600226464a66602c60320042930b1bae3017001301337540042c60226ea8004dc3a40146e1d2008370e90031b87480104c8ccc004004dd5980198071baa3002300e37540089408894ccc04400840044c8ccc010010c05400ccc88c94ccc048c034c04cdd500189929998099806980a1baa001132533301400714a2266e3c004048dd7180c180a9baa001002301730143754006002200860200026eb4c044004c04c0088c0400048c03cc040c040c040c040c040c0400045261365632533300830030011533300b300a37540082930b0a99980418010008a99980598051baa00414985858c020dd50019b8748008dc3a40006eb80055cd2ab9d5573caae7d5d02ba1574498011e581c99e5aacf401fed0eb0e2993d72d423947f42342e8f848353d03efe610001