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

Redesign GYTxMonad and its instances to be able to submit transactions #322

Merged
merged 28 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
938b5d9
Rename `GYTxMonadNode` to `GYTxMonadIO`
TotallyNotChase Jul 2, 2024
cc2c902
`instance IsGYApiError GYApiError`
TotallyNotChase Jul 2, 2024
543b019
Remove `Clb` export from `TxBuilder`
TotallyNotChase Jul 2, 2024
ab938b9
Remove `GeniusYield.TxBuilder.Random`
TotallyNotChase Jul 3, 2024
ec2cec4
Remove `randSeed` method from `GYTxMonad`
TotallyNotChase Jul 3, 2024
77d7f81
Common MTL transformer instances (also remove `ExceptT` instance)
TotallyNotChase Jul 3, 2024
ded6be9
Make `GYTxMonadIO` a newtype of `GYTxQueryMonadIO`
TotallyNotChase Jul 3, 2024
2c47b8f
Emphasize context on 'gyBTxEnvOwnUtxos' field of 'GYBuildTxEnv'
TotallyNotChase Jul 3, 2024
310f4e6
Remove `MonadIO` instance from `GYTxMonadIO` and `GYTxQueryMonadIO`
TotallyNotChase Jul 8, 2024
6905d1f
Reorder `GYInScript` type variables for better interaction with type …
TotallyNotChase Jul 8, 2024
4a25c0d
Add `ownChangeAddress` and `ownCollateral` to `GYTxMonad`
TotallyNotChase Jul 8, 2024
2d8b95d
Remove `BuildTxException`, rename to `GYBuildTxError` and make it par…
TotallyNotChase Jul 8, 2024
30d8aa0
`instance Default GYCoinSelectionStrategy`
TotallyNotChase Jul 8, 2024
4144100
Add 'submitTx' and 'awaitTxConfirmed' under 'GYTxMonad' and refine mo…
TotallyNotChase Jul 8, 2024
70e186b
Make transaction building a part of `GYTxMonad` utilities and rework …
TotallyNotChase Jul 8, 2024
0c3e3df
Use pre-provided submission utils rather than privnet defined ones
TotallyNotChase Jul 8, 2024
d4140d3
Query logs in `GYTxQueryMonadIO` should be debug level
TotallyNotChase Jul 9, 2024
037f5a5
Wait for tx confirmation after submission in privnet code
TotallyNotChase Jul 9, 2024
8dd162b
Ability to filter logs based on severity in privnet code
TotallyNotChase Jul 9, 2024
d1d42d6
Better `withSetup` type signature
TotallyNotChase Jul 10, 2024
c0536fe
Add signing ability to `GYTxMonad` and separate out a monad just for …
TotallyNotChase Jul 10, 2024
481e23b
Minor improvements
TotallyNotChase Jul 11, 2024
f0e501f
Add `User` module for shareable user wallet type
TotallyNotChase Jul 13, 2024
81b8370
Add `GYTxGameMonad`
TotallyNotChase Jul 13, 2024
632cc26
Move query monads to `GeniusYield.TxBuilder.Query.Class`
TotallyNotChase Jul 15, 2024
5e3c214
Add `GYTxBuilderMonad` for custom build tx impls
TotallyNotChase Jul 15, 2024
3477ef9
Remove `Traversable f` parameter from tx skeleton building entirely
TotallyNotChase Jul 23, 2024
c64eeff
Fix typos as per review comments
TotallyNotChase Jul 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions atlas-cardano.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ common common
default-language: GHC2021
default-extensions:
DataKinds
DefaultSignatures
DeriveAnyClass
DerivingStrategies
DerivingVia
Expand All @@ -34,7 +35,7 @@ common common
OverloadedStrings
RecordWildCards
RoleAnnotations
TypeFamilies
TypeFamilyDependencies
UndecidableInstances
ViewPatterns

