feat: remove contract tx usage from constructor and fix smartweave global after calling constructor
This commit is contained in:
@@ -8,6 +8,7 @@ import { WarpFactory } from '../../../core/WarpFactory';
|
|||||||
import { LoggerFactory } from '../../../logging/LoggerFactory';
|
import { LoggerFactory } from '../../../logging/LoggerFactory';
|
||||||
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
|
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
|
||||||
import { mineBlock } from '../_helpers';
|
import { mineBlock } from '../_helpers';
|
||||||
|
import { WriteInteractionResponse } from '../../../contract/Contract';
|
||||||
|
|
||||||
describe('Constructor', () => {
|
describe('Constructor', () => {
|
||||||
let contractSrc: string;
|
let contractSrc: string;
|
||||||
@@ -145,7 +146,7 @@ describe('Constructor', () => {
|
|||||||
|
|
||||||
await contract.readState();
|
await contract.readState();
|
||||||
const { cachedValue: kv } = await contract.getStorageValues(['__init']);
|
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 } });
|
const contract = await deployContract({ addToState: { accessBlock: true } });
|
||||||
|
|
||||||
await expect(contract.readState()).rejects.toThrowError(
|
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 } });
|
const contract = await deployContract({ addToState: { accessVrf: true } });
|
||||||
|
|
||||||
await expect(contract.readState()).rejects.toThrowError(
|
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', () => {
|
describe('Internal writes', () => {
|
||||||
it('should throw when using internal writes in contract in __init', async () => {
|
it('should throw when using internal writes in contract in __init', async () => {
|
||||||
const writesInConstructorContract = await deployContract({
|
const writesInConstructorContract = await deployContract({
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ describe('Testing the Profit Sharing Token', () => {
|
|||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await arlocal.stop();
|
await arlocal.stop();
|
||||||
fs.rmSync(`${DEFAULT_LEVEL_DB_LOCATION}/kv/ldb/${contractTxId}`, { recursive: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize', async () => {
|
it('should initialize', async () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function handle(state, action) {
|
|||||||
if (action.input.function == '__init') {
|
if (action.input.function == '__init') {
|
||||||
state.caller = action.caller;
|
state.caller = action.caller;
|
||||||
state.caller2 = SmartWeave.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;
|
state.counter = action.input.args.counter + 1;
|
||||||
|
|
||||||
@@ -21,6 +21,22 @@ export async function handle(state, action) {
|
|||||||
if (action.input.args.accessVrf) {
|
if (action.input.args.accessVrf) {
|
||||||
SmartWeave.vrf.data;
|
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 }
|
return { state }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { GQLNodeInterface } from 'legacy/gqlResult';
|
|||||||
import { ContractDefinition } from '../../../../core/ContractDefinition';
|
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 { SWBlock, SmartWeaveGlobal, SWTransaction, SWVrf } from '../../../../legacy/smartweave-global';
|
||||||
import { deepCopy, timeout } from '../../../../utils/utils';
|
import { deepCopy, timeout } from '../../../../utils/utils';
|
||||||
import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
|
import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
|
||||||
import { genesisSortKey } from '../LexicographicalInteractionsSorter';
|
import { genesisSortKey } from '../LexicographicalInteractionsSorter';
|
||||||
@@ -57,16 +57,18 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
|
|||||||
caller: this.contractDefinition.owner
|
caller: this.contractDefinition.owner
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionTx = (await this.makeInteractionTxFromContractTx(
|
const interactionTx = {
|
||||||
this.contractDefinition.contractTx,
|
owner: { address: executionContext.caller, key: null },
|
||||||
this.contractDefinition.owner
|
sortKey: genesisSortKey
|
||||||
)) as GQLNodeInterface;
|
} as GQLNodeInterface;
|
||||||
const interactionData: InteractionData<Input> = { interaction, interactionTx };
|
const interactionData: InteractionData<Input> = { interaction, interactionTx };
|
||||||
|
|
||||||
this.setupSwGlobal(interactionData);
|
this.setupSwGlobal(interactionData);
|
||||||
this.configureSwGlobalForConstructor();
|
const cleanUpSwGlobal = this.configureSwGlobalForConstructor();
|
||||||
|
|
||||||
const result = await this.runContractFunction(executionContext, interaction, {} as State);
|
const result = await this.runContractFunction(executionContext, interaction, {} as State);
|
||||||
|
|
||||||
|
cleanUpSwGlobal();
|
||||||
if (result.type !== 'ok') {
|
if (result.type !== 'ok') {
|
||||||
throw new Error(`Exception while calling constructor: ${JSON.stringify(interaction)}:\n${result.errorMessage}`);
|
throw new Error(`Exception while calling constructor: ${JSON.stringify(interaction)}:\n${result.errorMessage}`);
|
||||||
}
|
}
|
||||||
@@ -76,21 +78,6 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async makeInteractionTxFromContractTx(
|
|
||||||
contractTx: ContractDefinition<unknown>['contractTx'],
|
|
||||||
owner: string
|
|
||||||
): Promise<Omit<GQLNodeInterface, 'anchor' | 'signature' | 'parent' | 'bundledIn' | 'data' | 'block'>> {
|
|
||||||
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<Input>(interaction: ContractInteraction<Input>) {
|
private assertNotConstructorCall<Input>(interaction: ContractInteraction<Input>) {
|
||||||
if (
|
if (
|
||||||
this.contractDefinition.manifest?.evaluationOptions?.useConstructor &&
|
this.contractDefinition.manifest?.evaluationOptions?.useConstructor &&
|
||||||
@@ -101,27 +88,37 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private configureSwGlobalForConstructor() {
|
private configureSwGlobalForConstructor() {
|
||||||
// disable internal writes
|
const handler = (prop) => ({
|
||||||
const templateErrorMessage = (op) =>
|
get: (target, property) =>
|
||||||
`Can't ${op} foreign contract state: Internal writes feature is not available in constructor`;
|
throwErrorWithName(
|
||||||
|
'ConstructorError',
|
||||||
|
`SmartWeave.${prop}.${String(property)} is not accessible in constructor context`
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
this.swGlobal.contracts.readContractState = () =>
|
this.swGlobal.contracts.readContractState = () =>
|
||||||
throwErrorWithName('ConstructorError', templateErrorMessage('readContractState'));
|
throwErrorWithName('ConstructorError', 'Internal writes feature is not available in constructor');
|
||||||
this.swGlobal.contracts.write = () => throwErrorWithName('ConstructorError', templateErrorMessage('write'));
|
|
||||||
this.swGlobal.contracts.refreshState = () =>
|
|
||||||
throwErrorWithName('ConstructorError', templateErrorMessage('refreshState'));
|
|
||||||
this.swGlobal.contracts.viewContractState = () =>
|
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, {
|
const originalBlock = new SWBlock(this.swGlobal);
|
||||||
get: () => throwErrorWithName('ConstructorError', `SmartWeave.vrf object is not accessible in constructor`)
|
this.swGlobal.block = new Proxy(this.swGlobal.block, handler('block'));
|
||||||
});
|
|
||||||
|
|
||||||
const disabledBlock = new Proxy(this.swGlobal.block, {
|
const originalVrf = new SWVrf(this.swGlobal);
|
||||||
get: () => throwErrorWithName('ConstructorError', 'SmartWeave.block object is not accessible in constructor')
|
this.swGlobal.vrf = new Proxy(this.swGlobal.vrf, handler('vrf'));
|
||||||
});
|
|
||||||
|
|
||||||
this.swGlobal.vrf = disabledVrf;
|
const originalTransaction = new SWTransaction(this.swGlobal);
|
||||||
this.swGlobal.block = disabledBlock;
|
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<Input>(
|
private async runContractFunction<Input>(
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ import { BatchDBOp, CacheKey, PutBatch, SortKeyCache } from '../cache/SortKeyCac
|
|||||||
export class SmartWeaveGlobal {
|
export class SmartWeaveGlobal {
|
||||||
gasUsed: number;
|
gasUsed: number;
|
||||||
gasLimit: number;
|
gasLimit: number;
|
||||||
transaction: Transaction;
|
transaction: SWTransaction;
|
||||||
block: Block;
|
block: SWBlock;
|
||||||
vrf: Vrf;
|
vrf: SWVrf;
|
||||||
evaluationOptions: EvaluationOptions;
|
evaluationOptions: EvaluationOptions;
|
||||||
arweave: Pick<Arweave, 'ar' | 'wallets' | 'utils' | 'crypto'>;
|
arweave: Pick<Arweave, 'ar' | 'wallets' | 'utils' | 'crypto'>;
|
||||||
contract: {
|
contract: {
|
||||||
@@ -78,8 +78,8 @@ export class SmartWeaveGlobal {
|
|||||||
this.evaluationOptions = evaluationOptions;
|
this.evaluationOptions = evaluationOptions;
|
||||||
|
|
||||||
this.contract = contract;
|
this.contract = contract;
|
||||||
this.transaction = new Transaction(this);
|
this.transaction = new SWTransaction(this);
|
||||||
this.block = new Block(this);
|
this.block = new SWBlock(this);
|
||||||
this.contracts = {
|
this.contracts = {
|
||||||
readContractState: (contractId: string, height?: number, returnValidity?: boolean) => {
|
readContractState: (contractId: string, height?: number, returnValidity?: boolean) => {
|
||||||
throw new Error('Not implemented - should be set by HandlerApi implementor');
|
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');
|
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.useGas = this.useGas.bind(this);
|
||||||
this.getBalance = this.getBalance.bind(this);
|
this.getBalance = this.getBalance.bind(this);
|
||||||
@@ -141,7 +141,7 @@ export class SmartWeaveGlobal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: max-classes-per-file
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
class Transaction {
|
export class SWTransaction {
|
||||||
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
@@ -202,7 +202,7 @@ class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: max-classes-per-file
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
class Block {
|
export class SWBlock {
|
||||||
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
||||||
|
|
||||||
get height() {
|
get height() {
|
||||||
@@ -227,7 +227,7 @@ class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Vrf {
|
export class SWVrf {
|
||||||
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
constructor(private readonly smartWeaveGlobal: SmartWeaveGlobal) {}
|
||||||
|
|
||||||
get data(): VrfData {
|
get data(): VrfData {
|
||||||
@@ -257,7 +257,7 @@ class Vrf {
|
|||||||
export class KV {
|
export class KV {
|
||||||
private _kvBatch: BatchDBOp<any>[] = [];
|
private _kvBatch: BatchDBOp<any>[] = [];
|
||||||
|
|
||||||
constructor(private readonly _storage: SortKeyCache<any> | null, private readonly _transaction: Transaction) {}
|
constructor(private readonly _storage: SortKeyCache<any> | null, private readonly _transaction: SWTransaction) {}
|
||||||
|
|
||||||
async put(key: string, value: any): Promise<void> {
|
async put(key: string, value: any): Promise<void> {
|
||||||
this.checkStorageAvailable();
|
this.checkStorageAvailable();
|
||||||
|
|||||||
Reference in New Issue
Block a user