330 lines
10 KiB
Go
330 lines
10 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
type TransactionErrorVariant uint32
|
|
type InstructionErrorVariant uint32
|
|
|
|
type InstructionErrorEnum struct {
|
|
Index uint8
|
|
Variant InstructionErrorVariant
|
|
rest []byte
|
|
}
|
|
|
|
type CustomInstructionErrorEnum struct {
|
|
Code uint32
|
|
}
|
|
|
|
const (
|
|
AccountInUse TransactionErrorVariant = iota
|
|
AccountLoadedTwice
|
|
AccountNotFound
|
|
ProgramAccountNotFound
|
|
InsufficientFundsForFee
|
|
InvalidAccountForFee
|
|
AlreadyProcessed
|
|
BlockhashNotFound
|
|
|
|
InstructionError //InstructionError(u8, InstructionError),
|
|
|
|
CallChainTooDeep
|
|
MissingSignatureForFee
|
|
InvalidAccountIndex
|
|
SignatureFailure
|
|
InvalidProgramForExecution
|
|
SanitizeFailure
|
|
ClusterMaintenance
|
|
AccountBorrowOutstanding
|
|
WouldExceedMaxBlockCostLimit
|
|
UnsupportedVersion
|
|
InvalidWritableAccount
|
|
WouldExceedMaxAccountCostLimit
|
|
WouldExceedAccountDataBlockLimit
|
|
TooManyAccountLocks
|
|
AddressLookupTableNotFound
|
|
InvalidAddressLookupTableOwner
|
|
InvalidAddressLookupTableData
|
|
InvalidAddressLookupTableIndex
|
|
InvalidRentPayingAccount
|
|
WouldExceedMaxVoteCostLimit
|
|
WouldExceedAccountDataTotalLimit
|
|
|
|
DuplicateInstruction //DuplicateInstruction(u8),
|
|
|
|
/*
|
|
InsufficientFundsForRent {
|
|
account_index: u8,
|
|
},
|
|
*/
|
|
InsufficientFundsForRent
|
|
|
|
MaxLoadedAccountsDataSizeExceeded
|
|
InvalidLoadedAccountsDataSizeLimit
|
|
ResanitizationNeeded
|
|
|
|
/*
|
|
ProgramExecutionTemporarilyRestricted {
|
|
account_index: u8,
|
|
},
|
|
*/
|
|
ProgramExecutionTemporarilyRestricted
|
|
|
|
UnbalancedTransaction
|
|
ProgramCacheHitMaxLimit
|
|
CommitCancelled
|
|
)
|
|
|
|
const (
|
|
GenericError InstructionErrorVariant = iota
|
|
// InvalidArgument / The arguments provided to a program were invalid
|
|
InvalidArgument
|
|
// InvalidInstructionData / An instruction's data contents were invalid
|
|
InvalidInstructionData
|
|
// InvalidAccountData / An account's data contents was invalid
|
|
InvalidAccountData
|
|
// AccountDataTooSmall / An account's data was too small
|
|
AccountDataTooSmall
|
|
// InsufficientFunds / An account's balance was too small to complete the instruction
|
|
InsufficientFunds
|
|
// IncorrectProgramId / The account did not have the expected program id
|
|
IncorrectProgramId
|
|
// MissingRequiredSignature / A signature was required but not found
|
|
MissingRequiredSignature
|
|
// AccountAlreadyInitialized / An initialize instruction was sent to an account that has already been initialized.
|
|
AccountAlreadyInitialized
|
|
// UninitializedAccount / An attempt to operate on an account that hasn't been initialized.
|
|
UninitializedAccount
|
|
// UnbalancedInstruction / Program's instruction lamport balance does not equal the balance after the instruction
|
|
UnbalancedInstruction
|
|
// ModifiedProgramId / Program illegally modified an account's program id
|
|
ModifiedProgramId
|
|
// ExternalAccountLamportSpend / Program spent the lamports of an account that doesn't belong to it
|
|
ExternalAccountLamportSpend
|
|
// ExternalAccountDataModified / Program modified the data of an account that doesn't belong to it
|
|
ExternalAccountDataModified
|
|
// ReadonlyLamportChange / Read-only account's lamports modified
|
|
ReadonlyLamportChange
|
|
// ReadonlyDataModified / Read-only account's data was modified
|
|
ReadonlyDataModified
|
|
// DuplicateAccountIndex / An account was referenced more than once in a single instruction
|
|
// Deprecated, instructions can now contain duplicate accounts
|
|
DuplicateAccountIndex
|
|
// ExecutableModified / Executable bit on account changed, but shouldn't have
|
|
ExecutableModified
|
|
// RentEpochModified / Rent_epoch account changed, but shouldn't have
|
|
RentEpochModified
|
|
// NotEnoughAccountKeys / The instruction expected additional account keys
|
|
NotEnoughAccountKeys
|
|
// AccountDataSizeChanged / Program other than the account's owner changed the size of the account data
|
|
AccountDataSizeChanged
|
|
// AccountNotExecutable / The instruction expected an executable account
|
|
AccountNotExecutable
|
|
// AccountBorrowFailed / Failed to borrow a reference to account data, already borrowed
|
|
AccountBorrowFailed
|
|
// InstructionAccountBorrowOutstanding / Account data has an outstanding reference after a program's execution
|
|
InstructionAccountBorrowOutstanding
|
|
// DuplicateAccountOutOfSync / The same account was multiply passed to an on-chain program's entrypoint, but the program
|
|
/// modified them differently. A program can only modify one instance of the account because
|
|
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
|
|
DuplicateAccountOutOfSync
|
|
/// Allows on-chain programs to implement program-specific error types and see them returned
|
|
/// by the Solana runtime. A program-specific error may be any type that is represented as
|
|
/// or serialized to a u32 integer.
|
|
|
|
Custom // Custom(u32),
|
|
|
|
// InvalidError / The return value from the program was invalid. Valid errors are either a defined builtin
|
|
/// error value or a user-defined error in the lower 32 bits.
|
|
InvalidError
|
|
// ExecutableDataModified / Executable account's data was modified
|
|
ExecutableDataModified
|
|
// ExecutableLamportChange / Executable account's lamports modified
|
|
ExecutableLamportChange
|
|
// ExecutableAccountNotRentExempt / Executable accounts must be rent exempt
|
|
ExecutableAccountNotRentExempt
|
|
// UnsupportedProgramId / Unsupported program id
|
|
UnsupportedProgramId
|
|
// CallDepth / Cross-program invocation call depth too deep
|
|
CallDepth
|
|
// MissingAccount / An account required by the instruction is missing
|
|
MissingAccount
|
|
// ReentrancyNotAllowed / Cross-program invocation reentrancy not allowed for this instruction
|
|
ReentrancyNotAllowed
|
|
// MaxSeedLengthExceeded / Length of the seed is too long for address generation
|
|
MaxSeedLengthExceeded
|
|
// InvalidSeeds / Provided seeds do not result in a valid address
|
|
InvalidSeeds
|
|
// InvalidRealloc / Failed to reallocate account data of this length
|
|
InvalidRealloc
|
|
// ComputationalBudgetExceeded / Computational budget exceeded
|
|
ComputationalBudgetExceeded
|
|
// PrivilegeEscalation / Cross-program invocation with unauthorized signer or writable account
|
|
PrivilegeEscalation
|
|
// ProgramEnvironmentSetupFailure / Failed to create program execution environment
|
|
ProgramEnvironmentSetupFailure
|
|
// ProgramFailedToComplete / Program failed to complete
|
|
ProgramFailedToComplete
|
|
// ProgramFailedToCompile / Program failed to compile
|
|
ProgramFailedToCompile
|
|
// Immutable / Account is immutable
|
|
Immutable
|
|
// IncorrectAuthority / Incorrect authority provided
|
|
IncorrectAuthority
|
|
/// Failed to serialize or deserialize account data
|
|
///
|
|
/// Warning: This error should never be emitted by the runtime.
|
|
///
|
|
/// This error includes strings from the underlying 3rd party Borsh crate
|
|
/// which can be dangerous because the error strings could change across
|
|
/// Borsh versions. Only programs can use this error because they are
|
|
/// consistent across Solana software versions.
|
|
/// string values from this error should not be used in
|
|
|
|
BorshIoError // BorshIoError(String)
|
|
|
|
// AccountNotRentExempt An account does not have enough lamports to be rent-exempt
|
|
AccountNotRentExempt
|
|
// InvalidAccountOwner Invalid account owner
|
|
InvalidAccountOwner
|
|
// ArithmeticOverflow Program arithmetic overflowed
|
|
ArithmeticOverflow
|
|
// UnsupportedSysvar Unsupported sysvar
|
|
UnsupportedSysvar
|
|
// IllegalOwner Illegal account owner
|
|
IllegalOwner
|
|
// MaxAccountsDataAllocationsExceeded / Accounts data allocations exceeded the maximum allowed per transaction
|
|
MaxAccountsDataAllocationsExceeded
|
|
// MaxAccountsExceeded Max accounts exceeded
|
|
MaxAccountsExceeded
|
|
// MaxInstructionTraceLengthExceeded Max instruction trace length exceeded
|
|
MaxInstructionTraceLengthExceeded
|
|
// BuiltinProgramsMustConsumeComputeUnits Builtin programs must consume compute units
|
|
BuiltinProgramsMustConsumeComputeUnits
|
|
)
|
|
|
|
type TransactionError struct {
|
|
Variant TransactionErrorVariant
|
|
rest []byte
|
|
}
|
|
|
|
type TransactionParsedError struct {
|
|
Index uint8
|
|
Variant TransactionErrorVariant
|
|
Enum InstructionErrorVariant
|
|
CustomCode uint32
|
|
|
|
UnKnown string
|
|
}
|
|
|
|
var (
|
|
ErrInvalidTransactionError = errors.New("invalid transaction error")
|
|
NotAnInstructionError = errors.New("not an instruction error")
|
|
NotACustomInstructionError = errors.New("not a custom instruction error")
|
|
UnsupportedInstructionError = errors.New("unsupported instruction error")
|
|
)
|
|
|
|
func DecodeTransactionError(data []byte) (*TransactionError, error) {
|
|
if len(data) < 4 {
|
|
return nil, ErrInvalidTransactionError
|
|
}
|
|
|
|
var err TransactionError
|
|
variant := binary.LittleEndian.Uint32(data[:4])
|
|
if variant > uint32(CommitCancelled) {
|
|
return nil, UnsupportedInstructionError
|
|
}
|
|
err.Variant = TransactionErrorVariant(variant)
|
|
err.rest = data[4:]
|
|
|
|
return &err, nil
|
|
}
|
|
|
|
func ParseTransactionErrorFromGeyser(data []byte) *TransactionParsedError {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
transactionError, err := DecodeTransactionError(data)
|
|
if err != nil {
|
|
return &TransactionParsedError{
|
|
UnKnown: string(data),
|
|
}
|
|
}
|
|
|
|
enumErr, err := transactionError.GetInstructionError()
|
|
if err != nil {
|
|
return &TransactionParsedError{
|
|
Variant: transactionError.Variant,
|
|
UnKnown: string(data),
|
|
}
|
|
}
|
|
if enumErr.Variant != Custom {
|
|
return &TransactionParsedError{
|
|
Index: enumErr.Index,
|
|
Variant: transactionError.Variant,
|
|
Enum: enumErr.Variant,
|
|
}
|
|
}
|
|
customCode, err := enumErr.Custom()
|
|
if err != nil {
|
|
return &TransactionParsedError{
|
|
Index: enumErr.Index,
|
|
Variant: transactionError.Variant,
|
|
Enum: enumErr.Variant,
|
|
UnKnown: string(data),
|
|
}
|
|
}
|
|
|
|
return &TransactionParsedError{
|
|
Index: enumErr.Index,
|
|
Variant: transactionError.Variant,
|
|
Enum: enumErr.Variant,
|
|
CustomCode: customCode.Code,
|
|
}
|
|
}
|
|
|
|
func (e *TransactionError) GetCustomErrorCode() (uint8, uint32, error) {
|
|
instr, err := e.GetInstructionError()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
custom, err := instr.Custom()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return instr.Index, custom.Code, nil
|
|
}
|
|
|
|
func (e *TransactionError) GetInstructionError() (*InstructionErrorEnum, error) {
|
|
if e.Variant != InstructionError {
|
|
return nil, NotAnInstructionError
|
|
}
|
|
if len(e.rest) < 5 {
|
|
return nil, NotAnInstructionError
|
|
}
|
|
|
|
var err InstructionErrorEnum
|
|
err.Index = e.rest[0]
|
|
variant := binary.LittleEndian.Uint32(e.rest[1:5])
|
|
if variant > uint32(BuiltinProgramsMustConsumeComputeUnits) {
|
|
return nil, UnsupportedInstructionError
|
|
}
|
|
err.Variant = InstructionErrorVariant(binary.LittleEndian.Uint32(e.rest[1:5]))
|
|
err.rest = e.rest[5:]
|
|
return &err, nil
|
|
}
|
|
|
|
func (e *InstructionErrorEnum) Custom() (*CustomInstructionErrorEnum, error) {
|
|
if e.Variant != Custom {
|
|
return nil, NotACustomInstructionError
|
|
}
|
|
if len(e.rest) < 4 {
|
|
return nil, NotACustomInstructionError
|
|
}
|
|
var custom CustomInstructionErrorEnum
|
|
custom.Code = binary.LittleEndian.Uint32(e.rest[:4])
|
|
return &custom, nil
|
|
}
|