feat: pass errors between contracts as they were generated in the origin contract

gh-309
This commit is contained in:
robal
2023-03-11 00:06:45 +01:00
parent 6cd1bf1ce4
commit d1cb12356d
5 changed files with 21 additions and 48 deletions

View File

@@ -310,7 +310,7 @@ describe('Testing internal writes', () => {
}, },
{ strict: true } { 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 () => { it('should not auto throw on default settings during writeInteraction if strict and IW call force to NOT throw an exception', async () => {

View File

@@ -371,7 +371,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
reward?: string reward?: string
) { ) {
if (this._evaluationOptions.internalWrites) { if (this._evaluationOptions.internalWrites) {
// it modifies tags // it modifies tags
await this.discoverInternalWrites<Input>(input, tags, transfer, strict, vrf); await this.discoverInternalWrites<Input>(input, tags, transfer, strict, vrf);
} }
@@ -403,7 +403,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
: interactionTx.owner; : interactionTx.owner;
const handlerResult = await this.callContract(input, 'write', caller, undefined, tags, transfer, strict, vrf); const handlerResult = await this.callContract(input, 'write', caller, undefined, tags, transfer, strict, vrf);
if (handlerResult.type !== 'ok') { 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<State> implements Contract<State> {
); );
if (strict && handlerResult.type !== 'ok') { 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 callStack: ContractCallRecord = this.getCallStack();
const innerWrites = this._innerWritesEvaluator.eval(callStack); const innerWrites = this._innerWritesEvaluator.eval(callStack);

View File

@@ -23,9 +23,9 @@ import { Buffer } from 'warp-isomorphic';
// eslint-disable-next-line // eslint-disable-next-line
const BigNumber = require('bignumber.js'); const BigNumber = require('bignumber.js');
export class ContractError extends Error { export class ContractError<T> extends Error {
constructor(message, readonly subtype?: string) { constructor(readonly error: T, readonly subtype?: string) {
super(message); super(error.toString());
this.name = 'ContractError'; this.name = 'ContractError';
} }
} }
@@ -277,6 +277,7 @@ export type HandlerResult<State, Result> = {
export type InteractionResult<State, Result> = HandlerResult<State, Result> & { export type InteractionResult<State, Result> = HandlerResult<State, Result> & {
type: InteractionResultType; type: InteractionResultType;
errorMessage?: string; errorMessage?: string;
error?: unknown;
originalValidity?: Record<string, boolean>; originalValidity?: Record<string, boolean>;
originalErrorMessages?: Record<string, string>; originalErrorMessages?: Record<string, string>;
}; };

View File

@@ -88,7 +88,7 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
}); });
if (shouldAutoThrow) { if (shouldAutoThrow) {
throw new ContractError(effectiveErrorMessage); throw new ContractError(result.type === 'error' && result.error ? result.error : effectiveErrorMessage);
} }
return result; return result;

View File

@@ -3,7 +3,7 @@ import { ContractDefinition } from '../../../../core/ContractDefinition';
import { ExecutionContext } from '../../../../core/ExecutionContext'; import { ExecutionContext } from '../../../../core/ExecutionContext';
import { EvalStateResult } from '../../../../core/modules/StateEvaluator'; import { EvalStateResult } from '../../../../core/modules/StateEvaluator';
import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global'; import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global';
import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory'; import { ContractError, ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
import { AbstractContractHandler } from './AbstractContractHandler'; import { AbstractContractHandler } from './AbstractContractHandler';
export class WasmHandlerApi<State> extends AbstractContractHandler<State> { export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
@@ -44,32 +44,21 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
}; };
} catch (e) { } catch (e) {
await this.swGlobal.kv.rollback(); 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 = { const result = {
errorMessage: e.message, errorMessage: e.message,
state: currentResult.state, state: currentResult.state,
result: null result: null
}; };
if (e.message.startsWith('[RE:')) { if (e instanceof ContractError) {
this.logger.fatal(e);
return { return {
...result, ...result,
type: 'exception' error: e.error,
type: 'error'
}; };
} else { } else {
return { return {
...result, ...result,
type: 'error' type: 'exception'
}; };
} }
} finally { } finally {
@@ -95,32 +84,15 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
const handleResult = action.interactionType === 'write' ? await this.wasmExports.warpContractWrite(action.input) : await this.wasmExports.warpContractView(action.input); const handleResult = action.interactionType === 'write' ? await this.wasmExports.warpContractWrite(action.input) : await this.wasmExports.warpContractView(action.input);
if (Object.prototype.hasOwnProperty.call(handleResult, 'WriteResponse')) { if (!handleResult) {
return handleResult.WriteResponse; return;
} }
if (Object.prototype.hasOwnProperty.call(handleResult, 'ViewResponse')) { if (handleResult.type === 'ok') {
return handleResult.ViewResponse; return handleResult.result;
}
{
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}]`);
}
} }
this.logger.error('Error from rust', handleResult);
if (handleResult.type === 'error') throw new ContractError(handleResult.error);
throw new Error(handleResult.errorMessage);
} }
default: { default: {
throw new Error(`Support for ${this.contractDefinition.srcWasmLang} not implemented yet.`); throw new Error(`Support for ${this.contractDefinition.srcWasmLang} not implemented yet.`);