Skip to content

Commit

Permalink
Use reference input (#4114)
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] update `ApiMinBurnFromInput` to accommodate policy id
- [x] update swagger and spec
- [x] regenerate golden and make sure unit tests pass
- [x] incorporate new `ApiMinBurnData` inside `constructTransaction`
- [x] introduce ScriptSource
- [x] handle from input case to `mkUnsignedTx`
- [x] adjust `mkUnsignedTx`
- [x] show the case in integration testing`

### Comments

builds on top of
#4086

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

### Issue Number
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 18, 2023
2 parents 8ae7966 + 9498a85 commit 6157932
Show file tree
Hide file tree
Showing 11 changed files with 3,262 additions and 4,341 deletions.
14 changes: 13 additions & 1 deletion lib/balance-tx/lib/Cardano/Wallet/Write/Tx/Sign.hs
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,24 @@ estimateKeyWitnessCount utxo txbody@(Cardano.TxBody txbodycontent) =
fromIntegral
$ sumVia estimateMaxWitnessRequiredPerInput
$ mapMaybe toTimelockScript scripts
-- when wallets uses reference input it means script containing
-- its policy key was already published in previous tx
-- if so we need to add one witness that will stem from policy signing
-- key. As it is not allowed to publish and consume in the same transaction
-- we are not going to double count.
txRefInpsWit = case Cardano.txInsReference txbodycontent of
Cardano.TxInsReferenceNone -> 0
Cardano.TxInsReference{} ->
case Cardano.txMintValue txbodycontent of
Cardano.TxMintNone -> 0
Cardano.TxMintValue{} -> 1
nonInputWits = numberOfShelleyWitnesses $ fromIntegral $
length txExtraKeyWits' +
length txWithdrawals' +
txUpdateProposal' +
fromIntegral txCerts +
scriptVkWitsUpperBound
scriptVkWitsUpperBound +
txRefInpsWit
inputWits = KeyWitnessCount
{ nKeyWits = fromIntegral
. length
Expand Down
188 changes: 113 additions & 75 deletions lib/wallet/api/http/Cardano/Wallet/Api/Http/Shelley/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ import Cardano.Wallet.Api.Types
, ApiForeignStakeKey (..)
, ApiIncompleteSharedWallet (..)
, ApiMintBurnData (..)
, ApiMintBurnDataFromInput (..)
, ApiMintBurnDataFromScript (..)
, ApiMintBurnOperation (..)
, ApiMintData (..)
Expand Down Expand Up @@ -591,7 +592,7 @@ import Data.Coerce
import Data.Either
( isLeft, isRight )
import Data.Either.Extra
( eitherToMaybe, fromLeft' )
( eitherToMaybe )
import Data.Function
( (&) )
import Data.Functor
Expand Down Expand Up @@ -2449,7 +2450,7 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
validityInterval <-
liftHandler $ parseValidityInterval ti $ body ^. #validityInterval

mintBurnData <-
mintBurnDatum <-
liftHandler $ except $ parseMintBurnData body validityInterval

mintBurnReferenceScriptTemplate <-
Expand Down Expand Up @@ -2501,47 +2502,65 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
liftHandler $ W.readPolicyPublicKey wrk

transactionCtx2 <-
if isJust mintBurnData then do
let isMinting (ApiMintBurnDataFromScript _ _ (ApiMint _)) = True
isMinting _ = False
let getMinting = \case
ApiMintBurnDataFromScript
if isJust mintBurnDatum then do
let isMinting mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript _ _ (ApiMint _)) -> True
Right (ApiMintBurnDataFromInput _ _ _ (ApiMint _)) -> True
_ -> False

makeLeft (a,t,s) = (a,t, Left s)
getMinting mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript
(ApiT scriptT)
(Just (ApiT tName))
(ApiMint (ApiMintData _ amt)) ->
(ApiMint (ApiMintData _ amt))) ->
makeLeft $
toTokenMapAndScript ShelleyKeyS
scriptT
(Map.singleton (Cosigner 0) policyXPub)
tName
amt
Right (ApiMintBurnDataFromInput
refInp
(ApiT policyId)
(Just (ApiT tName))
(ApiMint (ApiMintData _ amt))) ->
(AssetId policyId tName, TokenQuantity amt, Right refInp)
_ -> error "getMinting should not be used in this way"
let getBurning = \case
ApiMintBurnDataFromScript
getBurning mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript
(ApiT scriptT)
(Just (ApiT tName))
(ApiBurn (ApiBurnData amt)) ->
(ApiBurn (ApiBurnData amt))) ->
makeLeft $
toTokenMapAndScript ShelleyKeyS
scriptT
(Map.singleton (Cosigner 0) policyXPub)
tName
amt
Right (ApiMintBurnDataFromInput
refInp
(ApiT policyId)
(Just (ApiT tName))
(ApiBurn (ApiBurnData amt))) ->
(AssetId policyId tName, TokenQuantity amt, Right refInp)
_ -> error "getBurning should not be used in this way"
let toTokenMap =
toTokenMap =
fromFlatList .
map (\(a,q,_) -> (a,q))
let toScriptTemplateMap =
toScriptTemplateMap =
Map.fromList .
map (\(a,_,s) -> (a,s))
let mintingData =
mintingData =
toTokenMap &&& toScriptTemplateMap $
map getMinting $
filter isMinting $
NE.toList $ fromJust mintBurnData
let burningData =
NE.toList $ fromJust mintBurnDatum
burningData =
toTokenMap &&& toScriptTemplateMap $
map getBurning $
filter (not . isMinting) $
NE.toList $ fromJust mintBurnData
NE.toList $ fromJust mintBurnDatum
pure transactionCtx1
{ txAssetsToMint = mintingData
, txAssetsToBurn = burningData
Expand All @@ -2564,11 +2583,13 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
Just (ApiPaymentAddresses content) ->
pure $ F.toList (addressAmountToTxOut <$> content)

let mintWithAddress
(ApiMintBurnDataFromScript _ _ (ApiMint (ApiMintData (Just _) _)))
= True
mintWithAddress _ = False
let mintingOuts = case mintBurnData of
let mintWithAddress mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript _ _ (ApiMint (ApiMintData (Just _) _))) ->
True
Right (ApiMintBurnDataFromInput _ _ _ (ApiMint (ApiMintData (Just _) _))) ->
True
_ -> False
let mintingOuts = case mintBurnDatum of
Just mintBurns ->
coalesceTokensPerAddr $
map (toMintTxOut policyXPub) $
Expand Down Expand Up @@ -2648,83 +2669,93 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
parseMintBurnData
:: ApiConstructTransactionData n
-> (SlotNo, SlotNo)
-> Either ErrConstructTx (Maybe (NonEmpty (ApiMintBurnDataFromScript n)))
-> Either ErrConstructTx (Maybe (NonEmpty (ApiMintBurnData n)))
parseMintBurnData tx validity = do
when (notAllFromScript (tx ^. #mintBurn)) $
Left ErrConstructTxNotImplemented
let mbMintingBurning :: Maybe (NonEmpty (ApiMintBurnDataFromScript n))
let mbMintingBurning :: Maybe (NonEmpty (ApiMintBurnData n))
mbMintingBurning =
fmap (handleMissingAssetName . takeMintingFromScript)
<$> tx ^. #mintBurn
fmap handleMissingAssetName <$> tx ^. #mintBurn
for mbMintingBurning $ \mintBurnData -> do
guardWrongMintingTemplate mintBurnData
guardAssetNameTooLong mintBurnData
guardAssetQuantityOutOfBounds mintBurnData
guardOutsideValidityInterval validity mintBurnData
Right mintBurnData
where
notAllFromScript = \case
Nothing -> False
Just mintData ->
any isRight $ mintBurnData <$> NE.toList mintData

-- we checked that only left are present in preceding line
takeMintingFromScript (ApiMintBurnData mintData) =
fromLeft' mintData

handleMissingAssetName :: ApiMintBurnDataFromScript n -> ApiMintBurnDataFromScript n
handleMissingAssetName mb = case mb ^. #assetName of
Nothing -> mb {assetName = Just (ApiT nullTokenName)}
Just _ -> mb
handleMissingAssetName :: ApiMintBurnData n -> ApiMintBurnData n
handleMissingAssetName mb = case mb ^. #mintBurnData of
Left fromScript -> ApiMintBurnData $ Left $
updateFromScript fromScript
Right fromInp -> ApiMintBurnData $ Right $
updateFromInp fromInp
where
updateFromScript :: ApiMintBurnDataFromScript n -> ApiMintBurnDataFromScript n
updateFromScript mbd = case mbd ^. #assetName of
Nothing -> mbd {assetName = Just (ApiT nullTokenName)}
Just _ -> mbd
updateFromInp :: ApiMintBurnDataFromInput n -> ApiMintBurnDataFromInput n
updateFromInp mbd = case mbd ^. #assetName of
Nothing -> mbd {assetName = Just (ApiT nullTokenName)}
Just _ -> mbd

guardWrongMintingTemplate
:: NonEmpty (ApiMintBurnDataFromScript n) -> Either ErrConstructTx ()
guardWrongMintingTemplate mintBurnData =
when (any wrongMintingTemplate mintBurnData)
:: NonEmpty (ApiMintBurnData n) -> Either ErrConstructTx ()
guardWrongMintingTemplate mbs =
when (any wrongMintingTemplate mbs)
$ Left ErrConstructTxWrongMintingBurningTemplate
where
wrongMintingTemplate (ApiMintBurnDataFromScript (ApiT script) _ _) =
isLeft (validateScriptOfTemplate RecommendedValidation script)
|| countCosigners script /= (1 :: Int)
|| existsNonZeroCosigner script
wrongMintingTemplate mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript (ApiT script) _ _) ->
isLeft (validateScriptOfTemplate RecommendedValidation script)
|| countCosigners script /= (1 :: Int)
|| existsNonZeroCosigner script
Right (ApiMintBurnDataFromInput _ _ _ _) -> False
countCosigners = foldScript (const (+ 1)) 0
existsNonZeroCosigner =
foldScript (\cosigner a -> a || cosigner /= Cosigner 0) False

guardAssetNameTooLong
:: NonEmpty (ApiMintBurnDataFromScript n) -> Either ErrConstructTx ()
guardAssetNameTooLong mintBurnData =
when (any assetNameTooLong mintBurnData)
$ Left ErrConstructTxAssetNameTooLong
:: NonEmpty (ApiMintBurnData n) -> Either ErrConstructTx ()
guardAssetNameTooLong mbs =
when (any assetNameTooLong mbs)$ Left ErrConstructTxAssetNameTooLong
where
assetNameTooLong = \case
ApiMintBurnDataFromScript _ (Just (ApiT (UnsafeTokenName bs))) _ ->
assetNameTooLong mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript _ (Just (ApiT (UnsafeTokenName bs))) _) ->
BS.length bs > tokenNameMaxLength
Right (ApiMintBurnDataFromInput _ _ (Just (ApiT (UnsafeTokenName bs))) _) ->
BS.length bs > tokenNameMaxLength
_ -> error "tokenName should be nonempty at this step"
_ -> error "at this moment there should be asset name attributed"

guardAssetQuantityOutOfBounds
:: NonEmpty (ApiMintBurnDataFromScript n) -> Either ErrConstructTx ()
guardAssetQuantityOutOfBounds mintBurnData =
when (any assetQuantityOutOfBounds mintBurnData)
:: NonEmpty (ApiMintBurnData n) -> Either ErrConstructTx ()
guardAssetQuantityOutOfBounds mbs =
when (any assetQuantityOutOfBounds mbs)
$ Left ErrConstructTxMintOrBurnAssetQuantityOutOfBounds
where
assetQuantityOutOfBounds = \case
ApiMintBurnDataFromScript _ _ (ApiMint (ApiMintData _ amt)) ->
amt <= 0 || amt > unTokenQuantity txMintBurnMaxTokenQuantity
ApiMintBurnDataFromScript _ _ (ApiBurn (ApiBurnData amt)) ->
amt <= 0 || amt > unTokenQuantity txMintBurnMaxTokenQuantity
checkAmt amt =
amt <= 0 || amt > unTokenQuantity txMintBurnMaxTokenQuantity
assetQuantityOutOfBounds mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript _ _ (ApiMint (ApiMintData _ amt))) ->
checkAmt amt
Left (ApiMintBurnDataFromScript _ _ (ApiBurn (ApiBurnData amt))) ->
checkAmt amt
Right (ApiMintBurnDataFromInput _ _ _ (ApiMint (ApiMintData _ amt))) ->
checkAmt amt
Right (ApiMintBurnDataFromInput _ _ _ (ApiBurn (ApiBurnData amt))) ->
checkAmt amt

guardOutsideValidityInterval
:: (SlotNo, SlotNo)
-> NonEmpty (ApiMintBurnDataFromScript n)
-> NonEmpty (ApiMintBurnData n)
-> Either ErrConstructTx ()
guardOutsideValidityInterval (before, hereafter) mintBurnData =
when (any notWithinValidityInterval mintBurnData) $
guardOutsideValidityInterval (before, hereafter) mbs =
when (any notWithinValidityInterval mbs) $
Left ErrConstructTxValidityIntervalNotWithinScriptTimelock
where
notWithinValidityInterval (ApiMintBurnDataFromScript (ApiT script) _ _) =
not $ withinSlotInterval before hereafter $
notWithinValidityInterval mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript (ApiT script) _ _) ->
not $ withinSlotInterval before hereafter $
scriptSlotIntervals script
Right _ -> False

unsignedTx path initialOuts decodedTx = UnsignedTx
{ unsignedCollateral =
Expand All @@ -2747,20 +2778,27 @@ constructTransaction api argGenChange knownPools poolStatus apiWalletId body = d
mapMaybe (toUsignedTxWdrl path) (decodedTx ^. #withdrawals)
}

toMintTxOut policyXPub
(ApiMintBurnDataFromScript (ApiT scriptT) (Just (ApiT tName))
(ApiMint (ApiMintData (Just addr) amt))) =
toMintTxOut policyXPub mb = case mb ^. #mintBurnData of
Left (ApiMintBurnDataFromScript (ApiT scriptT) (Just (ApiT tName))
(ApiMint (ApiMintData (Just addr) amt))) ->
let (assetId, tokenQuantity, _) =
toTokenMapAndScript ShelleyKeyS
scriptT (Map.singleton (Cosigner 0) policyXPub)
tName amt
assets = fromFlatList [(assetId, tokenQuantity)]
in
(addr, assets)
toMintTxOut _ _ = error $ unwords
[ "toMintTxOut can only be used in the minting context with addr"
, "specified"
]
Right (ApiMintBurnDataFromInput _ (ApiT policyId) (Just (ApiT tName))
(ApiMint (ApiMintData (Just addr) amt))) ->
let assetId = AssetId policyId tName
tokenQuantity = TokenQuantity amt
assets = fromFlatList [(assetId, tokenQuantity)]
in
(addr, assets)
_ -> error $ unwords
[ "toMintTxOut can only be used in the minting context with addr"
, "specified"
]

coalesceTokensPerAddr =
let toTxOut (addr, assets) =
Expand Down
3 changes: 3 additions & 0 deletions lib/wallet/api/http/Cardano/Wallet/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3128,6 +3128,9 @@ data ApiMintBurnDataFromInput (n :: NetworkDiscriminant) = ApiMintBurnDataFromIn
{ referenceInput
:: !ReferenceInput
-- ^ A reference input that contains script regulating minting/burning policy.
, policyId
:: !(ApiT W.TokenPolicyId)
-- ^ A policy id of the script regulating minting/burning policy.
, assetName
:: !(Maybe (ApiT W.TokenName))
-- ^ The name of the asset to mint/burn.
Expand Down
Loading

0 comments on commit 6157932

Please sign in to comment.