solana-haskell-sdk-1.2.0.0: test/Test/NativePrograms/SystemProgram.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.NativePrograms.SystemProgram (tests) where
import Data.Binary (decode, decodeOrFail, encode)
import Data.Binary.Get (ByteOffset)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BL
import Network.Solana.Core.Block (BlockHash (..))
import Network.Solana.Core.Crypto (SolanaPublicKey, createSolanaKeypairFromSeed, unsafeSolanaPublicKeyRaw)
import Network.Solana.Core.Instruction
import Network.Solana.NativePrograms.SystemProgram qualified as SP
import Network.Solana.Sysvar qualified as Sysvar
import Test.Fixtures
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
basePk, ownerPk, authorityPk, funderPk, newAccountPk :: SolanaPublicKey
basePk = unsafeSolanaPublicKeyRaw (replicate 32 3)
ownerPk = unsafeSolanaPublicKeyRaw (replicate 32 5)
authorityPk = unsafeSolanaPublicKeyRaw (replicate 32 6)
funderPk = unsafeSolanaPublicKeyRaw (replicate 32 7)
newAccountPk = unsafeSolanaPublicKeyRaw (replicate 32 8)
seedStr :: String
seedStr = "hello-seed"
dataOf :: Instruction -> BS.ByteString
dataOf = instrData . iData
genSystemInstruction :: Gen SP.SystemInstruction
genSystemInstruction =
oneof
[ SP.CreateAccount <$> arbitrary <*> arbitrary <*> genPk,
SP.Assign <$> genPk,
SP.Transfer <$> arbitrary,
SP.CreateAccountWithSeed <$> genPk <*> genSeed <*> arbitrary <*> arbitrary <*> genPk,
pure SP.AdvanceNonceAccount,
SP.WithdrawNonceAccount <$> arbitrary,
SP.InitializeNonceAccount <$> genPk,
SP.AuthorizeNonceAccount <$> genPk,
SP.Allocate <$> arbitrary,
SP.AllocateWithSeed <$> genPk <*> genSeed <*> arbitrary <*> genPk,
SP.AssignWithSeed <$> genPk <*> genSeed <*> genPk,
SP.TransferWithSeed <$> arbitrary <*> genSeed <*> genPk,
pure SP.UpgradeNonceAccount
]
where
genPk = unsafeSolanaPublicKeyRaw <$> vectorOf 32 arbitrary
genSeed = listOf (elements (['a' .. 'z'] ++ ['0' .. '9'] ++ "-_"))
payerPk :: SolanaPublicKey
payerPk =
case createSolanaKeypairFromSeed (BS.replicate 32 1) of
Just (pk, _) -> pk
Nothing -> error "failed to derive payer"
tests :: TestTree
tests = testGroup "SystemProgram" [instructionDataTests, nonceStateTests]
nonceStateTests :: TestTree
nonceStateTests =
withResource (loadFixtures "test/fixtures/state_fixtures.json") (const (pure ())) $ \getStateFixtures ->
testGroup
"SystemProgram nonce account state (golden + rejection)"
[ testCase "decodeNonceAccount: nonce-account golden" $ do
fs <- getStateFixtures
let bs = requireFixture "nonce-account" fs
durableNonceBytes = requireFixture "nonce-durable-hash" fs
case SP.decodeNonceAccount bs of
Left err -> assertFailure $ "decode failed: " <> err
Right ns -> case ns of
SP.NonceUninitialized -> assertFailure "expected NonceInitialized, got NonceUninitialized"
SP.NonceInitialized {SP.nsAuthority = auth, SP.nsDurableNonce = BlockHash nonceBytes, SP.nsLamportsPerSignature = fee} -> do
auth @?= payerPk
nonceBytes @?= durableNonceBytes
fee @?= 5000,
testCase "decodeNonceAccount: rejects unsupported version" $ do
let bs = BS.pack ([2, 0, 0, 0] <> replicate 76 0)
case SP.decodeNonceAccount bs of
Left _ -> pure ()
Right _ -> assertFailure "expected decode failure for unsupported version",
testCase "decodeNonceAccount: rejects unknown state" $ do
let bs = BS.pack ([1, 0, 0, 0, 2, 0, 0, 0] <> replicate 72 0)
case SP.decodeNonceAccount bs of
Left _ -> pure ()
Right _ -> assertFailure "expected decode failure for unknown state"
]
instructionDataTests :: TestTree
instructionDataTests =
withResource
(loadFixtures "test/fixtures/system_instruction_data.json")
(const (pure ()))
$ \getFixtures ->
testGroup
"SystemProgram instruction data (golden)"
[ testCase "CreateAccount" $ do
fs <- getFixtures
dataOf (SP.createAccount funderPk newAccountPk 1000000 165 ownerPk)
@?= requireFixture "CreateAccount" fs,
testCase "Assign" $ do
fs <- getFixtures
dataOf (SP.assignAccount newAccountPk ownerPk)
@?= requireFixture "Assign" fs,
testCase "Transfer" $ do
fs <- getFixtures
dataOf (SP.transfer funderPk newAccountPk 1000000)
@?= requireFixture "Transfer" fs,
testCase "CreateAccountWithSeed" $ do
fs <- getFixtures
dataOf (SP.createAccountWithSeed basePk seedStr funderPk newAccountPk 1000000 165 ownerPk)
@?= requireFixture "CreateAccountWithSeed" fs,
testCase "AdvanceNonceAccount" $ do
fs <- getFixtures
dataOf (SP.advanceNonceAccount newAccountPk authorityPk)
@?= requireFixture "AdvanceNonceAccount" fs,
testCase "WithdrawNonceAccount" $ do
fs <- getFixtures
dataOf (SP.withdrawNonceAccount newAccountPk authorityPk funderPk 1000000)
@?= requireFixture "WithdrawNonceAccount" fs,
testCase "InitializeNonceAccount" $ do
fs <- getFixtures
dataOf (SP.initializeNonceAccount newAccountPk authorityPk)
@?= requireFixture "InitializeNonceAccount" fs,
testCase "AuthorizeNonceAccount" $ do
fs <- getFixtures
dataOf (SP.authorizeNonceAccount newAccountPk basePk authorityPk)
@?= requireFixture "AuthorizeNonceAccount" fs,
testCase "UpgradeNonceAccount" $ do
fs <- getFixtures
dataOf (SP.upgradeNonceAccount newAccountPk)
@?= requireFixture "UpgradeNonceAccount" fs,
testCase "Allocate" $ do
fs <- getFixtures
dataOf (SP.allocate newAccountPk 165)
@?= requireFixture "Allocate" fs,
testCase "AllocateWithSeed" $ do
fs <- getFixtures
dataOf (SP.allocateWithSeed newAccountPk basePk seedStr 165 ownerPk)
@?= requireFixture "AllocateWithSeed" fs,
testCase "AssignWithSeed" $ do
fs <- getFixtures
dataOf (SP.assignWithSeed newAccountPk basePk seedStr ownerPk)
@?= requireFixture "AssignWithSeed" fs,
testCase "TransferWithSeed" $ do
fs <- getFixtures
dataOf (SP.transferWithSeed funderPk basePk seedStr ownerPk newAccountPk 1000000)
@?= requireFixture "TransferWithSeed" fs,
testCase "advanceNonceAccount account metas" $
iAccounts (SP.advanceNonceAccount newAccountPk authorityPk)
@?= [ AccountMeta {accountPubKey = newAccountPk, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = Sysvar.recentBlockhashes, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = authorityPk, isSigner = True, isWritable = False}
],
testCase "withdrawNonceAccount account metas" $
iAccounts (SP.withdrawNonceAccount newAccountPk authorityPk funderPk 1000000)
@?= [ AccountMeta {accountPubKey = newAccountPk, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = funderPk, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = Sysvar.recentBlockhashes, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = Sysvar.rent, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = authorityPk, isSigner = True, isWritable = False}
],
testCase "allocateWithSeed account metas" $
iAccounts (SP.allocateWithSeed newAccountPk basePk seedStr 165 ownerPk)
@?= [ AccountMeta {accountPubKey = newAccountPk, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = basePk, isSigner = True, isWritable = False}
],
testCase "transferWithSeed account metas" $
iAccounts (SP.transferWithSeed funderPk basePk seedStr ownerPk newAccountPk 1000000)
@?= [ AccountMeta {accountPubKey = funderPk, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = basePk, isSigner = True, isWritable = False},
AccountMeta {accountPubKey = newAccountPk, isSigner = False, isWritable = True}
],
testProperty "SystemInstruction Binary round-trip" $
forAll genSystemInstruction $ \si -> decode (encode si) === si,
testCase "decode fails on unknown discriminant" $
case decodeOrFail (BL.pack [13, 0, 0, 0]) :: Either (BL.ByteString, ByteOffset, String) (BL.ByteString, ByteOffset, SP.SystemInstruction) of
Left _ -> pure ()
Right _ -> assertFailure "expected decode failure for discriminant 13",
testCase "getBincodeString rejects length beyond Int range" $
-- discriminant 3 (CreateAccountWithSeed) -> base pk (32 bytes) -> u64 length 2^63
let bytes = BL.pack ([3, 0, 0, 0] <> replicate 32 3 <> [0, 0, 0, 0, 0, 0, 0, 0x80])
in case decodeOrFail bytes :: Either (BL.ByteString, ByteOffset, String) (BL.ByteString, ByteOffset, SP.SystemInstruction) of
Left _ -> pure ()
Right _ -> assertFailure "expected decode failure for absurd length"
]