Skip to content

Commit

Permalink
fix: smartweave global in constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
koonopek committed Mar 14, 2023
1 parent 63a2581 commit a7558fd
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 10 deletions.
18 changes: 17 additions & 1 deletion src/__tests__/integration/basic/constructor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('Constructor', () => {
});
});

describe('Constructor has access to all smartweave globals', () => {
describe('Constructor has access to part of smartweave globals', () => {
it('should assign as caller deployer of contract', async () => {
const contract = await deployContract({});

Expand Down Expand Up @@ -181,6 +181,22 @@ describe('Constructor', () => {
expect(state2.counter).toStrictEqual(2);
});

it('should fail to access block data', async () => {
const contract = await deployContract({ addToState: { accessBlock: true } });

await expect(contract.readState()).rejects.toThrowError(
'ConstructorError: SmartWeave.block object is not accessible in constructor'
);
});

it('should fail to access vrf data', async () => {
const contract = await deployContract({ addToState: { accessVrf: true } });

await expect(contract.readState()).rejects.toThrowError(
'ConstructorError: SmartWeave.vrf object is not accessible in constructor'
);
});

describe('Internal writes', () => {
it('should throw when using internal writes in contract in __init', async () => {
const writesInConstructorContract = await deployContract({
Expand Down
11 changes: 10 additions & 1 deletion src/__tests__/integration/data/constructor/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ export async function handle(state, action) {
state.caller2 = SmartWeave.caller;
await SmartWeave.kv.put("__init", SmartWeave.transaction.id);

state.counter = action.input.args.counter + 1;

if (action.input.args.fail) {
throw new ContractError("Fail on purpose")
}
state.counter = action.input.args.counter + 1;

if (action.input.args.accessBlock) {
SmartWeave.block.timestamp;
}

if (action.input.args.accessVrf) {
SmartWeave.vrf.data;
}
}

return { state }
Expand Down
2 changes: 1 addition & 1 deletion src/contract/deploy/CreateContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const emptyTransfer: ArTransfer = {
};

export type EvaluationManifest = {
evaluationOptions: Partial<EvaluationOptions>;
evaluationOptions?: Partial<EvaluationOptions>;
plugins?: WarpPluginType[];
};

Expand Down
42 changes: 36 additions & 6 deletions src/core/modules/impl/handler/JsHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,20 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
initialState: State,
executionContext: ExecutionContext<State>
): Promise<State> {
if (this.contractDefinition.manifest?.evaluationOptions.useConstructor) {
if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) {
const interaction = {
input: { function: INIT_FUNC_NAME, args: initialState } as Input,
caller: this.contractDefinition.owner
};
const interactionTx = { ...this.contractDefinition.contractTx, sortKey: genesisSortKey };
// this is hard corded sortKey to make KV possible

const interactionTx = (await this.makeInteractionTxFromContractTx(
this.contractDefinition.contractTx,
this.contractDefinition.owner
)) as GQLNodeInterface;
const interactionData: InteractionData<Input> = { interaction, interactionTx };

this.setupSwGlobal(interactionData);
this.disableInternalWritesForConstructor();
this.configureSwGlobalForConstructor();

const result = await this.runContractFunction(executionContext, interaction, {} as State);
if (result.type !== 'ok') {
Expand All @@ -73,16 +76,32 @@ 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 &&
this.contractDefinition.manifest?.evaluationOptions?.useConstructor &&
interaction.input['function'] === INIT_FUNC_NAME
) {
throw new Error(`You have enabled {useConstructor: true} option, so you can't call function ${INIT_FUNC_NAME}`);
}
}

private disableInternalWritesForConstructor() {
private configureSwGlobalForConstructor() {
// disable internal writes
const templateErrorMessage = (op) =>
`Can't ${op} foreign contract state: Internal writes feature is not available in constructor`;
this.swGlobal.contracts.readContractState = () =>
Expand All @@ -92,6 +111,17 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
throwErrorWithName('ConstructorError', templateErrorMessage('refreshState'));
this.swGlobal.contracts.viewContractState = () =>
throwErrorWithName('ConstructorError', templateErrorMessage('viewContractState'));

const disabledVrf = new Proxy(this.swGlobal.vrf, {
get: () => throwErrorWithName('ConstructorError', `SmartWeave.vrf object is not accessible in constructor`)
});

const disabledBlock = new Proxy(this.swGlobal.block, {
get: () => throwErrorWithName('ConstructorError', 'SmartWeave.block object is not accessible in constructor')
});

this.swGlobal.vrf = disabledVrf;
this.swGlobal.block = disabledBlock;
}

private async runContractFunction<Input>(
Expand Down
2 changes: 1 addition & 1 deletion src/core/modules/impl/handler/WasmHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
initialState: State,
executionContext: ExecutionContext<State, unknown>
): Promise<State> {
if (this.contractDefinition.manifest?.evaluationOptions.useConstructor) {
if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) {
throw Error('Constructor is not implemented for wasm');
}
return initialState;
Expand Down

0 comments on commit a7558fd

Please sign in to comment.