Skip to content

Commit

Permalink
feat: update to coin selection and tx build strategy to incorporate u…
Browse files Browse the repository at this point in the history
…pdate of certificates and withdrawals

Related to #294.
  • Loading branch information
sourabhxyz committed Apr 26, 2024
1 parent 28e9059 commit cbd2aa5
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 21 deletions.
2 changes: 1 addition & 1 deletion atlas-cardano.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ test-suite atlas-privnet-tests
ghc-options: -threaded -rtsopts
hs-source-dirs: tests-privnet
main-is: atlas-privnet-tests.hs
other-modules:
other-modules: GeniusYield.Test.Privnet.Stake

-- Dependencies inherited from the library. No need to specify bounds.
build-depends:
Expand Down
44 changes: 28 additions & 16 deletions src/GeniusYield/Transaction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import Data.Foldable (Foldable (foldMap'),
import Data.List (delete)
import qualified Data.Map as Map
import Data.Ratio ((%))
import qualified Data.Set as Set

import Data.Either.Combinators (maybeToRight)
import Data.Maybe (fromJust)
Expand All @@ -75,6 +76,7 @@ import qualified Cardano.Ledger.Alonzo.Scripts as AlonzoScripts
import qualified Cardano.Ledger.Alonzo.Tx as AlonzoTx
import Cardano.Slotting.Time (SystemStart)

import qualified Cardano.Api.Shelley as Api
import Cardano.Ledger.Core (EraTx (sizeTxF))
import Control.Lens (view)
import Control.Monad.Random
Expand Down Expand Up @@ -337,11 +339,12 @@ finalizeGYBalancedTx
collaterals
ss
eh
pp
unbundledPP
ps
(utxosToApi utxos)
body
changeAddr
unregisteredStakeCredsMap
where

inRefs :: Api.TxInsReference Api.BuildTx Api.BabbageEra
Expand Down Expand Up @@ -442,6 +445,12 @@ finalizeGYBalancedTx
) (mempty, mempty) certs
in Api.TxCertificates Api.S.CertificatesInBabbageEra (reverse $ fst apiCertsFromGY) $ Api.BuildTxWith (snd apiCertsFromGY)

unbundledPP = Api.S.unbundleProtocolParams pp

ppStakeAddressDeposit = fromIntegral $ Api.S.protocolParamStakeAddressDeposit unbundledPP

unregisteredStakeCredsMap = Map.fromList [ (stakeCredentialToApi sc, ppStakeAddressDeposit) | GYStakeAddressDeregistrationCertificate sc <- map gyTxCertCertificate certs]

body :: Api.TxBodyContent Api.BuildTx Api.BabbageEra
body = Api.TxBodyContent
ins'
Expand All @@ -455,7 +464,7 @@ finalizeGYBalancedTx
txMetadata
Api.TxAuxScriptsNone
extra
(Api.BuildTxWith $ Just $ Api.S.unbundleProtocolParams pp)
(Api.BuildTxWith $ Just unbundledPP)
wdrls'
certs'
Api.TxUpdateProposalNone
Expand All @@ -469,34 +478,37 @@ If not checked, the returned txbody may fail during submission.
makeTransactionBodyAutoBalanceWrapper :: GYUTxOs
-> SystemStart
-> Api.S.EraHistory Api.S.CardanoMode
-> Api.S.BundledProtocolParameters Api.S.BabbageEra
-> Api.ProtocolParameters
-> Set Api.S.PoolId
-> Api.S.UTxO Api.S.BabbageEra
-> Api.S.TxBodyContent Api.S.BuildTx Api.S.BabbageEra
-> GYAddress
-> Map.Map Api.StakeCredential Api.Lovelace
-> Int
-> Either BuildTxException GYTxBody
makeTransactionBodyAutoBalanceWrapper collaterals ss eh pp ps utxos body changeAddr numSkeletonOuts = do
makeTransactionBodyAutoBalanceWrapper collaterals ss eh unbundledPP _ps utxos body changeAddr stakeDelegDeposits numSkeletonOuts = do
let poolids = Set.empty -- TODO: This denotes the set of registered stake pools, that are being unregistered in this transaction.
nkeys = Api.estimateTransactionKeyWitnessCount body

Api.ExecutionUnits
{ executionSteps = maxSteps
, executionMemory = maxMemory
} <- maybeToRight BuildTxMissingMaxExUnitsParam $ Api.S.protocolParamMaxTxExUnits $ Api.S.unbundleProtocolParams pp
let maxTxSize = Api.S.protocolParamMaxTxSize $ Api.S.unbundleProtocolParams pp
} <- maybeToRight BuildTxMissingMaxExUnitsParam $ Api.S.protocolParamMaxTxExUnits unbundledPP
let maxTxSize = Api.S.protocolParamMaxTxSize unbundledPP
changeAddrApi :: Api.S.AddressInEra Api.S.BabbageEra = addressToApi' changeAddr
stakeDelegDeposits = mempty -- TODO: Currently it's empty as we don't support for unregistration!

