diff --git a/src/__tests__/integration/basic/constructor.test.ts b/src/__tests__/integration/basic/constructor.test.ts index 4b7f7e6..36b0944 100644 --- a/src/__tests__/integration/basic/constructor.test.ts +++ b/src/__tests__/integration/basic/constructor.test.ts @@ -8,6 +8,7 @@ import { WarpFactory } from '../../../core/WarpFactory'; import { LoggerFactory } from '../../../logging/LoggerFactory'; import { DeployPlugin } from 'warp-contracts-plugin-deploy'; import { mineBlock } from '../_helpers'; +import { WriteInteractionResponse } from '../../../contract/Contract'; describe('Constructor', () => { let contractSrc: string; @@ -145,7 +146,7 @@ describe('Constructor', () => { await contract.readState(); const { cachedValue: kv } = await contract.getStorageValues(['__init']); - expect(kv.get('__init')).toEqual(contract.txId()); + expect(kv.get('__init')).toEqual('KV welcome to'); }); }); @@ -185,18 +186,61 @@ describe('Constructor', () => { const contract = await deployContract({ addToState: { accessBlock: true } }); await expect(contract.readState()).rejects.toThrowError( - 'ConstructorError: SmartWeave.block object is not accessible in constructor' + 'ConstructorError: SmartWeave.block.timestamp is not accessible in constructor context' ); }); - it('should fail to access vrf data', async () => { + it('should fail to access vrf data in constructor', async () => { const contract = await deployContract({ addToState: { accessVrf: true } }); await expect(contract.readState()).rejects.toThrowError( - 'ConstructorError: SmartWeave.vrf object is not accessible in constructor' + 'ConstructorError: SmartWeave.vrf.data is not accessible in constructor context' ); }); + it('should fail to access transaction data in constructor', async () => { + const contract = await deployContract({ addToState: { accessTx: true } }); + + await expect(contract.readState()).rejects.toThrowError( + 'ConstructorError: SmartWeave.transaction.id is not accessible in constructor context' + ); + }); + + it('should be able to access SmartWeave global after constructor', async () => { + const contract = await deployContract({}); + const contractB = await deployContract({}); + + const { originalTxId } = (await contract.writeInteraction({ + function: 'callMe', + contractB: (contractB as any)._contractTxId + })) as WriteInteractionResponse; + + const { + cachedValue: { state } + } = await contract.readState(); + + expect(state.blockId).toBeDefined(); + expect(state.txId).toBe(originalTxId); + expect(state.vrf).toBe(undefined); + }); + + it('XX should be able to access SmartWeave internal writes after constructor', async () => { + const foreignContract = await deployContract({ withKv: false, src: contractIrSrc }); + const contract = await deployContract({ + withKv: false, + src: contractIrSrc, + addToState: { + foreignContract: foreignContract.txId() + } + }); + + console.log((foreignContract as any)._contractTxId); + const response = await contract.viewState({ + function: 'read' + }); + expect(response.type).toEqual('ok'); + }); + describe('Internal writes', () => { it('should throw when using internal writes in contract in __init', async () => { const writesInConstructorContract = await deployContract({ diff --git a/src/__tests__/integration/basic/pst-kv.test.ts b/src/__tests__/integration/basic/pst-kv.test.ts index 0961057..54a6341 100644 --- a/src/__tests__/integration/basic/pst-kv.test.ts +++ b/src/__tests__/integration/basic/pst-kv.test.ts @@ -65,7 +65,6 @@ describe('Testing the Profit Sharing Token', () => { afterAll(async () => { await arlocal.stop(); - fs.rmSync(`${DEFAULT_LEVEL_DB_LOCATION}/kv/ldb/${contractTxId}`, { recursive: true }); }); it('should initialize', async () => { diff --git a/src/__tests__/integration/data/constructor/constructor.js b/src/__tests__/integration/data/constructor/constructor.js index 066d4d6..921a1be 100644 --- a/src/__tests__/integration/data/constructor/constructor.js +++ b/src/__tests__/integration/data/constructor/constructor.js @@ -6,7 +6,7 @@ export async function handle(state, action) { if (action.input.function == '__init') { state.caller = action.caller; state.caller2 = SmartWeave.caller; - await SmartWeave.kv.put("__init", SmartWeave.transaction.id); + await SmartWeave.kv.put("__init", 'KV welcome to'); state.counter = action.input.args.counter + 1; @@ -21,6 +21,22 @@ export async function handle(state, action) { if (action.input.args.accessVrf) { SmartWeave.vrf.data; } + + if (action.input.args.accessTx) { + SmartWeave.transaction.id; + } + } + + if (action.input.function == 'callMe') { + await SmartWeave.kv.put("okey", "okey"); + return { + state: { + ...state, + blockId: SmartWeave.block.height, + txId: SmartWeave.transaction.id, + vrf: SmartWeave.vrf.data + } + } } return { state } diff --git a/src/core/modules/impl/handler/JsHandlerApi.ts b/src/core/modules/impl/handler/JsHandlerApi.ts index 7dc7cd3..6827d89 100644 --- a/src/core/modules/impl/handler/JsHandlerApi.ts +++ b/src/core/modules/impl/handler/JsHandlerApi.ts @@ -2,7 +2,7 @@ import { GQLNodeInterface } from 'legacy/gqlResult'; import { ContractDefinition } from '../../../../core/ContractDefinition'; import { ExecutionContext } from '../../../../core/ExecutionContext'; import { EvalStateResult } from '../../../../core/modules/StateEvaluator'; -import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global'; +import { SWBlock, SmartWeaveGlobal, SWTransaction, SWVrf } from '../../../../legacy/smartweave-global'; import { deepCopy, timeout } from '../../../../utils/utils'; import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory'; import { genesisSortKey } from '../LexicographicalInteractionsSorter'; @@ -57,16 +57,18 @@ export class JsHandlerApi extends AbstractContractHandler { caller: this.contractDefinition.owner }; - const interactionTx = (await this.makeInteractionTxFromContractTx( - this.contractDefinition.contractTx, - this.contractDefinition.owner - )) as GQLNodeInterface; + const interactionTx = { + owner: { address: executionContext.caller, key: null }, + sortKey: genesisSortKey + } as GQLNodeInterface; const interactionData: InteractionData = { interaction, interactionTx }; this.setupSwGlobal(interactionData); - this.configureSwGlobalForConstructor(); + const cleanUpSwGlobal = this.configureSwGlobalForConstructor(); const result = await this.runContractFunction(executionContext, interaction, {} as State); + + cleanUpSwGlobal(); if (result.type !== 'ok') { throw new Error(`Exception while calling constructor: ${JSON.stringify(interaction)}:\n${result.errorMessage}`); } @@ -76,21 +78,6 @@ export class JsHandlerApi extends AbstractContractHandler { } } - private async makeInteractionTxFromContractTx( - contractTx: ContractDefinition['contractTx'], - owner: string - ): Promise> { - return { - id: contractTx.id, - tags: contractTx.tags, - recipient: contractTx.target, - owner: { address: owner, key: null }, - quantity: { winston: contractTx.quantity, ar: null }, - fee: { winston: contractTx.fee, ar: null }, - sortKey: genesisSortKey - }; - } - private assertNotConstructorCall(interaction: ContractInteraction) { if ( this.contractDefinition.manifest?.evaluationOptions?.useConstructor && @@ -101,27 +88,37 @@ export class JsHandlerApi extends AbstractContractHandler { } private configureSwGlobalForConstructor() { - // disable internal writes - const templateErrorMessage = (op) => - `Can't ${op} foreign contract state: Internal writes feature is not available in constructor`; + const handler = (prop) => ({ + get: (target, property) => + throwErrorWithName( + 'ConstructorError', + `SmartWeave.${prop}.${String(property)} is not accessible in constructor context` + ) + }); + this.swGlobal.contracts.readContractState = () => - throwErrorWithName('ConstructorError', templateErrorMessage('readContractState')); - this.swGlobal.contracts.write = () => throwErrorWithName('ConstructorError', templateErrorMessage('write')); - this.swGlobal.contracts.refreshState = () => - throwErrorWithName('ConstructorError', templateErrorMessage('refreshState')); + throwErrorWithName('ConstructorError', 'Internal writes feature is not available in constructor'); this.swGlobal.contracts.viewContractState = () => - throwErrorWithName('ConstructorError', templateErrorMessage('viewContractState')); + throwErrorWithName('ConstructorError', 'Internal writes feature is not available in constructor'); + this.swGlobal.contracts.refreshState = () => + throwErrorWithName('ConstructorError', 'Internal writes feature is not available in constructor'); + this.swGlobal.contracts.write = () => + throwErrorWithName('ConstructorError', 'Internal writes feature is not available in constructor'); - const disabledVrf = new Proxy(this.swGlobal.vrf, { - get: () => throwErrorWithName('ConstructorError', `SmartWeave.vrf object is not accessible in constructor`) - }); + const originalBlock = new SWBlock(this.swGlobal); + this.swGlobal.block = new Proxy(this.swGlobal.block, handler('block')); - const disabledBlock = new Proxy(this.swGlobal.block, { - get: () => throwErrorWithName('ConstructorError', 'SmartWeave.block object is not accessible in constructor') - }); + const originalVrf = new SWVrf(this.swGlobal); + this.swGlobal.vrf = new Proxy(this.swGlobal.vrf, handler('vrf')); - this.swGlobal.vrf = disabledVrf; - this.swGlobal.block = disabledBlock; + const originalTransaction = new SWTransaction(this.swGlobal); + this.swGlobal.transaction = new Proxy(this.swGlobal.vrf, handler('transaction')); + + return () => { + this.swGlobal.block = originalBlock; + this.swGlobal.vrf = originalVrf; + this.swGlobal.transaction = originalTransaction; + }; } private async runContractFunction( diff --git a/src/legacy/smartweave-global.ts b/src/legacy/smartweave-global.ts index 2dc0cbd..9eeee1b 100644 --- a/src/legacy/smartweave-global.ts +++ b/src/legacy/smartweave-global.ts @@ -33,9 +33,9 @@ import { BatchDBOp, CacheKey, PutBatch, SortKeyCache } from '../cache/SortKeyCac export class SmartWeaveGlobal { gasUsed: number; gasLimit: number; - transaction: Transaction; - block: Block; - vrf: Vrf; + transaction: SWTransaction; + block: SWBlock; + vrf: SWVrf; evaluationOptions: EvaluationOptions; arweave: Pick; contract: { @@ -78,8 +78,8 @@ export class SmartWeaveGlobal { this.evaluationOptions = evaluationOptions; this.contract = contract; - this.transaction = new Transaction(this); - this.block = new Block(this); + this.transaction = new SWTransaction(this); + this.block = new SWBlock(this); this.contracts = { readContractState: (contractId: string, height?: number, returnValidity?: boolean) => { throw new Error('Not implemented - should be set by HandlerApi implementor'); @@ -97,7 +97,7 @@ export class SmartWeaveGlobal { throw new Error('Not implemented - should be set by HandlerApi implementor'); } }; - this.vrf = new Vrf(this); + this.vrf = new SWVrf(this); this.useGas = this.useGas.bind(this); this.getBalance = this.getBalance.bind(this); @@ -141,7 +141,7 @@ export class SmartWeaveGlobal { } // tslint:disable-next-line: max-classes-per-file -class Transaction { +export class SWTransaction { constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {} get id() { @@ -202,7 +202,7 @@ class Transaction { } // tslint:disable-next-line: max-classes-per-file -class Block { +export class SWBlock { constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {} get height() { @@ -227,7 +227,7 @@ class Block { } } -class Vrf { +export class SWVrf { constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {} get data(): VrfData { @@ -257,7 +257,7 @@ class Vrf { export class KV { private _kvBatch: BatchDBOp[] = []; - constructor(private readonly _storage: SortKeyCache | null, private readonly _transaction: Transaction) {} + constructor(private readonly _storage: SortKeyCache | null, private readonly _transaction: SWTransaction) {} async put(key: string, value: any): Promise { this.checkStorageAvailable();