diff --git a/packages/near-api-js/fetch_error_schema.js b/packages/near-api-js/fetch_error_schema.js index cdaf29b60d..3308b3ff25 100644 --- a/packages/near-api-js/fetch_error_schema.js +++ b/packages/near-api-js/fetch_error_schema.js @@ -2,7 +2,7 @@ const https = require('https'); const fs = require('fs'); const ERROR_SCHEMA_URL = - 'https://raw.githubusercontent.com/nearprotocol/nearcore/4c1149974ccf899dbcb2253a3e27cbab86dc47be/chain/jsonrpc/res/rpc_errors_schema.json'; + 'https://raw.githubusercontent.com/near/nearcore/master/chain/jsonrpc/res/rpc_errors_schema.json'; const TARGET_DIR = process.argv[2] || process.cwd() + '/src/generated'; const TARGET_SCHEMA_FILE_PATH = TARGET_DIR + '/rpc_error_schema.json'; diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 9ca89f8a10..ec64297522 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -49,7 +49,9 @@ "compile": "tsc -p ./tsconfig.json", "dev": "pnpm compile -w", "build": "pnpm compile && pnpm browserify", - "test": "jest test", + "test": "concurrently \"pnpm:test:*\"", + "test:jest": "jest test", + "test:errorsjson": "node fetch_error_schema.js && git diff --exit-code", "lint": "concurrently \"pnpm:lint:*(!fix)\"", "lint:src": "eslint --ext .ts src", "lint:test": "eslint --ext .js test", diff --git a/packages/near-api-js/src/generated/rpc_error_schema.json b/packages/near-api-js/src/generated/rpc_error_schema.json index e5ac6cec66..4401321ff6 100644 --- a/packages/near-api-js/src/generated/rpc_error_schema.json +++ b/packages/near-api-js/src/generated/rpc_error_schema.json @@ -1,5 +1,12 @@ { "schema": { + "AltBn128InvalidInput": { + "name": "AltBn128InvalidInput", + "subtypes": [], + "props": { + "msg": "" + } + }, "BadUTF16": { "name": "BadUTF16", "subtypes": [], @@ -15,21 +22,6 @@ "subtypes": [], "props": {} }, - "BreakpointTrap": { - "name": "BreakpointTrap", - "subtypes": [], - "props": {} - }, - "CacheError": { - "name": "CacheError", - "subtypes": [ - "ReadError", - "WriteError", - "DeserializationError", - "SerializationError" - ], - "props": {} - }, "CallIndirectOOB": { "name": "CallIndirectOOB", "subtypes": [], @@ -81,29 +73,25 @@ "subtypes": [], "props": {} }, - "DeserializationError": { - "name": "DeserializationError", + "ECRecoverError": { + "name": "ECRecoverError", "subtypes": [], - "props": {} + "props": { + "msg": "" + } + }, + "Ed25519VerifyInvalidInput": { + "name": "Ed25519VerifyInvalidInput", + "subtypes": [], + "props": { + "msg": "" + } }, "EmptyMethodName": { "name": "EmptyMethodName", "subtypes": [], "props": {} }, - "FunctionCallError": { - "name": "FunctionCallError", - "subtypes": [ - "CompilationError", - "LinkError", - "MethodResolveError", - "WasmTrap", - "WasmUnknownError", - "HostError", - "EvmError" - ], - "props": {} - }, "GasExceeded": { "name": "GasExceeded", "subtypes": [], @@ -163,7 +151,10 @@ "NumberInputDataDependenciesExceeded", "ReturnedValueLengthExceeded", "ContractSizeExceeded", - "Deprecated" + "Deprecated", + "ECRecoverError", + "AltBn128InvalidInput", + "Ed25519VerifyInvalidInput" ], "props": {} }, @@ -177,6 +168,11 @@ "subtypes": [], "props": {} }, + "IndirectCallToNull": { + "name": "IndirectCallToNull", + "subtypes": [], + "props": {} + }, "Instantiate": { "name": "Instantiate", "subtypes": [], @@ -259,13 +255,6 @@ "limit": "" } }, - "LinkError": { - "name": "LinkError", - "subtypes": [], - "props": { - "msg": "" - } - }, "Memory": { "name": "Memory", "subtypes": [], @@ -300,17 +289,11 @@ "name": "MethodResolveError", "subtypes": [ "MethodEmptyName", - "MethodUTF8Error", "MethodNotFound", "MethodInvalidSignature" ], "props": {} }, - "MethodUTF8Error": { - "name": "MethodUTF8Error", - "subtypes": [], - "props": {} - }, "MisalignedAtomicAccess": { "name": "MisalignedAtomicAccess", "subtypes": [], @@ -348,7 +331,9 @@ "GasInstrumentation", "StackHeightInstrumentation", "Instantiate", - "Memory" + "Memory", + "TooManyFunctions", + "TooManyLocals" ], "props": {} }, @@ -359,11 +344,6 @@ "method_name": "" } }, - "ReadError": { - "name": "ReadError", - "subtypes": [], - "props": {} - }, "ReturnedValueLengthExceeded": { "name": "ReturnedValueLengthExceeded", "subtypes": [], @@ -377,13 +357,6 @@ "subtypes": [], "props": {} }, - "SerializationError": { - "name": "SerializationError", - "subtypes": [], - "props": { - "hash": "" - } - }, "StackHeightInstrumentation": { "name": "StackHeightInstrumentation", "subtypes": [], @@ -394,6 +367,16 @@ "subtypes": [], "props": {} }, + "TooManyFunctions": { + "name": "TooManyFunctions", + "subtypes": [], + "props": {} + }, + "TooManyLocals": { + "name": "TooManyLocals", + "subtypes": [], + "props": {} + }, "TotalLogLengthExceeded": { "name": "TotalLogLengthExceeded", "subtypes": [], @@ -424,17 +407,12 @@ "CallIndirectOOB", "IllegalArithmetic", "MisalignedAtomicAccess", - "BreakpointTrap", + "IndirectCallToNull", "StackOverflow", "GenericTrap" ], "props": {} }, - "WasmUnknownError": { - "name": "WasmUnknownError", - "subtypes": [], - "props": {} - }, "WasmerCompileError": { "name": "WasmerCompileError", "subtypes": [], @@ -442,11 +420,6 @@ "msg": "" } }, - "WriteError": { - "name": "WriteError", - "subtypes": [], - "props": {} - }, "AccessKeyNotFound": { "name": "AccessKeyNotFound", "subtypes": [], @@ -486,7 +459,8 @@ "InsufficientStake", "FunctionCallError", "NewReceiptValidationError", - "OnlyImplicitAccountCreationAllowed" + "OnlyImplicitAccountCreationAllowed", + "DeleteAccountWithLargeState" ], "props": { "index": "" @@ -589,6 +563,13 @@ "account_id": "" } }, + "DeleteAccountWithLargeState": { + "name": "DeleteAccountWithLargeState", + "subtypes": [], + "props": { + "account_id": "" + } + }, "DeleteActionMustBeFinal": { "name": "DeleteActionMustBeFinal", "subtypes": [], @@ -707,6 +688,7 @@ "InvalidSignerId", "SignerDoesNotExist", "InvalidNonce", + "NonceTooLarge", "InvalidReceiverId", "InvalidSignature", "NotEnoughBalance", @@ -714,7 +696,8 @@ "CostOverflow", "InvalidChain", "Expired", - "ActionsValidation" + "ActionsValidation", + "TransactionSizeExceeded" ], "props": {} }, @@ -733,6 +716,14 @@ "method_name": "" } }, + "NonceTooLarge": { + "name": "NonceTooLarge", + "subtypes": [], + "props": { + "tx_nonce": "", + "upper_bound": "" + } + }, "NotEnoughAllowance": { "name": "NotEnoughAllowance", "subtypes": [], @@ -808,6 +799,14 @@ "total_prepaid_gas": "" } }, + "TransactionSizeExceeded": { + "name": "TransactionSizeExceeded", + "subtypes": [], + "props": { + "limit": "", + "size": "" + } + }, "TriesToStake": { "name": "TriesToStake", "subtypes": [], @@ -845,18 +844,12 @@ "subtypes": [], "props": {} }, - "InternalError": { - "name": "InternalError", - "subtypes": [], - "props": {} - }, "ServerError": { "name": "ServerError", "subtypes": [ "TxExecutionError", "Timeout", - "Closed", - "InternalError" + "Closed" ], "props": {} }, diff --git a/packages/near-api-js/src/providers/json-rpc-provider.ts b/packages/near-api-js/src/providers/json-rpc-provider.ts index 960543e6ef..ae9cdca59c 100644 --- a/packages/near-api-js/src/providers/json-rpc-provider.ts +++ b/packages/near-api-js/src/providers/json-rpc-provider.ts @@ -28,7 +28,7 @@ import { ConnectionInfo, fetchJson } from '../utils/web'; import { TypedError, ErrorContext } from '../utils/errors'; import { baseEncode } from 'borsh'; import exponentialBackoff from '../utils/exponential-backoff'; -import { parseRpcError, getErrorTypeFromErrorMessage } from '../utils/rpc_errors'; +import { parseRpcError, getErrorTypeFromErrorMessage, ServerError, formatError } from '../utils/rpc_errors'; import { SignedTransaction } from '../transaction'; /** @hidden */ @@ -347,7 +347,13 @@ export class JsonRpcProvider extends Provider { throw new TypedError(errorMessage, 'TimeoutError'); } - throw new TypedError(errorMessage, getErrorTypeFromErrorMessage(response.error.data, response.error.name)); + const errorType = getErrorTypeFromErrorMessage(response.error.data, response.error.name); + throw new TypedError(formatError(errorType, params) || errorMessage, errorType); + } + } else if (typeof response.result?.error === 'string') { + const errorType = getErrorTypeFromErrorMessage(response.result.error, ''); + if (errorType) { + throw new ServerError(formatError(errorType, { method }), errorType); } } // Success when response.error is not exist diff --git a/packages/near-api-js/src/utils/rpc_errors.ts b/packages/near-api-js/src/utils/rpc_errors.ts index 01e5621a5b..9ec8fc77de 100644 --- a/packages/near-api-js/src/utils/rpc_errors.ts +++ b/packages/near-api-js/src/utils/rpc_errors.ts @@ -96,6 +96,8 @@ export function getErrorTypeFromErrorMessage(errorMessage, errorType) { return 'AccessKeyDoesNotExist'; case /wasm execution failed with error: FunctionCallError\(CompilationError\(CodeDoesNotExist/.test(errorMessage): return 'CodeDoesNotExist'; + case /wasm execution failed with error: FunctionCallError\(MethodResolveError\(MethodNotFound/.test(errorMessage): + return 'MethodNotFound'; case /Transaction nonce \d+ must be larger than nonce of the used access key \d+/.test(errorMessage): return 'InvalidNonce'; default: diff --git a/packages/near-api-js/test/providers_errors.test.js b/packages/near-api-js/test/providers_errors.test.js new file mode 100644 index 0000000000..c97d82bd50 --- /dev/null +++ b/packages/near-api-js/test/providers_errors.test.js @@ -0,0 +1,70 @@ +const nearApi = require('../src/index'); +const testUtils = require('./test-utils'); +let ERRORS_JSON = require('../src/res/error_messages.json'); + +jest.setTimeout(20000); + +const withProvider = (fn) => { + const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test')); + const provider = new nearApi.providers.JsonRpcProvider(config.nodeUrl); + return () => fn(provider); +}; + +test('JSON RPC Error: MethodNotFound', withProvider(async (provider) => { + const near = await testUtils.setUpTestConnection(); + const account = await testUtils.createAccount(near); + const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); + + await contract.setValue({ args: { value: 'hello' } }); + + try { + const response = await provider.query({ + request_type: 'call_function', + finality: 'final', + account_id: contract.contractId, + method_name: 'methodNameThatDoesNotExist', + args_base64: '' + }); + expect(response).toBeUndefined(); + } catch (e) { + const errorType = 'MethodNotFound'; + expect(e.type).toEqual(errorType); + expect(e.message).toEqual(ERRORS_JSON[errorType]); + } + +})); + +test('JSON RPC Error: CodeDoesNotExist', withProvider(async (provider) => { + try { + const response = await provider.query({ + request_type: 'call_function', + finality: 'final', + account_id: 'test.near', + method_name: 'methodNameThatDoesNotExistOnContractNotDeployed', + args_base64: '' + }); + expect(response).toBeUndefined(); + } catch (e) { + const errorType = 'CodeDoesNotExist'; + expect(e.type).toEqual(errorType); + expect(e.message.split(' ').slice(0, 5)).toEqual(ERRORS_JSON[errorType].split(' ').slice(0, 5)); + } +})); + +test('JSON RPC Error: AccountDoesNotExist', withProvider(async (provider) => { + const accountName = 'abc.near'; + try { + const response = await provider.query({ + request_type: 'call_function', + finality: 'final', + account_id: accountName, + method_name: 'methodNameThatDoesNotExistOnContractNotDeployed', + args_base64: '' + }); + expect(response).toBeUndefined(); + } catch (e) { + const errorType = 'AccountDoesNotExist'; + expect(e.type).toEqual(errorType); + expect(e.message.split(' ').slice(0, 5)).toEqual(ERRORS_JSON[errorType].split(' ').slice(0, 5)); + } +})); \ No newline at end of file diff --git a/packages/near-api-js/test/utils/rpc-errors.test.js b/packages/near-api-js/test/utils/rpc-errors.test.js index a176edfe38..136d58656c 100644 --- a/packages/near-api-js/test/utils/rpc-errors.test.js +++ b/packages/near-api-js/test/utils/rpc-errors.test.js @@ -16,7 +16,7 @@ describe('rpc-errors', () => { } }; let error = parseRpcError(rpc_error); - expect(error.type === 'AccountAlreadyExists').toBe(true); + expect(error.type).toBe('AccountAlreadyExists'); expect(error.index).toBe(1); expect(error.account_id).toBe('bob.near'); expect(formatError(error.type, error)).toBe('Can\'t create a new account bob.near, because it already exists'); @@ -36,7 +36,7 @@ describe('rpc-errors', () => { } }; let error = parseRpcError(rpc_error); - expect(error.type === 'ReceiverMismatch').toBe(true); + expect(error.type).toBe('ReceiverMismatch'); expect(error.ak_receiver).toBe('test.near'); expect(error.tx_receiver).toBe('bob.near'); expect(formatError(error.type, error)).toBe( @@ -57,9 +57,9 @@ describe('rpc-errors', () => { } }; let error = parseRpcError(rpc_error); - expect(error.type === 'InvalidIteratorIndex').toBe(true); - expect(error.iterator_index).toBe(42); - expect(formatError(error.type, error)).toBe('Iterator index 42 does not exist'); + expect(error.type).toBe('ActionError'); + expect(error.message).toBe('{"kind":{"FunctionCallError":{"HostError":{"InvalidIteratorIndex":{"iterator_index":42}}}}}'); + expect(error.message.includes('InvalidIteratorIndex')).toBe(true); }); test('test ActionError::FunctionCallError::GasLimitExceeded error', async () => { @@ -74,15 +74,15 @@ describe('rpc-errors', () => { } }; let error = parseRpcError(rpc_error); - expect(error.type === 'GasLimitExceeded').toBe(true); - - expect(formatError(error.type, error)).toBe('Exceeded the maximum amount of gas allowed to burn per contract'); + expect(error.type).toBe('ActionError'); + console.log(error.message); + expect(error.message).toBe('{"index":0,"kind":{"index":0,"kind":{"FunctionCallError":{"HostError":"GasLimitExceeded"}}}}'); }); test('test parse error object', async () => { const errorStr = '{"status":{"Failure":{"ActionError":{"index":0,"kind":{"FunctionCallError":{"EvmError":"ArgumentParseError"}}}}},"transaction":{"signer_id":"test.near","public_key":"ed25519:D5HVgBE8KgXkSirDE4UQ8qwieaLAR4wDDEgrPRtbbNep","nonce":110,"receiver_id":"evm","actions":[{"FunctionCall":{"method_name":"transfer","args":"888ZO7SvECKvfSCJ832LrnFXuF/QKrSGztwAAA==","gas":300000000000000,"deposit":"0"}}],"signature":"ed25519:7JtWQ2Ux63ixaKy7bTDJuRTWnv6XtgE84ejFMMjYGKdv2mLqPiCfkMqbAPt5xwLWwFdKjJniTcxWZe7FdiRWpWv","hash":"E1QorKKEh1WLJwRQSQ1pdzQN3f8yeFsQQ8CbJjnz1ZQe"},"transaction_outcome":{"proof":[],"block_hash":"HXXBPjGp65KaFtam7Xr67B8pZVGujZMZvTmVW6Fy9tXf","id":"E1QorKKEh1WLJwRQSQ1pdzQN3f8yeFsQQ8CbJjnz1ZQe","outcome":{"logs":[],"receipt_ids":["ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL"],"gas_burnt":2428001493624,"tokens_burnt":"2428001493624000000000","executor_id":"test.near","status":{"SuccessReceiptId":"ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL"}}},"receipts_outcome":[{"proof":[],"block_hash":"H6fQCVpxBDv9y2QtmTVHoxHibJvamVsHau7fDi7AmFa2","id":"ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL","outcome":{"logs":[],"receipt_ids":["DgRyf1Wv3ZYLFvM8b67k2yZjdmnyUUJtRkTxAwoFi3qD"],"gas_burnt":2428001493624,"tokens_burnt":"2428001493624000000000","executor_id":"evm","status":{"Failure":{"ActionError":{"index":0,"kind":{"FunctionCallError":{"EvmError":"ArgumentParseError"}}}}}}},{"proof":[],"block_hash":"9qNVA235L9XdZ8rZLBAPRNBbiGPyNnMUfpbi9WxbRdbB","id":"DgRyf1Wv3ZYLFvM8b67k2yZjdmnyUUJtRkTxAwoFi3qD","outcome":{"logs":[],"receipt_ids":[],"gas_burnt":0,"tokens_burnt":"0","executor_id":"test.near","status":{"SuccessValue":""}}}]}'; const error = parseRpcError(JSON.parse(errorStr).status.Failure); - expect(error).toEqual(new ServerError('{"index":0,"kind":{"EvmError":"ArgumentParseError"}}')); + expect(error).toEqual(new ServerError('{"index":0,"kind":{"index":0,"kind":{"FunctionCallError":{"EvmError":"ArgumentParseError"}}}}')); }); test('test getErrorTypeFromErrorMessage', () => {