Skip to content

Instantly share code, notes, and snippets.

@steveluscher
Created February 28, 2024 05:40
Show Gist options
  • Save steveluscher/aaa7cbbb5433b1197983908a40860c47 to your computer and use it in GitHub Desktop.
Save steveluscher/aaa7cbbb5433b1197983908a40860c47 to your computer and use it in GitHub Desktop.
/**
* 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