From b63530ae558ce7a9d3027fd487aba04255d9a3a9 Mon Sep 17 00:00:00 2001 From: gagliardetto Date: Sun, 3 Nov 2024 20:58:40 +0100 Subject: [PATCH] Fix error conversion --- solana-errors/from-json-to-protobuf.go | 248 +++++++++++++++++++ solana-errors/parser.go | 322 +++++++++++++++++++++++++ 2 files changed, 570 insertions(+) create mode 100644 solana-errors/from-json-to-protobuf.go create mode 100644 solana-errors/parser.go diff --git a/solana-errors/from-json-to-protobuf.go b/solana-errors/from-json-to-protobuf.go new file mode 100644 index 00000000..2714e890 --- /dev/null +++ b/solana-errors/from-json-to-protobuf.go @@ -0,0 +1,248 @@ +package solanaerrors + +import ( + "bytes" + "encoding/json" + "fmt" + + bin "github.com/gagliardetto/binary" +) + +func FromJSONToProtobuf(j map[string]interface{}) ([]byte, error) { + // get first key + firstKey := getFirstKey(j) + if firstKey == "" { + return nil, fmt.Errorf("no keys found in map") + } + buf := new(bytes.Buffer) + wr := bin.NewBinEncoder(buf) + doer := &ChainOps{} + switch firstKey { + case InstructionError: + { + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(TransactionErrorType_INSTRUCTION_ERROR), bin.LE) + }) + + { + // read instructionErrorType + arr, ok := j[InstructionError].([]interface{}) + if !ok { + return nil, fmt.Errorf("expected an array") + } + if len(arr) != 2 { + return nil, fmt.Errorf("expected an array of length 2") + } + instructionErrorCodeFloat, ok := arr[0].(float64) + if !ok { + return nil, fmt.Errorf("expected a float64, got %T", arr[0]) + } + + instructionErrorCode := uint8(instructionErrorCodeFloat) + doer.Do("write errorCode", func() error { + return wr.WriteUint8(instructionErrorCode) + }) + + { + switch as := arr[1].(type) { + case string: + { + var found bool + // if string, then map instructionErrorTypeName to code + ixLoop: + for k, v := range InstructionErrorType_name { + // TODO: the conversion to PascalCase might be wrong and break things. + if bin.ToPascalCase(v) == as { + doer.Do("write instructionErrorType", func() error { + return wr.WriteUint32(uint32(k), bin.LE) + }) + found = true + break ixLoop + } + } + if !found { + return nil, fmt.Errorf("unknown error type: %q", as) + } + } + case map[string]interface{}: + { + // if object, then it's custom + firstKey := getFirstKey(as) + if firstKey == "" { + return nil, fmt.Errorf("no keys found in map") + } + if firstKey != "Custom" { + return nil, fmt.Errorf("expected a Custom key") + } + doer.Do("write customErrorType", func() error { + return wr.WriteUint32(uint32(InstructionErrorType_CUSTOM), bin.LE) + }) + customErrorTypeFloat, ok := as[firstKey].(float64) + if !ok { + return nil, fmt.Errorf("expected a float64") + } + customErrorType := uint32(customErrorTypeFloat) + doer.Do("write customErrorType", func() error { + return wr.WriteUint32(customErrorType, bin.LE) + }) + } + default: + return nil, fmt.Errorf("unhandled type %T", arr[1]) + } + } + + } + + err := doer.Err() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil + } + case InsufficientFundsForRent: + { + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(TransactionErrorType_INSUFFICIENT_FUNDS_FOR_RENT), bin.LE) + }) + // write the accountIndex + { + // "{\"InsufficientFundsForRent\":{\"account_index\":2}}" + // read accountIndex + object, ok := j[InsufficientFundsForRent].(map[string]any) + if !ok { + return nil, fmt.Errorf("expected an object") + } + accountIndexFloat, ok := object["account_index"].(float64) + if !ok { + return nil, fmt.Errorf("expected a float64") + } + accountIndex := uint8(accountIndexFloat) + doer.Do("write accountIndex", func() error { + return wr.WriteUint8(accountIndex) + }) + + if err := doer.Err(); err != nil { + return nil, err + } + } + return buf.Bytes(), nil + } + case ProgramExecutionTemporarilyRestricted: + { + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(TransactionErrorType_PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED), bin.LE) + }) + // write the accountIndex + { + // "{\"ProgramExecutionTemporarilyRestricted\":{\"account_index\":2}}" + // read accountIndex + object, ok := j[ProgramExecutionTemporarilyRestricted].(map[string]any) + if !ok { + return nil, fmt.Errorf("expected an object") + } + accountIndexFloat, ok := object["account_index"].(float64) + if !ok { + return nil, fmt.Errorf("expected a float64") + } + accountIndex := uint8(accountIndexFloat) + doer.Do("write accountIndex", func() error { + return wr.WriteUint8(accountIndex) + }) + + if err := doer.Err(); err != nil { + return nil, err + } + } + return buf.Bytes(), nil + } + case DuplicateInstruction: + { + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(TransactionErrorType_DUPLICATE_INSTRUCTION), bin.LE) + }) + // write the instruction index + { + // "{\"DuplicateInstruction\":[2]}" + // read instructionIndex + arr, ok := j[DuplicateInstruction].([]interface{}) + if !ok { + return nil, fmt.Errorf("expected an array") + } + if len(arr) != 1 { + return nil, fmt.Errorf("expected an array of length 1") + } + instructionIndexFloat, ok := arr[0].(float64) + if !ok { + return nil, fmt.Errorf("expected a float64") + } + instructionIndex := uint8(instructionIndexFloat) + doer.Do("write instructionIndex", func() error { + return wr.WriteUint8(instructionIndex) + }) + + if err := doer.Err(); err != nil { + return nil, err + } + } + return buf.Bytes(), nil + } + + default: + // it's one of the single-value errors + { + // iterate over TransactionErrorType_name and find the matching key + var found bool + for k, v := range TransactionErrorType_name { + if bin.ToPascalCase(v) == firstKey { + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(k), bin.LE) + }) + found = true + break + } + } + if !found { + return nil, fmt.Errorf("unknown error type: %q", firstKey) + } + } + + err := doer.Err() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil + } +} + +func toJsonString(v interface{}) string { + j, err := json.Marshal(v) + if err != nil { + return fmt.Sprintf("%v", v) + } + return string(j) +} + +func getFirstKey(m map[string]interface{}) string { + for k := range m { + return k + } + return "" +} + +type ChainOps struct { + e error +} + +func (c *ChainOps) Do(name string, f func() error) *ChainOps { + if c.e != nil { + return c + } + c.e = f() + return c +} + +func (c *ChainOps) Err() error { + return c.e +} diff --git a/solana-errors/parser.go b/solana-errors/parser.go new file mode 100644 index 00000000..f384cb36 --- /dev/null +++ b/solana-errors/parser.go @@ -0,0 +1,322 @@ +package solanaerrors + +// InstructionError(u8, InstructionError), +// where InstructionError is `Custom(u32),` (i.e. a tuple of 2 elements) + +// "err": { +// "InstructionError": [ +// 2, +// { +// "Custom": 6302 +// } +// ] +// }, + +// InstructionError(u8, InstructionError), +// where InstructionError is `InvalidInstructionData,` (i.e. a string) +// "err": { +// "InstructionError": [ +// 2, +// "InvalidInstructionData" +// ] +// }, + +// InstructionError(u8, InstructionError), +// where InstructionError is `IncorrectProgramId,` (i.e. a string) +// "err": { +// "InstructionError": [ +// 0, +// "IncorrectProgramId" +// ] +// }, + +// NOTE: +// - InstructionError(u8, InstructionError), +// - DuplicateInstruction(u8), +// - InsufficientFundsForRent { account_index: u8 }, +// - ProgramExecutionTemporarilyRestricted { account_index: u8 }, +// pub enum TransactionError { +const ( + // AccountInUse, + AccountInUse = "AccountInUse" + + // AccountLoadedTwice, + AccountLoadedTwice = "AccountLoadedTwice" + + // AccountNotFound, + AccountNotFound = "AccountNotFound" + + // ProgramAccountNotFound, + ProgramAccountNotFound = "ProgramAccountNotFound" + + // InsufficientFundsForFee, + InsufficientFundsForFee = "InsufficientFundsForFee" + + // InvalidAccountForFee, + InvalidAccountForFee = "InvalidAccountForFee" + + // AlreadyProcessed, + AlreadyProcessed = "AlreadyProcessed" + + // BlockhashNotFound, + BlockhashNotFound = "BlockhashNotFound" + + // InstructionError(u8, InstructionError), + InstructionError = "InstructionError" + + // CallChainTooDeep, + CallChainTooDeep = "CallChainTooDeep" + + // MissingSignatureForFee, + MissingSignatureForFee = "MissingSignatureForFee" + + // InvalidAccountIndex, + InvalidAccountIndex = "InvalidAccountIndex" + + // SignatureFailure, + SignatureFailure = "SignatureFailure" + + // InvalidProgramForExecution, + InvalidProgramForExecution = "InvalidProgramForExecution" + + // SanitizeFailure, + SanitizeFailure = "SanitizeFailure" + + // ClusterMaintenance, + ClusterMaintenance = "ClusterMaintenance" + + // AccountBorrowOutstanding, + AccountBorrowOutstanding = "AccountBorrowOutstanding" + + // WouldExceedMaxBlockCostLimit, + WouldExceedMaxBlockCostLimit = "WouldExceedMaxBlockCostLimit" + + // UnsupportedVersion, + UnsupportedVersion = "UnsupportedVersion" + + // InvalidWritableAccount, + InvalidWritableAccount = "InvalidWritableAccount" + + // WouldExceedMaxAccountCostLimit, + WouldExceedMaxAccountCostLimit = "WouldExceedMaxAccountCostLimit" + + // WouldExceedAccountDataBlockLimit, + WouldExceedAccountDataBlockLimit = "WouldExceedAccountDataBlockLimit" + + // TooManyAccountLocks, + TooManyAccountLocks = "TooManyAccountLocks" + + // AddressLookupTableNotFound, + AddressLookupTableNotFound = "AddressLookupTableNotFound" + + // InvalidAddressLookupTableOwner, + InvalidAddressLookupTableOwner = "InvalidAddressLookupTableOwner" + + // InvalidAddressLookupTableData, + InvalidAddressLookupTableData = "InvalidAddressLookupTableData" + + // InvalidAddressLookupTableIndex, + InvalidAddressLookupTableIndex = "InvalidAddressLookupTableIndex" + + // InvalidRentPayingAccount, + InvalidRentPayingAccount = "InvalidRentPayingAccount" + + // WouldExceedMaxVoteCostLimit, + WouldExceedMaxVoteCostLimit = "WouldExceedMaxVoteCostLimit" + + // WouldExceedAccountDataTotalLimit, + WouldExceedAccountDataTotalLimit = "WouldExceedAccountDataTotalLimit" + + // DuplicateInstruction(u8), + DuplicateInstruction = "DuplicateInstruction" + + // InsufficientFundsForRent { account_index: u8 }, + InsufficientFundsForRent = "InsufficientFundsForRent" + + // MaxLoadedAccountsDataSizeExceeded, + MaxLoadedAccountsDataSizeExceeded = "MaxLoadedAccountsDataSizeExceeded" + + // InvalidLoadedAccountsDataSizeLimit, + InvalidLoadedAccountsDataSizeLimit = "InvalidLoadedAccountsDataSizeLimit" + + // ResanitizationNeeded, + ResanitizationNeeded = "ResanitizationNeeded" + + // ProgramExecutionTemporarilyRestricted { account_index: u8 }, + ProgramExecutionTemporarilyRestricted = "ProgramExecutionTemporarilyRestricted" + + // UnbalancedTransaction, + UnbalancedTransaction = "UnbalancedTransaction" + + // ProgramCacheHitMaxLimit, + ProgramCacheHitMaxLimit = "ProgramCacheHitMaxLimit" +) + +// NOTE: +// - Custom(u32), +// - BorshIoError(String), + +// pub enum InstructionError { +const ( + // GenericError, + GenericError = "GenericError" + + // InvalidArgument, + InvalidArgument = "InvalidArgument" + + // InvalidInstructionData, + InvalidInstructionData = "InvalidInstructionData" + + // InvalidAccountData, + InvalidAccountData = "InvalidAccountData" + + // AccountDataTooSmall, + AccountDataTooSmall = "AccountDataTooSmall" + + // InsufficientFunds, + InsufficientFunds = "InsufficientFunds" + + // IncorrectProgramId, + IncorrectProgramId = "IncorrectProgramId" + + // MissingRequiredSignature, + MissingRequiredSignature = "MissingRequiredSignature" + + // AccountAlreadyInitialized, + AccountAlreadyInitialized = "AccountAlreadyInitialized" + + // UninitializedAccount, + UninitializedAccount = "UninitializedAccount" + + // UnbalancedInstruction, + UnbalancedInstruction = "UnbalancedInstruction" + + // ModifiedProgramId, + ModifiedProgramId = "ModifiedProgramId" + + // ExternalAccountLamportSpend, + ExternalAccountLamportSpend = "ExternalAccountLamportSpend" + + // ExternalAccountDataModified, + ExternalAccountDataModified = "ExternalAccountDataModified" + + // ReadonlyLamportChange, + ReadonlyLamportChange = "ReadonlyLamportChange" + + // ReadonlyDataModified, + ReadonlyDataModified = "ReadonlyDataModified" + + // DuplicateAccountIndex, + DuplicateAccountIndex = "DuplicateAccountIndex" + + // ExecutableModified, + ExecutableModified = "ExecutableModified" + + // RentEpochModified, + RentEpochModified = "RentEpochModified" + + // NotEnoughAccountKeys, + NotEnoughAccountKeys = "NotEnoughAccountKeys" + + // AccountDataSizeChanged, + AccountDataSizeChanged = "AccountDataSizeChanged" + + // AccountNotExecutable, + AccountNotExecutable = "AccountNotExecutable" + + // AccountBorrowFailed, + AccountBorrowFailed = "AccountBorrowFailed" + + // AccountBorrowOutstanding, + Instruction_AccountBorrowOutstanding = "AccountBorrowOutstanding" + + // DuplicateAccountOutOfSync, + DuplicateAccountOutOfSync = "DuplicateAccountOutOfSync" + + // Custom(u32), + Custom = "Custom" + + // InvalidError, + InvalidError = "InvalidError" + + // ExecutableDataModified, + ExecutableDataModified = "ExecutableDataModified" + + // ExecutableLamportChange, + ExecutableLamportChange = "ExecutableLamportChange" + + // ExecutableAccountNotRentExempt, + ExecutableAccountNotRentExempt = "ExecutableAccountNotRentExempt" + + // UnsupportedProgramId, + UnsupportedProgramId = "UnsupportedProgramId" + + // CallDepth, + CallDepth = "CallDepth" + + // MissingAccount, + MissingAccount = "MissingAccount" + + // ReentrancyNotAllowed, + ReentrancyNotAllowed = "ReentrancyNotAllowed" + + // MaxSeedLengthExceeded, + MaxSeedLengthExceeded = "MaxSeedLengthExceeded" + + // InvalidSeeds, + InvalidSeeds = "InvalidSeeds" + + // InvalidRealloc, + InvalidRealloc = "InvalidRealloc" + + // ComputationalBudgetExceeded, + ComputationalBudgetExceeded = "ComputationalBudgetExceeded" + + // PrivilegeEscalation, + PrivilegeEscalation = "PrivilegeEscalation" + + // ProgramEnvironmentSetupFailure, + ProgramEnvironmentSetupFailure = "ProgramEnvironmentSetupFailure" + + // ProgramFailedToComplete, + ProgramFailedToComplete = "ProgramFailedToComplete" + + // ProgramFailedToCompile, + ProgramFailedToCompile = "ProgramFailedToCompile" + + // Immutable, + Immutable = "Immutable" + + // IncorrectAuthority, + IncorrectAuthority = "IncorrectAuthority" + + // BorshIoError(String), + BorshIoError = "BorshIoError" + + // AccountNotRentExempt, + AccountNotRentExempt = "AccountNotRentExempt" + + // InvalidAccountOwner, + InvalidAccountOwner = "InvalidAccountOwner" + + // ArithmeticOverflow, + ArithmeticOverflow = "ArithmeticOverflow" + + // UnsupportedSysvar, + UnsupportedSysvar = "UnsupportedSysvar" + + // IllegalOwner, + IllegalOwner = "IllegalOwner" + + // MaxAccountsDataAllocationsExceeded, + MaxAccountsDataAllocationsExceeded = "MaxAccountsDataAllocationsExceeded" + + // MaxAccountsExceeded, + MaxAccountsExceeded = "MaxAccountsExceeded" + + // MaxInstructionTraceLengthExceeded, + MaxInstructionTraceLengthExceeded = "MaxInstructionTraceLengthExceeded" + + // BuiltinProgramsMustConsumeComputeUnits, + BuiltinProgramsMustConsumeComputeUnits = "BuiltinProgramsMustConsumeComputeUnits" +)