diff --git a/solana-errors/from-json-to-protobuf.go b/solana-errors/from-json-to-protobuf.go index b271da45..2714e890 100644 --- a/solana-errors/from-json-to-protobuf.go +++ b/solana-errors/from-json-to-protobuf.go @@ -19,108 +19,200 @@ func FromJSONToProtobuf(j map[string]interface{}) ([]byte, error) { 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) + doer.Do("write transactionErrorType", func() error { + return wr.WriteUint32(uint32(TransactionErrorType_INSTRUCTION_ERROR), bin.LE) }) { - switch as := arr[1].(type) { - case string: - { - // 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) - }) - break ixLoop + // 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") + 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) + }) } - 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 + } - err := doer.Err() - if err != nil { - return nil, err + return buf.Bytes(), nil } - - 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") + 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 + } } - accountIndex := uint8(accountIndexFloat) - doer.Do("write accountIndex", func() error { - return wr.WriteUint8(accountIndex) + 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 + 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) + }) - return buf.Bytes(), nil + if err := doer.Err(); err != nil { + return nil, err + } + } + return buf.Bytes(), nil + } default: - return nil, fmt.Errorf("unhandled error type: %s from %q", firstKey, toJsonString(j)) + // 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 } } @@ -139,11 +231,6 @@ func getFirstKey(m map[string]interface{}) string { return "" } -const ( - InstructionError = "InstructionError" - InsufficientFundsForRent = "InsufficientFundsForRent" -) - type ChainOps struct { e error } 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" +)