Expand Down Expand Up @@ -105,9 +106,9 @@ library
GeniusYield.TxBuilder.Clb
GeniusYield.TxBuilder.Common
GeniusYield.TxBuilder.Errors
GeniusYield.TxBuilder.Node
GeniusYield.TxBuilder.NodeQuery
GeniusYield.TxBuilder.Random
GeniusYield.TxBuilder.IO
GeniusYield.TxBuilder.Query.Class
GeniusYield.TxBuilder.User
GeniusYield.Types
GeniusYield.Types.Ada
GeniusYield.Types.Address
Expand Down Expand Up @@ -155,6 +156,8 @@ library
GeniusYield.Providers.LiteChainIndex
GeniusYield.Providers.Node.AwaitTx
GeniusYield.Providers.Node.Query
GeniusYield.TxBuilder.IO.Query
GeniusYield.TxBuilder.IO.Builder
GeniusYield.Utils
build-depends:
, async ^>=2.2.5
Expand Down
4 changes: 2 additions & 2 deletions src/GeniusYield/Examples/Limbo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ getRefInfos = do
-- First it checks whether there is an UTxO already with a script.
-- Only if there aren't the new transaction skeleton is constructed.
--
addRefScript :: GYTxMonad m => GYScript 'PlutusV2 -> m (Either GYTxOutRef (GYTxSkeleton v))
addRefScript :: GYTxQueryMonad m => GYScript 'PlutusV2 -> m (Either GYTxOutRef (GYTxSkeleton v))
addRefScript sc = do
addr <- scriptAddress limboValidatorV2
utxo <- utxosAtAddress addr Nothing
Expand All @@ -97,7 +97,7 @@ addRefScript sc = do

-- | Create UTxO with a reference script.
--
addRefScript' :: GYTxMonad m => GYScript 'PlutusV2 -> m (GYTxSkeleton v)
addRefScript' :: GYTxQueryMonad m => GYScript 'PlutusV2 -> m (GYTxSkeleton v)
addRefScript' sc = do
addr <- scriptAddress limboValidatorV2
return $ mustHaveOutput (mkGYTxOut addr mempty (datumFromPlutusData ())) { gyTxOutRefS = Just $ GYPlutusScript sc }
Expand Down
5 changes: 5 additions & 0 deletions src/GeniusYield/HTTP/Errors.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ data GYApiError = GYApiError
}
deriving stock (Show, Eq)

instance Exception GYApiError where

instance IsGYApiError GYApiError where
toApiError = id

-- | Create a typical BACKEND_ERROR internal serval error with given message.
someBackendError :: Text -> GYApiError
someBackendError msg = GYApiError
Expand Down
10 changes: 6 additions & 4 deletions src/GeniusYield/Test/Privnet/Asserts.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import Data.Typeable (typeRep)
import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure)

import GeniusYield.Imports
import GeniusYield.Types
import GeniusYield.Transaction
import GeniusYield.TxBuilder.Errors
import GeniusYield.Types

import GeniusYield.Test.Privnet.Ctx