-- First we obtain the calculated fees to correct for our collaterals.
bodyBeforeCollUpdate@(Api.BalancedTxBody _ _ _ (Api.Lovelace feeOld)) <-
first BuildTxBodyErrorAutoBalance $ Api.makeTransactionBodyAutoBalance
ss
(Api.toLedgerEpochInfo eh)
(Api.S.unbundleProtocolParams pp)
ps
unbundledPP
poolids
stakeDelegDeposits
utxos
body
changeAddrApi
Nothing
(Just nkeys)

-- We should call `makeTransactionBodyAutoBalance` again with updated values of collaterals so as to get slightly lower fee estimate.
Api.BalancedTxBody txBodyContent txBody extraOut _ <- if collaterals == mempty then return bodyBeforeCollUpdate else
Expand All @@ -505,7 +517,7 @@ makeTransactionBodyAutoBalanceWrapper collaterals ss eh pp ps utxos body changeA

collateralTotalValue :: GYValue = foldMapUTxOs utxoValue collaterals
collateralTotalLovelace :: Integer = fst $ valueSplitAda collateralTotalValue
balanceNeeded :: Integer = ceiling $ (feeOld * toInteger (fromJust $ Api.S.protocolParamCollateralPercent $ Api.S.unbundleProtocolParams pp)) % 100
balanceNeeded :: Integer = ceiling $ (feeOld * toInteger (fromJust $ Api.S.protocolParamCollateralPercent unbundledPP)) % 100

in do

Expand All @@ -521,13 +533,13 @@ makeTransactionBodyAutoBalanceWrapper collaterals ss eh pp ps utxos body changeA
first BuildTxBodyErrorAutoBalance $ Api.makeTransactionBodyAutoBalance
ss
(Api.toLedgerEpochInfo eh)
(Api.S.unbundleProtocolParams pp)
ps
unbundledPP
poolids
stakeDelegDeposits
utxos
body {Api.txTotalCollateral = txColl, Api.txReturnCollateral = collRet}
changeAddrApi
Nothing
(Just nkeys)

let Api.S.ShelleyTx _ ltx = Api.Tx txBody []
-- This sums up the ExUnits for all embedded Plutus Scripts anywhere in the transaction:
Expand Down Expand Up @@ -561,9 +573,9 @@ collapseExtraOut
:: Api.TxOut Api.S.CtxTx Api.S.BabbageEra
-- ^ The extra output generated by @makeTransactionBodyAutoBalance@.
-> Api.TxBodyContent Api.S.BuildTx Api.S.BabbageEra
-- ^ The body content generted by @makeTransactionBodyAutoBalance@.
-- ^ The body content generated by @makeTransactionBodyAutoBalance@.
-> Api.TxBody Api.S.BabbageEra
-- ^ The body generted by @makeTransactionBodyAutoBalance@.
-- ^ The body generated by @makeTransactionBodyAutoBalance@.
-> Int
-- ^ The number of skeleton outputs we don't want to touch.
-> Either Api.S.TxBodyError (Api.TxBody Api.S.BabbageEra)
Expand Down
20 changes: 16 additions & 4 deletions src/GeniusYield/Transaction/CoinSelection.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,31 @@ selectInputs :: forall m v. MonadRandom m
-> ExceptT BalancingError m ([GYTxInDetailed v], [GYTxOut v])
selectInputs
GYCoinSelectionEnv
{ existingInputs
{ existingInputs = existingInputs'
, requiredOutputs
, mintValue
, changeAddr
, ownUtxos
, extraLovelace
, minimumUTxOF
, adaSource
, adaSink
}
GYLegacy = do
additionalInputForReplayProtection <- except $
if existingInputs' == mempty then -- For replay protection, every transaction must spend at least one UTxO.
-- We pick the UTxO having most value.
let ownUtxosList = utxosToList ownUtxos
in case ownUtxosList of
[] -> Left BalancingErrorEmptyOwnUTxOs
_ -> pure . pure $ utxoAsPubKeyInp $ maximumBy (compare `on` utxoValue) ownUtxosList
else pure Nothing
let
additionalInputForReplayProtectionAsList = maybe [] pure additionalInputForReplayProtection
existingInputs = additionalInputForReplayProtectionAsList <> existingInputs'
valueIn, valueOut :: GYValue
valueIn = foldMap gyTxInDetValue existingInputs
valueOut = foldMap snd requiredOutputs
valueIn = foldMap gyTxInDetValue existingInputs <> valueFromLovelace (fromIntegral adaSource)
valueOut = foldMap snd requiredOutputs <> valueFromLovelace (fromIntegral adaSink)
valueMissing = missing (valueFromLovelace (fromIntegral extraLovelace) <> valueOut `valueMinus` (valueIn <> mintValue))
(addIns, addVal) <- except $ selectInputsLegacy
ownUtxos
Expand All @@ -136,7 +148,7 @@ selectInputs
[adjustTxOut minimumUTxOF (GYTxOut changeAddr tokenChange Nothing Nothing)
| not $ isEmptyValue tokenChange
]
pure (addIns, changeOuts)
pure (additionalInputForReplayProtectionAsList <> addIns, changeOuts)
where
missing :: GYValue -> Map GYAssetClass Natural
missing v = foldl' f Map.empty $ valueToList v
Expand Down
6 changes: 6 additions & 0 deletions src/GeniusYield/Types/TxCert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import GeniusYield.Types.TxCert.Internal
mkStakeAddressRegistrationCertificate :: GYStakeCredential -> GYTxCert v
mkStakeAddressRegistrationCertificate sc = GYTxCert (GYStakeAddressRegistrationCertificate sc) Nothing

{-| Note that deregistration certificate requires following preconditions:
1. The stake address must be registered.
2. The corresponding rewards balance is zero.
-}
mkStakeAddressDeregistrationCertificate :: GYStakeCredential -> GYTxCertWitness v -> GYTxCert v
mkStakeAddressDeregistrationCertificate sc wit = GYTxCert (GYStakeAddressDeregistrationCertificate sc) (Just wit)

Expand Down
38 changes: 38 additions & 0 deletions tests-privnet/GeniusYield/Test/Privnet/Stake.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module GeniusYield.Test.Privnet.Stake (
tests,
) where

import Data.Maybe (fromJust)
import GeniusYield.Imports
import GeniusYield.Test.Privnet.Ctx
import GeniusYield.Test.Privnet.Setup
import GeniusYield.TxBuilder (mustHaveCertificate)
import GeniusYield.Types
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCaseSteps)

