From 28123c9412de31ee1d6e0a23fe1e1c9e959b3b86 Mon Sep 17 00:00:00 2001 From: ppedziwiatr Date: Thu, 9 Nov 2023 12:10:10 +0100 Subject: [PATCH] feat: throw error if trying to write to kv storage in a view function --- src/core/modules/impl/handler/JsHandlerApi.ts | 12 +++++------ .../modules/impl/handler/WasmHandlerApi.ts | 1 + src/legacy/smartweave-global.ts | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/core/modules/impl/handler/JsHandlerApi.ts b/src/core/modules/impl/handler/JsHandlerApi.ts index 1866f22d..a398d564 100644 --- a/src/core/modules/impl/handler/JsHandlerApi.ts +++ b/src/core/modules/impl/handler/JsHandlerApi.ts @@ -144,15 +144,16 @@ export class JsHandlerApi extends AbstractContractHandler { try { await this.swGlobal.kv.open(); - if (interaction.interactionType === 'write') { - await this.swGlobal.kv.begin(); - } + await this.swGlobal.kv.begin(); const handlerResult = await Promise.race([timeoutPromise, this.contractFunction(stateClone, interaction)]); if (handlerResult && (handlerResult.state !== undefined || handlerResult.result !== undefined)) { if (interaction.interactionType === 'write') { await this.swGlobal.kv.commit(); + } else { + // view state function should not change anything in the kv storage + await this.swGlobal.kv.rollback(); } let interactionEvent: InteractionCompleteEvent = null; @@ -181,9 +182,7 @@ export class JsHandlerApi extends AbstractContractHandler { // Will be caught below as unexpected exception. throw new Error(`Unexpected result from contract: ${JSON.stringify(handlerResult)}`); } catch (err) { - if (interaction.interactionType === 'write') { - await this.swGlobal.kv.rollback(); - } + await this.swGlobal.kv.rollback(); switch (err.name) { case KnownErrors.ContractError: return { @@ -238,6 +237,7 @@ export class JsHandlerApi extends AbstractContractHandler { private setupSwGlobal({ interaction, interactionTx }: InteractionData) { this.swGlobal._activeTx = interactionTx; + this.swGlobal.interactionType = interaction.interactionType; this.swGlobal.caller = interaction.caller; // either contract tx id (for internal writes) or transaction.owner } diff --git a/src/core/modules/impl/handler/WasmHandlerApi.ts b/src/core/modules/impl/handler/WasmHandlerApi.ts index bd7c16d2..c505f004 100644 --- a/src/core/modules/impl/handler/WasmHandlerApi.ts +++ b/src/core/modules/impl/handler/WasmHandlerApi.ts @@ -33,6 +33,7 @@ export class WasmHandlerApi extends AbstractContractHandler { this.swGlobal._activeTx = interactionTx; this.swGlobal.caller = interaction.caller; // either contract tx id (for internal writes) or transaction.owner + this.swGlobal.interactionType = interaction.interactionType; this.swGlobal.gasLimit = executionContext.evaluationOptions.gasLimit; this.swGlobal.gasUsed = 0; diff --git a/src/legacy/smartweave-global.ts b/src/legacy/smartweave-global.ts index 364c89a2..6e8607a6 100644 --- a/src/legacy/smartweave-global.ts +++ b/src/legacy/smartweave-global.ts @@ -6,6 +6,7 @@ import { CacheKey, SortKeyCache } from '../cache/SortKeyCache'; import { SortKeyCacheRangeOptions } from '../cache/SortKeyCacheRangeOptions'; import { InteractionState } from '../contract/states/InteractionState'; import { safeGet } from '../utils/utils'; +import { ContractError, InteractionType } from "../core/modules/impl/HandlerExecutorFactory"; /** * @@ -66,6 +67,8 @@ export class SmartWeaveGlobal { kv: KV; + interactionType: InteractionType; + constructor( arweave: Arweave, contract: { id: string; owner: string }, @@ -218,6 +221,13 @@ export class SWTransaction { } return this.smartWeaveGlobal._activeTx.source === 'redstone-sequencer' ? 'L2' : 'L1'; } + + get interactionType(): InteractionType { + if (!this.smartWeaveGlobal._activeTx) { + throw new Error('No current Tx'); + } + return this.smartWeaveGlobal.interactionType; + } } // tslint:disable-next-line: max-classes-per-file @@ -282,7 +292,13 @@ export class KV { ) {} async put(key: string, value: any): Promise { + if (this._transaction.interactionType === 'view') { + throw new ContractError('Forbidden write operation for "view" function: "KV.put"'); + } + this.checkStorageAvailable(); + + await this._storage.put(new CacheKey(key, this._transaction.sortKey), value); } @@ -304,6 +320,10 @@ export class KV { } async del(key: string): Promise { + if (this._transaction.interactionType === 'view') { + throw new ContractError('Forbidden write operation for "view" function: "KV.del"'); + } + this.checkStorageAvailable(); const sortKey = this._transaction.sortKey;