From d1cb12356dc4ae6cb2dcc490c7b00d4895c3b935 Mon Sep 17 00:00:00 2001 From: robal Date: Sat, 11 Mar 2023 00:06:45 +0100 Subject: [PATCH] feat: pass errors between contracts as they were generated in the origin contract gh-309 --- .../internal-write-callee.test.ts | 2 +- src/contract/HandlerBasedContract.ts | 6 +-- .../modules/impl/HandlerExecutorFactory.ts | 7 +-- .../impl/handler/AbstractContractHandler.ts | 2 +- .../modules/impl/handler/WasmHandlerApi.ts | 52 +++++-------------- 5 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/__tests__/integration/internal-writes/internal-write-callee.test.ts b/src/__tests__/integration/internal-writes/internal-write-callee.test.ts index 6a24c4aa..84dcb811 100644 --- a/src/__tests__/integration/internal-writes/internal-write-callee.test.ts +++ b/src/__tests__/integration/internal-writes/internal-write-callee.test.ts @@ -310,7 +310,7 @@ describe('Testing internal writes', () => { }, { strict: true } ) - ).rejects.toThrowError(/^Cannot create interaction: Internal write auto error for call/); + ).rejects.toThrowError(/Internal write auto error for call/); }); it('should not auto throw on default settings during writeInteraction if strict and IW call force to NOT throw an exception', async () => { diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index a5096bea..fc3dfba4 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -371,7 +371,7 @@ export class HandlerBasedContract implements Contract { reward?: string ) { if (this._evaluationOptions.internalWrites) { - // it modifies tags + // it modifies tags await this.discoverInternalWrites(input, tags, transfer, strict, vrf); } @@ -403,7 +403,7 @@ export class HandlerBasedContract implements Contract { : interactionTx.owner; const handlerResult = await this.callContract(input, 'write', caller, undefined, tags, transfer, strict, vrf); if (handlerResult.type !== 'ok') { - throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); + throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage)); } } @@ -1008,7 +1008,7 @@ export class HandlerBasedContract implements Contract { ); if (strict && handlerResult.type !== 'ok') { - throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); + throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage)); } const callStack: ContractCallRecord = this.getCallStack(); const innerWrites = this._innerWritesEvaluator.eval(callStack); diff --git a/src/core/modules/impl/HandlerExecutorFactory.ts b/src/core/modules/impl/HandlerExecutorFactory.ts index d45328a4..fb874894 100644 --- a/src/core/modules/impl/HandlerExecutorFactory.ts +++ b/src/core/modules/impl/HandlerExecutorFactory.ts @@ -23,9 +23,9 @@ import { Buffer } from 'warp-isomorphic'; // eslint-disable-next-line const BigNumber = require('bignumber.js'); -export class ContractError extends Error { - constructor(message, readonly subtype?: string) { - super(message); +export class ContractError extends Error { + constructor(readonly error: T, readonly subtype?: string) { + super(error.toString()); this.name = 'ContractError'; } } @@ -277,6 +277,7 @@ export type HandlerResult = { export type InteractionResult = HandlerResult & { type: InteractionResultType; errorMessage?: string; + error?: unknown; originalValidity?: Record; originalErrorMessages?: Record; }; diff --git a/src/core/modules/impl/handler/AbstractContractHandler.ts b/src/core/modules/impl/handler/AbstractContractHandler.ts index 92d29522..d32c11ff 100644 --- a/src/core/modules/impl/handler/AbstractContractHandler.ts +++ b/src/core/modules/impl/handler/AbstractContractHandler.ts @@ -88,7 +88,7 @@ export abstract class AbstractContractHandler implements HandlerApi extends AbstractContractHandler { @@ -44,32 +44,21 @@ export class WasmHandlerApi extends AbstractContractHandler { }; } catch (e) { await this.swGlobal.kv.rollback(); - // note: as exceptions handling in WASM is currently somewhat non-existent - // https://www.assemblyscript.org/status.html#exceptions - // and since we have to somehow differentiate different types of exceptions - // - each exception message has to have a proper prefix added. - - // exceptions with prefix [RE:] ("Runtime Exceptions") should break the execution immediately - // - eg: [RE:OOG] - [RuntimeException: OutOfGas] - - // exception with prefix [CE:] ("Contract Exceptions") should be logged, but should not break - // the state evaluation - as they are considered as contracts' business exception (eg. validation errors) - // - eg: [CE:ITT] - [ContractException: InvalidTokenTransfer] const result = { errorMessage: e.message, state: currentResult.state, result: null }; - if (e.message.startsWith('[RE:')) { - this.logger.fatal(e); + if (e instanceof ContractError) { return { ...result, - type: 'exception' + error: e.error, + type: 'error' }; } else { return { ...result, - type: 'error' + type: 'exception' }; } } finally { @@ -95,32 +84,15 @@ export class WasmHandlerApi extends AbstractContractHandler { const handleResult = action.interactionType === 'write' ? await this.wasmExports.warpContractWrite(action.input) : await this.wasmExports.warpContractView(action.input); - if (Object.prototype.hasOwnProperty.call(handleResult, 'WriteResponse')) { - return handleResult.WriteResponse; + if (!handleResult) { + return; } - if (Object.prototype.hasOwnProperty.call(handleResult, 'ViewResponse')) { - return handleResult.ViewResponse; - } - { - this.logger.error('Error from rust', handleResult); - let errorKey; - let errorArgs = ''; - if (typeof handleResult.Err === 'string' || handleResult.Err instanceof String) { - errorKey = handleResult.Err; - } else if ('kind' in handleResult.Err) { - errorKey = handleResult.Err.kind; - errorArgs = 'data' in handleResult.Err ? ' ' + handleResult.Err.data : ''; - } else { - errorKey = Object.keys(handleResult.Err)[0]; - errorArgs = ' ' + handleResult.Err[errorKey]; - } - - if (errorKey == 'RuntimeError') { - throw new Error(`[RE:RE]${errorArgs}`); - } else { - throw new Error(`[CE:${errorKey}${errorArgs}]`); - } + if (handleResult.type === 'ok') { + return handleResult.result; } + this.logger.error('Error from rust', handleResult); + if (handleResult.type === 'error') throw new ContractError(handleResult.error); + throw new Error(handleResult.errorMessage); } default: { throw new Error(`Support for ${this.contractDefinition.srcWasmLang} not implemented yet.`);