Skip to content

Commit

Permalink
Reference scripts impl - publication of a script (#4086)
Browse files Browse the repository at this point in the history
<!--
Detail in a few bullet points the work accomplished in this PR.

Before you submit, don't forget to:

* Make sure the GitHub PR fields are correct:
   ✓ Set a good Title for your PR.
   ✓ Assign yourself to the PR.
   ✓ Assign one or more reviewer(s).
   ✓ Link to a Jira issue, and/or other GitHub issues or PRs.
   ✓ In the PR description delete any empty sections
     and all text commented in <!--, so that this text does not appear
     in merge commit messages.

* Don't waste reviewers' time:
   ✓ If it's a draft, select the Create Draft PR option.
✓ Self-review your changes to make sure nothing unexpected slipped
through.

* Try to make your intent clear:
   ✓ Write a good Description that explains what this PR is meant to do.
   ✓ Jira will detect and link to this PR once created, but you can also
     link this PR in the description of the corresponding Jira ticket.
   ✓ Highlight what Testing you have done.
   ✓ Acknowledge any changes required to the Documentation.
-->

- [x] logic in `constructTransaction` on api and `Cardano.Wallet` level
- [x] extension of `TransactionCtx`
- [x] use the added field in `mkUnsignedTx`
- [x] fix `toCardanoTxOut`
- [x] adjust fee when reference script is present
- [x] integration test showing reference script publication

### Comments

PR tackles publication of reference script to be used in later
transactions by using reference inputs when minting/burning.
Script is propagated in blockchain in the first of TxOut of a given
transaction. It is shown in integration tests that
construction/signing/submitting of such a transaction works, plus when
decoding transaction a script is present in witness count as expected.

<!-- Additional comments, links, or screenshots to attach, if any. -->

### Issue Number
https://cardanofoundation.atlassian.net/browse/ADP-3090

<!-- Reference the Jira/GitHub issue that this PR relates to, and which
requirements it tackles.
  Note: Jira issues of the form ADP- will be auto-linked. -->
  • Loading branch information
paweljakubas authored Sep 12, 2023
2 parents ba21ca0 + baa6679 commit d6ef2b1
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 60 deletions.
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
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

0 comments on commit d6ef2b1

Please sign in to comment.