feat: remove contract tx usage from constructor and fix smartweave global after calling constructor

This commit is contained in:
Michał Konopka
2023-03-17 15:56:00 +01:00
committed by just_ppe
parent a7558fd866
commit c1d8fbece5
5 changed files with 109 additions and 53 deletions

View File

@@ -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({

View File

@@ -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 () => {

View File

@@ -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 }

View File

@@ -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>(

View File

@@ -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();