solana-haskell-sdk-1.2.0.0: src/Network/Solana/NativePrograms/SystemProgram.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Solana.NativePrograms.SystemProgram where
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BL
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import GHC.Generics
import Network.Solana.Core.Account (Lamport)
import Network.Solana.Core.Block (BlockHash)
import Network.Solana.Core.Crypto
import Network.Solana.Core.Instruction
import Network.Solana.Sysvar qualified as Sysvar
-- | System program address. This program contain instructions to:
-- create new accounts, allocate account data, assign accounts to owning programs,
-- transfer lamports from System Program owned accounts and pay transaction fees.
systemProgramId :: SolanaPublicKey
systemProgramId = "11111111111111111111111111111111"
data SystemInstruction
= -- | Create a new account
CreateAccount
{ -- | Number of lamports to transfer to the new account
lamports :: Word64,
-- | Number of bytes of memory to allocate
space :: Word64,
-- | Address of program that will own the new account
owner :: SolanaPublicKey
}
| -- | Assign account to a program
Assign
{ -- | Owner program account
owner :: SolanaPublicKey
}
| -- | Transfer lamports
Transfer
{ -- | Transfer amount
lamports :: Word64
}
| CreateAccountWithSeed
{ -- | Base public key
base :: SolanaPublicKey,
-- | String of ASCII chars, no longer than 'Network.Solana.Constants.maxSeedLen'
seed :: String,
-- | Number of lamports to transfer to the new account
lamports :: Word64,
-- | Number of bytes of memory to allocate
space :: Word64,
-- | Address of program that will own the new account
owner :: SolanaPublicKey
}
| -- | Consume a stored nonce, replacing it with a successor
AdvanceNonceAccount
| -- | Withdraw funds from a nonce account
WithdrawNonceAccount
{ -- | Withdraw amount
lamports :: Word64
}
| -- | Drive state of Uninitialized nonce account to Initialized, setting the nonce value
InitializeNonceAccount
{ -- | Entity authorized to execute nonce instructions on the account
authority :: SolanaPublicKey
}
| -- | Change the entity authorized to execute nonce instructions on the account
AuthorizeNonceAccount
{ -- | New nonce authority
authority :: SolanaPublicKey
}
| -- | Allocate space in a (possibly new) account without funding
Allocate
{ -- | Number of bytes of memory to allocate
space :: Word64
}
| -- | Allocate space for and assign an account at an address derived from a base public key and a seed
AllocateWithSeed
{ -- | Base public key
base :: SolanaPublicKey,
-- | String of ASCII chars, no longer than 'Network.Solana.Constants.maxSeedLen'
seed :: String,
-- | Number of bytes of memory to allocate
space :: Word64,
-- | Address of program that will own the account
owner :: SolanaPublicKey
}
| -- | Assign account to a program based on a seed
AssignWithSeed
{ -- | Base public key
base :: SolanaPublicKey,
-- | String of ASCII chars, no longer than 'Network.Solana.Constants.maxSeedLen'
seed :: String,
-- | Owner program account
owner :: SolanaPublicKey
}
| -- | Transfer lamports from a derived address
TransferWithSeed
{ -- | Transfer amount
lamports :: Word64,
-- | Seed to use to derive the funding account address
fromSeed :: String,
-- | Owner to use to derive the funding account address
fromOwner :: SolanaPublicKey
}
| -- | One-time idempotent upgrade of legacy nonce versions to bump them out of chain blockhash domain
UpgradeNonceAccount
deriving (Eq, Show, Generic)
instance Binary SystemInstruction where
put :: SystemInstruction -> Put
put (CreateAccount lamports space owner) = do
putWord32le (0 :: Word32)
putWord64le lamports
putWord64le space
putByteString (getSolanaPublicKeyRaw owner)
put (Assign owner) = do
putWord32le (1 :: Word32)
putByteString (getSolanaPublicKeyRaw owner)
put (Transfer lamports) = do
putWord32le (2 :: Word32)
putWord64le lamports
put (CreateAccountWithSeed base seed lamports space owner) = do
putWord32le (3 :: Word32)
putByteString (getSolanaPublicKeyRaw base)
putBincodeString seed
putWord64le lamports
putWord64le space
putByteString (getSolanaPublicKeyRaw owner)
put AdvanceNonceAccount =
putWord32le (4 :: Word32)
put (WithdrawNonceAccount lamports) = do
putWord32le (5 :: Word32)
putWord64le lamports
put (InitializeNonceAccount authority) = do
putWord32le (6 :: Word32)
putByteString (getSolanaPublicKeyRaw authority)
put (AuthorizeNonceAccount authority) = do
putWord32le (7 :: Word32)
putByteString (getSolanaPublicKeyRaw authority)
put (Allocate space) = do
putWord32le (8 :: Word32)
putWord64le space
put (AllocateWithSeed base seed space owner) = do
putWord32le (9 :: Word32)
putByteString (getSolanaPublicKeyRaw base)
putBincodeString seed
putWord64le space
putByteString (getSolanaPublicKeyRaw owner)
put (AssignWithSeed base seed owner) = do
putWord32le (10 :: Word32)
putByteString (getSolanaPublicKeyRaw base)
putBincodeString seed
putByteString (getSolanaPublicKeyRaw owner)
put (TransferWithSeed lamports fromSeed fromOwner) = do
putWord32le (11 :: Word32)
putWord64le lamports
putBincodeString fromSeed
putByteString (getSolanaPublicKeyRaw fromOwner)
put UpgradeNonceAccount =
putWord32le (12 :: Word32)
get :: Get SystemInstruction
get = do
disc <- getWord32le
case disc of
0 -> CreateAccount <$> getWord64le <*> getWord64le <*> get
1 -> Assign <$> get
2 -> Transfer <$> getWord64le
3 -> CreateAccountWithSeed <$> get <*> getBincodeString <*> getWord64le <*> getWord64le <*> get
4 -> pure AdvanceNonceAccount
5 -> WithdrawNonceAccount <$> getWord64le
6 -> InitializeNonceAccount <$> get
7 -> AuthorizeNonceAccount <$> get
8 -> Allocate <$> getWord64le
9 -> AllocateWithSeed <$> get <*> getBincodeString <*> getWord64le <*> get
10 -> AssignWithSeed <$> get <*> getBincodeString <*> get
11 -> TransferWithSeed <$> getWord64le <*> getBincodeString <*> get
12 -> pure UpgradeNonceAccount
_ -> fail ("SystemInstruction: unknown discriminant " <> show disc)
-- bincode encodes a Rust String as a u64 little-endian byte length
-- followed by the UTF-8 bytes
putBincodeString :: String -> Put
putBincodeString s = do
let bs = TE.encodeUtf8 (T.pack s)
putWord64le (fromIntegral (BS.length bs))
putByteString bs
-- | bincode decodes a Rust String as a u64 little-endian byte length
-- followed by that many UTF-8 bytes.
getBincodeString :: Get String
getBincodeString = do
len <- getWord64le
if len > fromIntegral (maxBound :: Int)
then fail "getBincodeString: length exceeds Int range"
else do
bs <- getByteString (fromIntegral len)
case TE.decodeUtf8' bs of
Left _ -> fail "getBincodeString: invalid UTF-8"
Right t -> pure (T.unpack t)
-- | A nonce account's on-chain state (bincode-encoded @Versions<State>@:
-- a u32 version tag around a u32 state tag).
data NonceState
= NonceUninitialized
| NonceInitialized
{ nsAuthority :: SolanaPublicKey,
nsDurableNonce :: BlockHash,
nsLamportsPerSignature :: Word64
}
deriving (Eq, Show)
-- | Decodes a nonce account's data: a u32 version (0 or 1), then a u32 state
-- discriminant (0 uninitialized, 1 initialized with authority, durable
-- nonce, and fee-rate fields). Trailing bytes (account padding) are allowed.
decodeNonceAccount :: BS.ByteString -> Either String NonceState
decodeNonceAccount bs = case runGetOrFail getNonceState (BL.fromStrict bs) of
Left (_, _, err) -> Left err
Right (_, _, ns) -> Right ns
where
getNonceState = do
version <- getWord32le
if version /= 0 && version /= 1
then fail ("NonceState: unsupported version " <> show version)
else do
st <- getWord32le
case st of
0 -> pure NonceUninitialized
1 -> NonceInitialized <$> get <*> get <*> getWord64le
_ -> fail ("NonceState: unknown state " <> show st)
-- | Creates instruction to "Create a new account"
-- Receives the funding account, the new account, amount to transfer, the number of bytes of memory to allocate and the owner program account.
-- # Account references
-- 0. `[WRITE, SIGNER]` Funding account
-- 1. `[WRITE, SIGNER]` New account
createAccount :: SolanaPublicKey -> SolanaPublicKey -> Lamport -> Int -> SolanaPublicKey -> Instruction
createAccount fundingAccount newAccount lamports space ownerProgramAccount =
mkInstruction
systemProgramId
[ AccountMeta
{ accountPubKey = fundingAccount,
isSigner = True,
isWritable = True
},
AccountMeta
{ accountPubKey = newAccount,
isSigner = True,
isWritable = True
}
]
(CreateAccount (fromIntegral lamports) (fromIntegral space) ownerProgramAccount)
-- | Creates instruction to "Assign account to a program"
-- Receives addresses for the assigned account and owner program account
-- # Account references
-- 0. `[WRITE, SIGNER]` Assigned account public key
assignAccount :: SolanaPublicKey -> SolanaPublicKey -> Instruction
assignAccount assignedAccount ownerProgramAccount =
mkInstruction
systemProgramId
[ AccountMeta
{ accountPubKey = assignedAccount,
isSigner = True,
isWritable = True
}
]
(Assign ownerProgramAccount)
-- | Creates instruction to "Transfer lamports"
-- Receives the funding account, the recipient account and the amount to transfer.
-- # Account references
-- 0. `[WRITE, SIGNER]` Funding account
-- 1. `[WRITE]` Recipient account
transfer :: SolanaPublicKey -> SolanaPublicKey -> Lamport -> Instruction
transfer fundingAccount recipientAccount amount =
mkInstruction
systemProgramId
[ AccountMeta
{ accountPubKey = fundingAccount,
isSigner = True,
isWritable = True
},
AccountMeta
{ accountPubKey = recipientAccount,
isSigner = False,
isWritable = True
}
]
(Transfer (fromIntegral amount))
-- | Creates instruction to "Create a new account at an address derived from a base pubkey and a seed"
-- Receives the funding account, the new account, base pubkey, seed, amount to transfer, the number of bytes of memory to allocate and the owner program account.
-- # Account references
-- 0. `[WRITE, SIGNER]` Funding account
-- 1. `[WRITE]` Created account
-- 2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be
-- provided as a signer, but may be the same as the funding account and provided as account 0
createAccountWithSeed :: SolanaPublicKey -> String -> SolanaPublicKey -> SolanaPublicKey -> Lamport -> Int -> SolanaPublicKey -> Instruction
createAccountWithSeed baseAccount seed fundingAccount newAccount lamports space ownerProgramAccount =
mkInstruction
systemProgramId
[ AccountMeta
{ accountPubKey = fundingAccount,
isSigner = True,
isWritable = True
},
AccountMeta
{ accountPubKey = newAccount,
isSigner = False,
isWritable = True
},
AccountMeta
{ accountPubKey = baseAccount,
isSigner = True,
isWritable = False
}
]
(CreateAccountWithSeed baseAccount seed (fromIntegral lamports) (fromIntegral space) ownerProgramAccount)
-- | Creates instruction to "Consume a stored nonce, replacing it with a successor"
-- Receives the nonce account and the nonce authority.
-- # Account references
-- 0. `[WRITE]` Nonce account
-- 1. `[]` RecentBlockhashes sysvar
-- 2. `[SIGNER]` Nonce authority
advanceNonceAccount :: SolanaPublicKey -> SolanaPublicKey -> Instruction
advanceNonceAccount nonceAccount nonceAuthority =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = nonceAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = Sysvar.recentBlockhashes, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = nonceAuthority, isSigner = True, isWritable = False}
]
AdvanceNonceAccount
-- | Creates instruction to "Withdraw funds from a nonce account"
-- Receives the nonce account, the nonce authority, the recipient account and the amount.
-- # Account references
-- 0. `[WRITE]` Nonce account
-- 1. `[WRITE]` Recipient account
-- 2. `[]` RecentBlockhashes sysvar
-- 3. `[]` Rent sysvar
-- 4. `[SIGNER]` Nonce authority
withdrawNonceAccount :: SolanaPublicKey -> SolanaPublicKey -> SolanaPublicKey -> Lamport -> Instruction
withdrawNonceAccount nonceAccount nonceAuthority recipientAccount amount =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = nonceAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = recipientAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = Sysvar.recentBlockhashes, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = Sysvar.rent, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = nonceAuthority, isSigner = True, isWritable = False}
]
(WithdrawNonceAccount (fromIntegral amount))
-- | Creates instruction to "Drive state of Uninitialized nonce account to Initialized"
-- Receives the nonce account and the entity authorized to execute nonce instructions on it.
-- # Account references
-- 0. `[WRITE]` Nonce account
-- 1. `[]` RecentBlockhashes sysvar
-- 2. `[]` Rent sysvar
initializeNonceAccount :: SolanaPublicKey -> SolanaPublicKey -> Instruction
initializeNonceAccount nonceAccount nonceAuthority =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = nonceAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = Sysvar.recentBlockhashes, isSigner = False, isWritable = False},
AccountMeta {accountPubKey = Sysvar.rent, isSigner = False, isWritable = False}
]
(InitializeNonceAccount nonceAuthority)
-- | Creates instruction to "Change the entity authorized to execute nonce instructions on the account"
-- Receives the nonce account, the current nonce authority and the new nonce authority.
-- # Account references
-- 0. `[WRITE]` Nonce account
-- 1. `[SIGNER]` Nonce authority
authorizeNonceAccount :: SolanaPublicKey -> SolanaPublicKey -> SolanaPublicKey -> Instruction
authorizeNonceAccount nonceAccount nonceAuthority newAuthority =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = nonceAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = nonceAuthority, isSigner = True, isWritable = False}
]
(AuthorizeNonceAccount newAuthority)
-- | Creates instruction to "Upgrade legacy nonce versions"
-- Receives the nonce account.
-- # Account references
-- 0. `[WRITE]` Nonce account
upgradeNonceAccount :: SolanaPublicKey -> Instruction
upgradeNonceAccount nonceAccount =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = nonceAccount, isSigner = False, isWritable = True}
]
UpgradeNonceAccount
-- | Creates instruction to "Allocate space in a (possibly new) account without funding"
-- Receives the account and the number of bytes of memory to allocate.
-- # Account references
-- 0. `[WRITE, SIGNER]` New account
allocate :: SolanaPublicKey -> Int -> Instruction
allocate newAccount space =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = newAccount, isSigner = True, isWritable = True}
]
(Allocate (fromIntegral space))
-- | Creates instruction to "Allocate space for and assign an account at an address derived from a base public key and a seed"
-- Receives the allocated account, base account, seed, the number of bytes and the owner program account.
-- # Account references
-- 0. `[WRITE]` Allocated account
-- 1. `[SIGNER]` Base account
allocateWithSeed :: SolanaPublicKey -> SolanaPublicKey -> String -> Int -> SolanaPublicKey -> Instruction
allocateWithSeed allocatedAccount baseAccount seed space ownerProgramAccount =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = allocatedAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = baseAccount, isSigner = True, isWritable = False}
]
(AllocateWithSeed baseAccount seed (fromIntegral space) ownerProgramAccount)
-- | Creates instruction to "Assign account to a program based on a seed"
-- Receives the assigned account, base account, seed and the owner program account.
-- # Account references
-- 0. `[WRITE]` Assigned account
-- 1. `[SIGNER]` Base account
assignWithSeed :: SolanaPublicKey -> SolanaPublicKey -> String -> SolanaPublicKey -> Instruction
assignWithSeed assignedAccount baseAccount seed ownerProgramAccount =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = assignedAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = baseAccount, isSigner = True, isWritable = False}
]
(AssignWithSeed baseAccount seed ownerProgramAccount)
-- | Creates instruction to "Transfer lamports from a derived address"
-- Receives the funding account, base account, seed, the owner of the funding account, the recipient and the amount.
-- # Account references
-- 0. `[WRITE]` Funding account
-- 1. `[SIGNER]` Base for funding account
-- 2. `[WRITE]` Recipient account
transferWithSeed :: SolanaPublicKey -> SolanaPublicKey -> String -> SolanaPublicKey -> SolanaPublicKey -> Lamport -> Instruction
transferWithSeed fundingAccount baseAccount fromSeed fromOwner recipientAccount amount =
mkInstruction
systemProgramId
[ AccountMeta {accountPubKey = fundingAccount, isSigner = False, isWritable = True},
AccountMeta {accountPubKey = baseAccount, isSigner = True, isWritable = False},
AccountMeta {accountPubKey = recipientAccount, isSigner = False, isWritable = True}
]
(TransferWithSeed (fromIntegral amount) fromSeed fromOwner)