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

Reference scripts impl - publication of a script #4086

Merged
merged 14 commits into from
Sep 12, 2023
65 changes: 49 additions & 16 deletions lib/wallet/api/http/Cardano/Wallet/Api/Http/Shelley/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import Cardano.Address.Script
( Cosigner (..)
, KeyHash (KeyHash)
, KeyRole (..)
, Script
, ScriptTemplate (..)
, ValidationLevel (..)
, foldScript
Expand Down Expand Up @@ -243,7 +244,7 @@ import Cardano.Wallet.Address.Discovery.Shared
import Cardano.Wallet.Address.HasDelegation
( HasDelegation (..) )
import Cardano.Wallet.Address.Keys.MintBurn
( toTokenMapAndScript, toTokenPolicyId )
( replaceCosigner, toTokenMapAndScript, toTokenPolicyId )
import Cardano.Wallet.Address.Keys.SequentialAny
( mkSeqStateFromRootXPrv )
import Cardano.Wallet.Address.Keys.Shared
Expand Down Expand Up @@ -719,7 +720,6 @@ import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Handler.WarpTLS as Warp



-- | Allow configuring which port the wallet server listen to in an integration
-- setup. Crashes if the variable is not a number.
walletListenFromEnv :: Show e
Expand Down Expand Up @@ -2462,12 +2462,12 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
mintBurnData <-
liftHandler $ except $ parseMintBurnData body validityInterval

mintBurnReferenceScriptTemplate <-
liftHandler $ except $ parseReferenceScript body

delegationRequest <-
liftHandler $ traverse parseDelegationRequest $ body ^. #delegations

when (isJust $ body ^. #referencePolicyScriptTemplate) $
liftHandler $ throwE ErrConstructTxNotImplemented

let metadata =
body ^? #metadata . traverse . #txMetadataWithSchema_metadata

Expand Down Expand Up @@ -2507,10 +2507,11 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
Just action ->
transactionCtx0 { txDelegationAction = Just action }

(transactionCtx2, policyXPubM) <-
(policyXPub, _) <-
liftHandler $ W.readPolicyPublicKey wrk

transactionCtx2 <-
if isJust mintBurnData then do
(policyXPub, _) <-
liftHandler $ W.readPolicyPublicKey wrk
let isMinting (ApiMintBurnDataFromScript _ _ (ApiMint _)) = True
isMinting _ = False
let getMinting = \case
Expand Down Expand Up @@ -2551,13 +2552,22 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
map getBurning $
filter (not . isMinting) $
NE.toList $ fromJust mintBurnData
pure ( transactionCtx1
pure transactionCtx1
{ txAssetsToMint = mintingData
, txAssetsToBurn = burningData
}
, Just policyXPub)
else
pure (transactionCtx1, Nothing)
pure transactionCtx1

let referenceScriptM =
replaceCosigner
ShelleyKeyS
(Map.singleton (Cosigner 0) policyXPub)
<$> mintBurnReferenceScriptTemplate

let transactionCtx3 = transactionCtx2
{ txReferenceScript = referenceScriptM
}

outs <- case body ^. #payments of
Nothing -> pure []
Expand All @@ -2571,14 +2581,14 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
let mintingOuts = case mintBurnData of
Just mintBurns ->
coalesceTokensPerAddr $
map (toMintTxOut (fromJust policyXPubM)) $
map (toMintTxOut policyXPub) $
filter mintWithAddress $
NE.toList mintBurns
Nothing -> []

unbalancedTx <- liftHandler $
W.constructTransaction @n @'CredFromKeyK @era
txLayer db transactionCtx2
txLayer db transactionCtx3
PreSelection { outputs = outs <> mintingOuts }

balancedTx <-
Expand All @@ -2598,11 +2608,11 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d

(_, _, rewardPath) <- handler $ W.readRewardAccount @s db

let deposits = case txDelegationAction transactionCtx2 of
let deposits = case txDelegationAction transactionCtx3 of
Just (JoinRegisteringKey _poolId) -> [W.getStakeKeyDeposit pp]
_ -> []

let refunds = case txDelegationAction transactionCtx2 of
let refunds = case txDelegationAction transactionCtx3 of
Just Quit -> [W.getStakeKeyDeposit pp]
_ -> []

Expand All @@ -2611,7 +2621,7 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
, coinSelection = mkApiCoinSelection
deposits
refunds
((,rewardPath) <$> transactionCtx2 ^. #txDelegationAction)
((,rewardPath) <$> transactionCtx3 ^. #txDelegationAction)
metadata
(unsignedTx rewardPath (outs ++ mintingOuts) apiDecoded)
, fee = apiDecoded ^. #fee
Expand All @@ -2622,6 +2632,29 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d

walletId = getApiT apiWalletId

parseReferenceScript
:: ApiConstructTransactionData n
-> Either ErrConstructTx (Maybe (Script Cosigner))
parseReferenceScript tx = do
let mbRefScript = tx ^. #referencePolicyScriptTemplate
for mbRefScript $ \(ApiT refScript) -> do
guardWrongScriptTemplate refScript
Right refScript
where
guardWrongScriptTemplate
:: Script Cosigner -> Either ErrConstructTx ()
guardWrongScriptTemplate apiScript =
when (wrongMintingTemplate apiScript)
$ Left ErrConstructTxWrongMintingBurningTemplate
where
wrongMintingTemplate script =
isLeft (validateScriptOfTemplate RecommendedValidation script)
|| countCosigners script /= (1 :: Int)
|| existsNonZeroCosigner script
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these conditions checked? According to the specification (swagger.yaml), any script template should be acceptable. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

script template allows more than one cosigner in general. But here we are using script for minting/burning in SHELLEY (not in shared style) so we need to make sure one cosigner is present.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's alright with me — all I'm asking for is that this fact is highlighted in the specification. 🤓📓 Could you amend the documents mint-burn.md and swagger.yaml to describe the allowed values for reference_policy_script_template?

(By definition, if the implementation is different from the specification, that's a bug.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, ok. done

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding a description of the restriction to mint-burn.md — could you add it to swagger.yaml as well?

countCosigners = foldScript (const (+ 1)) 0
existsNonZeroCosigner =
foldScript (\cosigner a -> a || cosigner /= Cosigner 0) False

parseMintBurnData
:: ApiConstructTransactionData n
-> (SlotNo, SlotNo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ module Test.Integration.Scenario.API.Shared.Transactions

import Prelude

import Cardano.Address.Script
( KeyHash (..), Script (..) )
import Cardano.Mnemonic
( MkSomeMnemonic (..) )
import Cardano.Wallet.Address.Derivation
Expand Down Expand Up @@ -78,7 +76,11 @@ import Cardano.Wallet.Primitive.Types.Tx
import Cardano.Wallet.Primitive.Types.Tx.TxMeta
( Direction (..), TxStatus (..) )
import Cardano.Wallet.Transaction
( AnyExplicitScript (..), ScriptReference (..), WitnessCount (..) )
( AnyExplicitScript (..)
, ScriptReference (..)
, WitnessCount (..)
, changeRoleInAnyExplicitScript
)
import Control.Monad
( forM_ )
import Control.Monad.IO.Unlift
Expand Down Expand Up @@ -926,7 +928,7 @@ spec = describe "SHARED_TRANSACTIONS" $ do
-- it only is aware of its policy verification key
let noVerKeyWitnessHex = mkApiWitnessCount WitnessCount
{ verificationKey = 0
, scripts = [changeRole CA.Unknown paymentScript]
, scripts = [changeRoleInAnyExplicitScript CA.Unknown paymentScript]
, bootstrap = 0
}
let witsExp1hex =
Expand Down Expand Up @@ -964,7 +966,7 @@ spec = describe "SHARED_TRANSACTIONS" $ do
-- it only is aware of its policy verification key
let oneVerKeyWitnessHex = mkApiWitnessCount WitnessCount
{ verificationKey = 1
, scripts = [changeRole CA.Unknown paymentScript]
, scripts = [changeRoleInAnyExplicitScript CA.Unknown paymentScript]
, bootstrap = 0
}
let witsExp2hex =
Expand Down Expand Up @@ -3301,22 +3303,3 @@ spec = describe "SHARED_TRANSACTIONS" $ do
(#balance . #available . #getQuantity)
(`shouldBe` amt)
]

changeRole :: CA.KeyRole -> AnyExplicitScript -> AnyExplicitScript
changeRole role = \case
NativeExplicitScript script scriptRole ->
let changeRole' = \case
RequireSignatureOf (KeyHash _ p) ->
RequireSignatureOf $ KeyHash role p
RequireAllOf xs ->
RequireAllOf (map changeRole' xs)
RequireAnyOf xs ->
RequireAnyOf (map changeRole' xs)
RequireSomeOf m xs ->
RequireSomeOf m (map changeRole' xs)
ActiveFromSlot s ->
ActiveFromSlot s
ActiveUntilSlot s ->
ActiveUntilSlot s
in NativeExplicitScript (changeRole' script) scriptRole
PlutusExplicitScript _ _ -> error "wrong usage"
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import Cardano.Wallet.Transaction
, ScriptReference (..)
, ValidityIntervalExplicit (..)
, WitnessCount (..)
, changeRoleInAnyExplicitScript
)
import Cardano.Wallet.Unsafe
( unsafeFromHex, unsafeMkMnemonic )
Expand Down Expand Up @@ -228,6 +229,7 @@ import Test.Integration.Framework.TestData
import UnliftIO.Exception
( fromEither )

import qualified Cardano.Address.Script as CA
import qualified Cardano.Api as Cardano
import qualified Cardano.Ledger.Keys as Ledger
import qualified Cardano.Wallet.Address.Derivation.Shelley as Shelley
Expand Down Expand Up @@ -1148,6 +1150,76 @@ spec = describe "NEW_SHELLEY_TRANSACTIONS" $ do
, expectErrorMessage errMsg403NotEnoughMoney
]

it "TRANS_NEW_ASSETS_CREATE_02 - using reference script" $ \ctx -> runResourceT $ do

let initialAmt = 1_000_000_000
wa <- fixtureWalletWith @n ctx [initialAmt]
wb <- emptyWallet ctx
let amt = 10_000_000 :: Natural

let policyWithHash = Link.getPolicyKey @'Shelley wa (Just True)
(_, policyKeyHashPayload) <-
unsafeRequest @ApiPolicyKey ctx policyWithHash Empty
let (Just policyKeyHash) =
keyHashFromBytes (Policy, getApiPolicyKey policyKeyHashPayload)
let scriptUsed = RequireAllOf
[ RequireSignatureOf policyKeyHash
]

addrs <- listAddresses @n ctx wb
let destination = (addrs !! 1) ^. #id
let payload = Json [json|{
"reference_policy_script_template":
{ "all": [ "cosigner#0" ] },
"payments": [{
"address": #{destination},
"amount": {
"quantity": #{amt},
"unit": "lovelace"
}
}]
}|]

let expectedCreateTx =
[ expectSuccess
, expectResponseCode HTTP.status202
, expectField (#coinSelection . #inputs) (`shouldSatisfy` (not . null))
, expectField (#coinSelection . #outputs) (`shouldSatisfy` (not . null))
, expectField (#coinSelection . #change) (`shouldSatisfy` (not . null))
, expectField (#fee . #getQuantity) (`shouldSatisfy` (> 0))
]

rTx <- request @(ApiConstructTransaction n) ctx
(Link.createUnsignedTransaction @'Shelley wa) Default payload
verify rTx expectedCreateTx

let (ApiSerialisedTransaction apiTx _) = getFromResponse #transaction rTx

signedTx <- signTx ctx wa apiTx [ expectResponseCode HTTP.status202 ]

submittedTx <- submitTxWithWid ctx wa signedTx
verify submittedTx
[ expectSuccess
, expectResponseCode HTTP.status202
]

let (ApiT txId) = getFromResponse #id submittedTx
let refInp = ReferenceInput $ TxIn txId 0
let referenceScript = NativeExplicitScript scriptUsed (ViaReferenceInput refInp)
let witnessCountWithNativeScript = mkApiWitnessCount WitnessCount
{ verificationKey = 1
, scripts = [changeRoleInAnyExplicitScript CA.Unknown referenceScript]
, bootstrap = 0
}

let decodePayload = Json (toJSON signedTx)
rTx1 <- request @(ApiDecodedTransaction n) ctx
(Link.decodeTransaction @'Shelley wa) Default decodePayload
verify rTx1
[ expectResponseCode HTTP.status202
, expectField (#witnessCount) (`shouldBe` witnessCountWithNativeScript)
]

it "TRANS_NEW_VALIDITY_INTERVAL_01a - \
\Validity interval with second" $
\ctx -> runResourceT $ do
Expand Down
1 change: 1 addition & 0 deletions lib/wallet/src/Cardano/Wallet/Address/Keys/MintBurn.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Cardano.Wallet.Address.Keys.MintBurn
( derivePolicyKeyAndHash
, toTokenMapAndScript
, toTokenPolicyId
, replaceCosigner
) where

import Prelude
Expand Down
30 changes: 24 additions & 6 deletions lib/wallet/src/Cardano/Wallet/Shelley/Compatibility.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1297,13 +1297,17 @@ toCardanoLovelace (W.Coin c) = Cardano.Lovelace $ intCast c
toCardanoUTxO :: ShelleyBasedEra era -> W.UTxO -> Cardano.UTxO era
toCardanoUTxO era = Cardano.UTxO
. Map.fromList
. map (bimap toCardanoTxIn (toCardanoTxOut era))
. map (bimap toCardanoTxIn (toCardanoTxOut era Nothing))
. Map.toList
. W.unUTxO

toCardanoTxOut :: HasCallStack
=> ShelleyBasedEra era -> W.TxOut -> Cardano.TxOut ctx era
toCardanoTxOut era = case era of
toCardanoTxOut
:: HasCallStack
=> ShelleyBasedEra era
-> Maybe (Script KeyHash)
-> W.TxOut
-> Cardano.TxOut ctx era
toCardanoTxOut era refScriptM = case era of
ShelleyBasedEraShelley -> toShelleyTxOut
ShelleyBasedEraAllegra -> toAllegraTxOut
ShelleyBasedEraMary -> toMaryTxOut
Expand Down Expand Up @@ -1402,7 +1406,14 @@ toCardanoTxOut era = case era of
datumHash
refScript
where
refScript = Cardano.ReferenceScriptNone
refScript = case refScriptM of
Nothing ->
Cardano.ReferenceScriptNone
Just script ->
let aux = Cardano.ReferenceTxInsScriptsInlineDatumsInBabbageEra
scriptApi = Cardano.toScriptInAnyLang $ Cardano.SimpleScript $
toCardanoSimpleScript script
in Cardano.ReferenceScript aux scriptApi
datumHash = Cardano.TxOutDatumNone
addrInEra = tina "toCardanoTxOut: malformed address"
[ Cardano.AddressInEra
Expand All @@ -1423,7 +1434,14 @@ toCardanoTxOut era = case era of
datumHash
refScript
where
refScript = Cardano.ReferenceScriptNone
refScript = case refScriptM of
Nothing ->
Cardano.ReferenceScriptNone
Just script ->
let aux = Cardano.ReferenceTxInsScriptsInlineDatumsInConwayEra
scriptApi = Cardano.toScriptInAnyLang $ Cardano.SimpleScript $
toCardanoSimpleScript script
in Cardano.ReferenceScript aux scriptApi
datumHash = Cardano.TxOutDatumNone
addrInEra = tina "toCardanoTxOut: malformed address"
[ Cardano.AddressInEra
Expand Down
Loading
Loading