-
-
Save steveluscher/aaa7cbbb5433b1197983908a40860c47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Oh, hello. You might wonder what in tarnation is going on here. Allow me to explain. | |
* | |
* One of the goals of `@solana/errors` is to allow error messages that are not interesting to your | |
* application shake out of your app bundle in production. This means that we must never export maps | |
* of error codes/messages. | |
* | |
* Unfortunately, where transaction errors from the RPC are concerned, we have no choice but to keep | |
* a map between the Rust `TransactionError` enum name and the JavaScript `SolanaError` code. In the | |
* interest of implementing that map in as few bytes as possible, we do the following: | |
* | |
* 1. Reserve sequential error codes for `TransactionError` in the range [7050000-7050999] | |
* 2. Produce as compact a hash as possible from the ordered `TransactionError` enum names | |
* 3. Match the hash of transaction error names from the RPC with the index of that hash in the | |
* static map, and reconstruct the `SolanaError` code by adding 7050001 to that index | |
*/ | |
import { SolanaErrorCode } from './codes'; | |
import { SolanaErrorContext } from './context'; | |
import { SolanaError } from './error'; | |
/** | |
* Adding an error | |
* | |
* 1. Follow the instructions in `./codes.ts` to add a corresponding Solana error code | |
* 2. Prepare an *ordered* array of error names from the Rust enum `InstructionError` | |
* 3. Hash those error names | |
* ```ts | |
* ORDERED_ERROR_NAMES.map(goodEnoughHash).map(h => `0x${h.toString(16)}`) | |
* ``` | |
* 4. Ensure that the hashes are unique. If they are not, adjust `goodEnoughHash` and repeat. | |
* ```ts | |
* if (new Set(hashes).size !== ORDERED_ERROR_NAMES.length) throw new Error('Hash collision'); | |
* ``` | |
* 5. Update `ORDERED_ERROR_HASHES` with the new hashes. Include comments with the error name. | |
* 6. Add the new errors to `./__tests__/transaction-error-test.ts` | |
*/ | |
const ORDERED_ERROR_HASHES = [ | |
104, // AccountInUse | |
672, // AccountLoadedTwice | |
582, // AccountNotFound | |
940, // ProgramAccountNotFound | |
307, // InsufficientFundsForFee | |
86, // InvalidAccountForFee | |
856, // AlreadyProcessed | |
644, // BlockhashNotFound | |
12, // InstructionError | |
246, // CallChainTooDeep | |
784, // MissingSignatureForFee | |
676, // InvalidAccountIndex | |
858, // SignatureFailure | |
602, // InvalidProgramForExecution | |
27, // SanitizeFailure | |
15, // ClusterMaintenance | |
540, // AccountBorrowOutstanding | |
936, // WouldExceedMaxBlockCostLimit | |
638, // UnsupportedVersion | |
136, // InvalidWritableAccount | |
800, // WouldExceedMaxAccountCostLimit | |
576, // WouldExceedAccountDataBlockLimit | |
322, // TooManyAccountLocks | |
592, // AddressLookupTableNotFound | |
918, // InvalidAddressLookupTableOwner | |
732, // InvalidAddressLookupTableData | |
348, // InvalidAddressLookupTableIndex | |
264, // InvalidRentPayingAccount | |
160, // WouldExceedMaxVoteCostLimit | |
324, // WouldExceedAccountDataTotalLimit | |
616, // DuplicateInstruction | |
496, // InsufficientFundsForRent | |
620, // MaxLoadedAccountsDataSizeExceeded | |
148, // InvalidLoadedAccountsDataSizeLimit | |
556, // ResanitizationNeeded | |
284, // ProgramExecutionTemporarilyRestricted | |
824, // UnbalancedTransaction | |
]; | |
function goodEnoughHash(str: string): number { | |
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function | |
let hash = | |
// FNV offset basis (32-bit) | |
0x811c9dc5; | |
for (let ii = 0; ii < str.length; ) { | |
hash ^= str.charCodeAt(ii++); | |
hash *= | |
// FNV prime number (32-bit) | |
16777619; | |
} | |
return hash & 0x3ff; | |
} | |
export function getSolanaErrorFromTransactionError(transactionError: string | { [key: string]: unknown }): SolanaError { | |
let errorName; | |
let errorContext; | |
if (typeof transactionError === 'string') { | |
errorName = transactionError; | |
} else { | |
errorName = Object.keys(transactionError)[0]; | |
errorContext = transactionError[errorName]; | |
} | |
const codeOffset = ORDERED_ERROR_HASHES.indexOf(goodEnoughHash(errorName)); | |
const err = new SolanaError( | |
(7050001 + codeOffset) as SolanaErrorCode, | |
errorContext as SolanaErrorContext[SolanaErrorCode], | |
); | |
if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') { | |
Error.captureStackTrace(err, getSolanaErrorFromTransactionError); | |
} | |
return err; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment