feat: pass errors between contracts as they were generated in the origin contract
gh-309
This commit is contained in:
@@ -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 () => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.`);
|
||||||
|
|||||||
Reference in New Issue
Block a user