assertFee :: HasCallStack => GYTxBody -> Integer -> Integer -> IO ()
Expand Down Expand Up @@ -54,6 +56,6 @@ assertUserFunds fees ctx u expectedValue = do
"\nCurrent: ", show currentValue])
(currentValue == expectedValue')

isTxBodyErrorAutoBalance :: BuildTxException -> Bool
isTxBodyErrorAutoBalance (BuildTxBodyErrorAutoBalance _) = True
isTxBodyErrorAutoBalance _ = False
isTxBodyErrorAutoBalance :: GYTxMonadException -> Bool
isTxBodyErrorAutoBalance (GYBuildTxException (GYBuildTxBodyErrorAutoBalance _)) = True
isTxBodyErrorAutoBalance _ = False
157 changes: 53 additions & 104 deletions src/GeniusYield/Test/Privnet/Ctx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module GeniusYield.Test.Privnet.Ctx (
-- * User
User (..),
CreateUserConfig (..),

ctxUsers,
userPkh,
userPaymentPkh,
Expand All @@ -22,20 +21,15 @@ module GeniusYield.Test.Privnet.Ctx (
userPaymentVKey,
userStakeVKey,
-- * Operations
ctxRunI,
ctxRunIWithStrategy,
ctxRunC,
ctxRunCWithStrategy,
ctxRunF,
ctxRunFWithStrategy,
ctxRunFWithCollateral,
ctxRun,
ctxRunQuery,
ctxRunBuilder,
ctxRunBuilderWithCollateral,
ctxSlotOfCurrentBlock,
ctxWaitNextBlock,
ctxWaitUntilSlot,
ctxProviders,
ctxSlotConfig,
submitTx,
submitTx',
-- * Helpers
newTempUserCtx,
ctxQueryBalance,
Expand All @@ -47,11 +41,11 @@ module GeniusYield.Test.Privnet.Ctx (
import qualified Cardano.Api as Api
import Data.Default (Default (..))
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set

import qualified GeniusYield.Examples.Limbo as Limbo
import GeniusYield.HTTP.Errors (someBackendError)
import GeniusYield.Imports
import GeniusYield.Providers.Node
import GeniusYield.Transaction
import GeniusYield.TxBuilder
import GeniusYield.Types
import Test.Tasty.HUnit (assertFailure)
Expand All @@ -67,30 +61,6 @@ data CreateUserConfig =
instance Default CreateUserConfig where
def = CreateUserConfig { cucGenerateCollateral = False, cucGenerateStakeKey = False }

data User = User
{ userPaymentSKey :: !GYPaymentSigningKey
, userStakeSKey :: !(Maybe GYStakeSigningKey)
, userAddr :: !GYAddress
}

{-# DEPRECATED userVKey "Use userPaymentVKey." #-}
userVKey :: User -> GYPaymentVerificationKey
userVKey = paymentVerificationKey . userPaymentSKey

userPaymentVKey :: User -> GYPaymentVerificationKey
userPaymentVKey = userVKey

userStakeVKey :: User -> Maybe GYStakeVerificationKey
userStakeVKey = fmap stakeVerificationKey . userStakeSKey

userPkh :: User -> GYPubKeyHash
userPkh = toPubKeyHash . paymentKeyHash . paymentVerificationKey . userPaymentSKey

userPaymentPkh :: User -> GYPaymentKeyHash
userPaymentPkh = paymentKeyHash . paymentVerificationKey . userPaymentSKey

userStakePkh :: User -> Maybe GYStakeKeyHash
userStakePkh = fmap (stakeKeyHash . stakeVerificationKey) . userStakeSKey

data Ctx = Ctx
{ ctxNetworkInfo :: !GYNetworkInfo
Expand Down Expand Up @@ -142,44 +112,40 @@ newTempUserCtx ctx fundUser fundValue CreateUserConfig {..} = do
-- Our balancer would add minimum ada required for other utxo in case of equality
when (cucGenerateCollateral && adaInValue < collateralLovelace) $ fail "Given value for new user has less than 5 ada"

txBody <- ctxRunI ctx fundUser $ return $
if cucGenerateCollateral then
mustHaveOutput (mkGYTxOutNoDatum newAddr (otherValue <> (valueFromLovelace adaInValue `valueMinus` collateralValue))) <>
mustHaveOutput (mkGYTxOutNoDatum newAddr collateralValue)
else
mustHaveOutput (mkGYTxOutNoDatum newAddr fundValue)

void $ submitTx ctx fundUser txBody
return $ User {userPaymentSKey = newPaymentSKey, userAddr = newAddr, userStakeSKey = newStakeSKey}


ctxRunF :: forall t v. Traversable t => Ctx -> User -> GYTxMonadNode (t (GYTxSkeleton v)) -> IO (t GYTxBody)
ctxRunF ctx User {..} = runGYTxMonadNodeF GYRandomImproveMultiAsset (ctxNetworkId ctx) (ctxProviders ctx) [userAddr] userAddr Nothing

ctxRunFWithStrategy :: forall t v. Traversable t => GYCoinSelectionStrategy -> Ctx -> User -> GYTxMonadNode (t (GYTxSkeleton v)) -> IO (t GYTxBody)
ctxRunFWithStrategy strat ctx User {..} = runGYTxMonadNodeF strat (ctxNetworkId ctx) (ctxProviders ctx) [userAddr] userAddr Nothing

-- | Variant of `ctxRunF` where caller can also give the UTxO to be used as collateral.
ctxRunFWithCollateral :: forall t v. Traversable t
=> Ctx
-> User
-> GYTxOutRef -- ^ Reference to UTxO to be used as collateral.
-> Bool -- ^ To check whether this given collateral UTxO has value of exact 5 ada? If it doesn't have exact 5 ada, it would be ignored.
-> GYTxMonadNode (t (GYTxSkeleton v))
-> IO (t GYTxBody)
ctxRunFWithCollateral ctx User {..} coll toCheck5Ada = runGYTxMonadNodeF GYRandomImproveMultiAsset (ctxNetworkId ctx) (ctxProviders ctx) [userAddr] userAddr $ Just (coll, toCheck5Ada)

ctxRunC :: forall a. Ctx -> User -> GYTxMonadNode a -> IO a
ctxRunC = coerce (ctxRunF @(Const a))

ctxRunCWithStrategy :: forall a. GYCoinSelectionStrategy -> Ctx -> User -> GYTxMonadNode a -> IO a
ctxRunCWithStrategy = coerce (ctxRunFWithStrategy @(Const a))

ctxRunI :: Ctx -> User -> GYTxMonadNode (GYTxSkeleton v) -> IO GYTxBody
ctxRunI = coerce (ctxRunF @Identity)

ctxRunIWithStrategy :: GYCoinSelectionStrategy -> Ctx -> User -> GYTxMonadNode (GYTxSkeleton v) -> IO GYTxBody
ctxRunIWithStrategy = coerce (ctxRunFWithStrategy @Identity)
ctxRun ctx fundUser $ do
txBody <- buildTxBody $
if cucGenerateCollateral then
mustHaveOutput (mkGYTxOutNoDatum newAddr (otherValue <> (valueFromLovelace adaInValue `valueMinus` collateralValue))) <>
mustHaveOutput (mkGYTxOutNoDatum newAddr collateralValue)
else
mustHaveOutput (mkGYTxOutNoDatum newAddr fundValue)
signAndSubmitConfirmed_ txBody

pure $ User' {userPaymentSKey' = newPaymentSKey, userAddr = newAddr, userStakeSKey' = newStakeSKey}


ctxRun :: Ctx -> User -> GYTxMonadIO a -> IO a
ctxRun ctx User' {..} = runGYTxMonadIO (ctxNetworkId ctx) (ctxProviders ctx) userPaymentSKey' userStakeSKey' [userAddr] userAddr Nothing

ctxRunQuery :: Ctx -> GYTxQueryMonadIO a -> IO a
ctxRunQuery ctx = runGYTxQueryMonadIO (ctxNetworkId ctx) (ctxProviders ctx)

ctxRunBuilder :: Ctx -> User -> GYTxBuilderMonadIO a -> IO a
ctxRunBuilder ctx User' {..} = runGYTxBuilderMonadIO (ctxNetworkId ctx) (ctxProviders ctx) [userAddr] userAddr Nothing

-- | Variant of `ctxRun` where caller can also give the UTxO to be used as collateral.
ctxRunBuilderWithCollateral :: Ctx
-> User
-> GYTxOutRef -- ^ Reference to UTxO to be used as collateral.
-> Bool -- ^ To check whether this given collateral UTxO has value of exact 5 ada? If it doesn't have exact 5 ada, it would be ignored.
-> GYTxBuilderMonadIO a
-> IO a
ctxRunBuilderWithCollateral ctx User' {..} coll toCheck5Ada = runGYTxBuilderMonadIO
(ctxNetworkId ctx)
(ctxProviders ctx)
[userAddr]
userAddr
(Just (coll, toCheck5Ada))

ctxSlotOfCurrentBlock :: Ctx -> IO GYSlot
ctxSlotOfCurrentBlock (ctxProviders -> providers) =
Expand All @@ -192,10 +158,10 @@ ctxWaitUntilSlot :: Ctx -> GYSlot -> IO ()
ctxWaitUntilSlot (ctxProviders -> providers) slot = void $ gyWaitUntilSlot providers slot

ctxSlotConfig :: Ctx -> IO GYSlotConfig
ctxSlotConfig (ctxProviders -> providers) = gyGetSlotConfig providers
ctxSlotConfig ctx = ctxRunQuery ctx slotConfig

ctxQueryBalance :: Ctx -> User -> IO GYValue
ctxQueryBalance ctx u = ctxRunC ctx u $ do
ctxQueryBalance ctx u = ctxRunQuery ctx $ do
queryBalance $ userAddr u

ctxProviders :: Ctx -> GYProviders
Expand All @@ -210,23 +176,6 @@ ctxProviders ctx = GYProviders
, gyGetStakeAddressInfo = nodeStakeAddressInfo (ctxInfo ctx)
}

submitTx :: Ctx -> User -> GYTxBody -> IO GYTxId
submitTx ctx User {..} txBody = do
let reqSigs = txBodyReqSignatories txBody
tx =
signGYTxBody' txBody $
case userStakeSKey of
Nothing -> [GYSomeSigningKey userPaymentSKey]
-- It might be the case that @cardano-api@ is clever enough to not add signature if it is not required but cursory look at their code suggests otherwise.
Just stakeKey -> if Set.member (toPubKeyHash . stakeKeyHash . stakeVerificationKey $ stakeKey) reqSigs then [GYSomeSigningKey userPaymentSKey, GYSomeSigningKey stakeKey] else [GYSomeSigningKey userPaymentSKey]
submitTx' ctx tx

submitTx' :: Ctx -> GYTx -> IO GYTxId
submitTx' ctx@Ctx { ctxInfo } tx = do
txId <- nodeSubmitTx ctxInfo tx
gyAwaitTxConfirmed (ctxProviders ctx) (GYAwaitTxParameters { maxAttempts = 30, checkInterval = 1_000_000, confirmations = 0 }) txId
return txId

-- | Function to find for the first locked output in the given `GYTxBody` at the given `GYAddress`.
findOutput :: GYAddress -> GYTxBody -> IO GYTxOutRef
findOutput addr txBody = do
Expand All @@ -239,17 +188,17 @@ addRefScriptCtx :: Ctx -- ^ Given context.
-> User -- ^ User which will execute the transaction (if required).
-> GYScript 'PlutusV2 -- ^ Given script.
-> IO GYTxOutRef -- ^ Returns the reference for the desired output.
addRefScriptCtx ctx user script = do
txBodyRefScript <- ctxRunF ctx user $ Limbo.addRefScript script
addRefScriptCtx ctx user script = ctxRun ctx user $ do
txBodyRefScript <- Limbo.addRefScript script >>= traverse buildTxBody
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Traversing manually instead of having the hack with GYTxMonadNode over a Traversable f

case txBodyRefScript of
Left ref -> return ref
Left ref -> pure ref
Right body -> do
let refs = Limbo.findRefScriptsInBody body
ref <- case Map.lookup (Some script) refs of
Just ref -> return ref
Nothing -> fail "Shouldn't happen: no ref in body"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

MonadFail removed since we want to unify exceptions as much as possible

void $ submitTx ctx user body
return ref
Nothing -> throwAppError $ someBackendError "Shouldn't happen: no ref in body"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's probably better to throw some typed error.

signAndSubmitConfirmed_ body
Copy link
Contributor Author

Choose a reason for hiding this comment

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

GYTxMonadIO (should really be used for bot-like usecase, use GYTxBuilderMonadIO for app-like usecases) now contains the user keys. So signAndSubmitConfirmed_ is a helper that uses the payment key.

pure ref

-- | Function to add for a reference input.
addRefInputCtx :: Ctx -- ^ Given context.
Expand All @@ -258,8 +207,8 @@ addRefInputCtx :: Ctx -- ^ Given context.
-> GYAddress -- ^ Address to put this output at.
-> GYDatum -- ^ The datum to put.
-> IO GYTxOutRef -- ^ Returns the reference for the required output.
addRefInputCtx ctx user toInline addr ourDatum = do
txBody <- ctxRunI ctx user $ return $ mustHaveOutput (GYTxOut addr mempty (Just (ourDatum, if toInline then GYTxOutUseInlineDatum else GYTxOutDontUseInlineDatum)) Nothing)
addRefInputCtx ctx user toInline addr ourDatum = ctxRun ctx user $ do
txBody <- buildTxBody $ mustHaveOutput (GYTxOut addr mempty (Just (ourDatum, if toInline then GYTxOutUseInlineDatum else GYTxOutDontUseInlineDatum)) Nothing)
let utxos = utxosToList $ txBodyUTxOs txBody
ourDatumHash = hashDatum ourDatum
mRefInputUtxo = find (\utxo ->
Expand All @@ -269,7 +218,7 @@ addRefInputCtx ctx user toInline addr ourDatum = do
GYOutDatumNone -> False
) utxos
case mRefInputUtxo of
Nothing -> fail "Shouldn't happen: Couldn't find desired UTxO in tx outputs"
Nothing -> throwAppError $ someBackendError "Shouldn't happen: Couldn't find desired UTxO in tx outputs"
Just GYUTxO {utxoRef} -> do
void $ submitTx ctx user txBody
return utxoRef
signAndSubmitConfirmed_ txBody
pure utxoRef
Loading