fix: prevent IW if no 'interact-write' tag is present

This commit is contained in:
Piotr Pędziwiatr
2024-03-08 16:08:07 +01:00
committed by Tadeuchi
parent 25b2abd351
commit 8fd023c148
2 changed files with 43 additions and 13 deletions

View File

@@ -118,4 +118,10 @@ export class TagsParser {
return t.name == WARP_TAGS.REQUEST_VRF && t.value === 'true'; return t.name == WARP_TAGS.REQUEST_VRF && t.value === 'true';
}); });
} }
hasInteractWriteTag(interaction: GQLNodeInterface, contractTxId: string): boolean {
return interaction.tags.some((t) => {
return t.name == WARP_TAGS.INTERACT_WRITE && t.value === contractTxId;
});
}
} }

View File

@@ -6,9 +6,12 @@ import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global';
import { LoggerFactory } from '../../../../logging/LoggerFactory'; import { LoggerFactory } from '../../../../logging/LoggerFactory';
import { deepCopy } from '../../../../utils/utils'; import { deepCopy } from '../../../../utils/utils';
import { ContractError, HandlerApi, InteractionData, InteractionResult } from '../HandlerExecutorFactory'; import { ContractError, HandlerApi, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
import { TagsParser } from '../TagsParser';
import { Contract } from '../../../../contract/Contract';
export abstract class AbstractContractHandler<State> implements HandlerApi<State> { export abstract class AbstractContractHandler<State> implements HandlerApi<State> {
protected logger = LoggerFactory.INST.create('ContractHandler'); protected logger = LoggerFactory.INST.create('ContractHandler');
private readonly tagsParser = new TagsParser();
protected constructor( protected constructor(
protected readonly swGlobal: SmartWeaveGlobal, protected readonly swGlobal: SmartWeaveGlobal,
@@ -46,30 +49,39 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
if (!executionContext.evaluationOptions.internalWrites) { if (!executionContext.evaluationOptions.internalWrites) {
throw new Error("Internal writes feature switched off. Change EvaluationOptions.internalWrites flag to 'true'"); throw new Error("Internal writes feature switched off. Change EvaluationOptions.internalWrites flag to 'true'");
} }
const effectiveThrowOnError =
throwOnError == undefined ? executionContext.evaluationOptions.throwOnInternalWriteError : throwOnError;
const debugData = { const debugData = {
from: this.contractDefinition.txId, from: this.contractDefinition.txId,
to: contractTxId, to: contractTxId,
input input
}; };
const activeTx = this.swGlobal._activeTx;
if (
!activeTx.dry &&
!this.tagsParser.hasInteractWriteTag(activeTx, contractTxId) &&
!this.isWriteBack(executionContext.contract, contractTxId)
) {
throw new Error(
`No interact write tag for interaction ${activeTx.id}: ${JSON.stringify(debugData)} \n ${JSON.stringify(
activeTx
)}`
);
}
const effectiveThrowOnError =
throwOnError == undefined ? executionContext.evaluationOptions.throwOnInternalWriteError : throwOnError;
this.logger.debug('swGlobal.write call:', debugData); this.logger.debug('swGlobal.write call:', debugData);
// The contract that we want to call and modify its state // The contract that we want to call and modify its state
const calleeContract = executionContext.warp.contract(contractTxId, executionContext.contract, { const calleeContract = executionContext.warp.contract(contractTxId, executionContext.contract, {
callingInteraction: this.swGlobal._activeTx, callingInteraction: activeTx,
callType: 'write' callType: 'write'
}); });
const result = await calleeContract.applyInput<Input>(input, this.swGlobal._activeTx, executionContext.signal); const result = await calleeContract.applyInput<Input>(input, activeTx, executionContext.signal);
this.logger.debug('Cache result?:', !this.swGlobal._activeTx.dry); this.logger.debug('Cache result?:', !activeTx.dry);
const shouldAutoThrow = const shouldAutoThrow =
result.type !== 'ok' && result.type !== 'ok' && effectiveThrowOnError && (!activeTx.dry || (activeTx.dry && activeTx.strict));
effectiveThrowOnError &&
(!this.swGlobal._activeTx.dry || (this.swGlobal._activeTx.dry && this.swGlobal._activeTx.strict));
const effectiveErrorMessage = shouldAutoThrow const effectiveErrorMessage = shouldAutoThrow
? `Internal write auto error for call [${JSON.stringify(debugData)}]: ${result.errorMessage}` ? `Internal write auto error for call [${JSON.stringify(debugData)}]: ${result.errorMessage}`
@@ -78,7 +90,7 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
const resultErrorMessages = effectiveErrorMessage const resultErrorMessages = effectiveErrorMessage
? { ? {
...result.originalErrorMessages, ...result.originalErrorMessages,
[this.swGlobal._activeTx.id]: effectiveErrorMessage [activeTx.id]: effectiveErrorMessage
} }
: result.originalErrorMessages; : result.originalErrorMessages;
calleeContract.interactionState().update( calleeContract.interactionState().update(
@@ -87,11 +99,11 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
state: result.state as State, state: result.state as State,
validity: { validity: {
...result.originalValidity, ...result.originalValidity,
[this.swGlobal._activeTx.id]: result.type == 'ok' [activeTx.id]: result.type == 'ok'
}, },
errorMessages: resultErrorMessages errorMessages: resultErrorMessages
}, },
this.swGlobal._activeTx.sortKey activeTx.sortKey
); );
if (shouldAutoThrow) { if (shouldAutoThrow) {
@@ -180,4 +192,16 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
.get(this.swGlobal.contract.id, this.swGlobal._activeTx.sortKey)?.state; .get(this.swGlobal.contract.id, this.swGlobal._activeTx.sortKey)?.state;
}; };
} }
private isWriteBack(contract: Contract<State>, target: string): boolean {
let current = contract as Contract;
while (current.parent()) {
current = current.parent();
if (current.txId() === target) {
return true;
}
}
return false;
}
} }