-- This will check if we are able to register a stake credential without it's signature.
registerStakeCredentialSteps :: (String -> IO ()) -> Ctx -> IO User
registerStakeCredentialSteps info ctx = do
newUser <- newTempUserCtx ctx (ctxUserF ctx) (valueFromLovelace 1_000_000_000) (CreateUserConfig {cucGenerateCollateral = False, cucGenerateStakeKey = True})
pp <- ctxGetParams ctx & gyGetProtocolParameters'
info $ "-- Protocol parameters --\n" <> show pp <> "\n-- x --\n"
txBodyReg <- ctxRunI ctx newUser $ do
return $ mustHaveCertificate (mkStakeAddressRegistrationCertificate (userStakePkh newUser & fromJust & GYStakeCredentialByKey))
info $ "-- Tx body --\n" <> show txBodyReg <> "\n-- x --\n"
void $ submitTx ctx newUser txBodyReg
pure newUser

deregisterStakeCredentialSteps :: User -> (String -> IO ()) -> Ctx -> IO ()
deregisterStakeCredentialSteps user@User{..} info ctx = do
txBodyDereg <- ctxRunI ctx user $ do
return $ mustHaveCertificate (mkStakeAddressDeregistrationCertificate (userStakePkh user & fromJust & GYStakeCredentialByKey) GYTxCertWitnessKey)
info $ "-- Tx body --\n" <> show txBodyDereg <> "\n-- x --\n"
void $ submitTx' ctx $ signGYTxBody txBodyDereg [GYSomeSigningKey userPaymentSKey, userStakeSKey & fromJust & GYSomeSigningKey]

tests :: IO Setup -> TestTree
tests setup = testGroup "stake"
[ testCaseSteps "able to deregister a registered stake credential" $ \info -> withSetup setup info $ \ctx -> do
newUser <- registerStakeCredentialSteps info ctx
deregisterStakeCredentialSteps newUser info ctx
]
3 changes: 3 additions & 0 deletions tests-privnet/atlas-privnet-tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import GeniusYield.Types
import GeniusYield.Test.Privnet.Ctx
import qualified GeniusYield.Test.Privnet.Examples
import GeniusYield.Test.Privnet.Setup
import qualified GeniusYield.Test.Privnet.Stake

main :: IO ()
main = do
Expand Down Expand Up @@ -61,5 +62,7 @@ main = do

, GeniusYield.Test.Privnet.Examples.tests setup

, GeniusYield.Test.Privnet.Stake.tests setup

]

0 comments on commit cbd2aa5

Please sign in to comment.