packages feed

solana-haskell-sdk-1.2.0.0: test-integration/Test/Integration/Nonce.hs

-- | Live durable-nonce coverage: create and initialize a nonce account, use
-- it via 'newNonceTransaction' to send a transfer, and check the durable
-- nonce advances and the transfer lands.
module Test.Integration.Nonce (tests) where

import Control.Exception (throwIO)
import Control.Monad.IO.Class (liftIO)
import Network.Solana.Core.Crypto (createSolanaKeyPair)
import Network.Solana.NativePrograms.SystemProgram qualified as SystemProgram
import Network.Solana.RPC.HTTP.Account (getBalance)
import Network.Solana.RPC.HTTP.Tokenomics (getMinimumBalanceForRentExemption)
import Network.Solana.SolanaWeb3 (getNonceAccount, newNonceTransaction)
import Network.Web3.Provider (Web3)
import Test.Integration.Setup
import Test.Tasty
import Test.Tasty.HUnit

tests :: TestTree
tests =
  testGroup
    "Nonce"
    [testCase "durable-nonce flow: create, use via newNonceTransaction, nonce advances" (run nonceFlow)]

-- | Creates and initializes a nonce account (payer as authority), checks its
-- decoded state before use, sends a transfer through 'newNonceTransaction',
-- and checks the durable nonce advanced and the transfer landed with the
-- exact amount.
--
-- 'newNonceTransaction' reads the nonce account at @confirmed@ commitment
-- internally, but the node preflights its send against the @finalized@
-- bank, so the create transaction is finalized before it is called.
nonceFlow :: Web3 ()
nonceFlow = do
  (payerPk, payerSk) <- fundedKeypair 2_000_000_000
  (noncePk, nonceSk) <- liftIO createSolanaKeyPair
  rent <- getMinimumBalanceForRentExemption 80
  createSig <-
    sendAndConfirm
      [payerSk, nonceSk]
      [ SystemProgram.createAccount payerPk noncePk rent 80 SystemProgram.systemProgramId,
        SystemProgram.initializeNonceAccount noncePk payerPk
      ]
  confirmFinalized createSig -- newNonceTransaction preflights against the finalized bank
  before <- requireJust "nonce account (before)" =<< getNonceAccount noncePk
  nonceBefore <- case before of
    SystemProgram.NonceInitialized auth dn _ -> liftIO (auth @?= payerPk) >> pure dn
    _ -> liftIO (throwIO (userError "nonce account not initialized"))
  recipient <- fst <$> liftIO createSolanaKeyPair
  sig <- newNonceTransaction payerPk [payerSk] noncePk [SystemProgram.transfer payerPk recipient 100_000_000]
  confirmFinalized sig
  afterAcc <- requireJust "nonce account (after)" =<< getNonceAccount noncePk
  case afterAcc of
    SystemProgram.NonceInitialized _ dn _ -> liftIO (assertBool "durable nonce advanced" (dn /= nonceBefore))
    _ -> liftIO (throwIO (userError "nonce account no longer initialized"))
  bal <- getBalance recipient
  liftIO (bal @?= 100_000_000)