@@ -85,6 +85,9 @@ tests =
8585 [ testProperty
8686 " well-funded transaction always succeeds"
8787 prop_calcMinFeeRecursive_well_funded_succeeds
88+ , testProperty
89+ " well-funded multi-asset transaction always succeeds"
90+ prop_calcMinFeeRecursive_well_funded_multi_asset
8891 , testProperty
8992 " fee calculation is idempotent"
9093 prop_calcMinFeeRecursive_fee_fixpoint
@@ -612,6 +615,44 @@ genFundedSimpleTx era = do
612615 & Exp. setTxFee 0
613616 return (Exp. makeUnsignedTx era txBodyContent, utxo, changeAddr)
614617
618+ -- | Like 'genFundedSimpleTx' but the UTxO and output both carry native tokens.
619+ -- The output sends all tokens; the surplus ADA goes to the change output.
620+ -- This exercises Case 2's multi-asset handling on the success path.
621+ genFundedMultiAssetTx
622+ :: Exp. Era era
623+ -> Gen
624+ ( Exp. UnsignedTx (Exp. LedgerEra era )
625+ , L. UTxO (Exp. LedgerEra era )
626+ , L. Addr
627+ )
628+ genFundedMultiAssetTx era = do
629+ let sbe = convert era
630+ txIn <- genTxIn
631+ addr <- Api. toShelleyAddr <$> genAddressInEra sbe
632+ changeAddr <- Api. toShelleyAddr <$> genAddressInEra sbe
633+ sendCoin <- L. Coin <$> Gen. integral (Range. linear 2_000_000 5_000_000 )
634+ surplus <- L. Coin <$> Gen. integral (Range. linear 2_000_000 17_000_000 )
635+ tokenQty <- Gen. integral (Range. linear 1 1_000_000 )
636+ let fundingCoin = sendCoin + surplus
637+ policyId = L. PolicyID $ L. ScriptHash " 1c14ee8e58fbcbd48dc7367c95a63fd1d937ba989820015db16ac7e5"
638+ multiAsset = L. MultiAsset $ Map. singleton policyId (Map. singleton (Mary. AssetName " testtoken" ) tokenQty)
639+ ledgerTxIn = Api. toShelleyTxIn txIn
640+ fundingTxOut =
641+ Exp. obtainCommonConstraints era $
642+ L. mkBasicTxOut addr (L. MaryValue fundingCoin multiAsset)
643+ utxo = L. UTxO $ Map. singleton ledgerTxIn fundingTxOut
644+ sendTxOut =
645+ Exp. obtainCommonConstraints era $
646+ Exp. TxOut $
647+ Exp. obtainCommonConstraints era $
648+ Ledger. mkBasicTxOut addr (L. MaryValue sendCoin multiAsset)
649+ txBodyContent =
650+ Exp. defaultTxBodyContent
651+ & Exp. setTxIns [(txIn, Exp. AnyKeyWitnessPlaceholder )]
652+ & Exp. setTxOuts [sendTxOut]
653+ & Exp. setTxFee 0
654+ return (Exp. makeUnsignedTx era txBodyContent, utxo, changeAddr)
655+
615656-- | Generates a simple lovelace-only transaction where the single output
616657-- (5-10 ADA) greatly exceeds the UTxO funding (0.5-2 ADA).
617658genUnderfundedTx
@@ -667,6 +708,27 @@ prop_calcMinFeeRecursive_well_funded_succeeds = H.property $ do
667708 (resultLedgerTx ^. L. bodyTxL)
668709 balance H. === mempty
669710
711+ -- | Like 'prop_calcMinFeeRecursive_well_funded_succeeds' but the UTxO and
712+ -- output carry native tokens. Verifies that surplus tokens are correctly
713+ -- distributed to the change output and the result is fully balanced.
714+ prop_calcMinFeeRecursive_well_funded_multi_asset :: Property
715+ prop_calcMinFeeRecursive_well_funded_multi_asset = H. property $ do
716+ (unsignedTx, utxo, changeAddr) <- H. forAll $ genFundedMultiAssetTx Exp. ConwayEra
717+ case Exp. calcMinFeeRecursive changeAddr unsignedTx utxo exampleProtocolParams mempty mempty mempty 0 of
718+ Left err -> H. annotateShow err >> H. failure
719+ Right (Exp. UnsignedTx resultLedgerTx) -> do
720+ let resultFee = resultLedgerTx ^. L. bodyTxL . L. feeTxBodyL
721+ H. assert $ resultFee > L. Coin 0
722+ let balance =
723+ UnexportedLedger. evalBalanceTxBody
724+ exampleProtocolParams
725+ (const Nothing )
726+ (const Nothing )
727+ (const False )
728+ utxo
729+ (resultLedgerTx ^. L. bodyTxL)
730+ balance H. === mempty
731+
670732-- | 'calcMinFeeRecursive' is idempotent: applying it to its own result
671733-- yields the same 'UnsignedTx'. This confirms the fee has reached a
672734-- fixed point and that any surplus was already distributed to outputs.
0 commit comments