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 { 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({
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<State> extends AbstractContractHandler<State> {
|
||||
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<Input> = { 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<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>) {
|
||||
if (
|
||||
this.contractDefinition.manifest?.evaluationOptions?.useConstructor &&
|
||||
@@ -101,27 +88,37 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
|
||||
}
|
||||
|
||||
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<Input>(
|
||||
|
||||
@@ -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<Arweave, 'ar' | 'wallets' | 'utils' | 'crypto'>;
|
||||
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<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> {
|
||||
this.checkStorageAvailable();
|
||||
|
||||
Reference in New Issue
Block a user