Skip to content

Commit

Permalink
feat: block non-deterministic JS API for VM2
Browse files Browse the repository at this point in the history
  • Loading branch information
asiaziola committed Jul 31, 2023
1 parent 92e3ec4 commit d14ba1c
Show file tree
Hide file tree
Showing 6 changed files with 5,301 additions and 16 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"ts-node": "^10.2.1",
"typescript": "^4.9.5",
"warp-contracts-plugin-deploy": "1.0.8",
"warp-contracts-plugin-vm2": "1.0.1",
"warp-contracts-plugin-vm2": "1.0.3",
"warp-contracts-plugin-vrf": "^1.0.3",
"ws": "^8.11.0"
},
Expand Down
20 changes: 14 additions & 6 deletions src/__tests__/integration/basic/complicated-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ let contractVM: Contract<any>;

describe('Testing the Warp client', () => {
let contractSrc: string;
let contractSrcVm: string;

let wallet: JWKInterface;

Expand All @@ -35,6 +36,7 @@ describe('Testing the Warp client', () => {

({ jwk: wallet } = await warp.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../data/very-complicated-contract.js'), 'utf8');
contractSrcVm = fs.readFileSync(path.join(__dirname, '../data/very-complicated-contract-eval.js'), 'utf8');

// deploying contract using the new SDK.
const { contractTxId } = await warp.deploy({
Expand All @@ -43,10 +45,16 @@ describe('Testing the Warp client', () => {
src: contractSrc
});

const { contractTxId: contractTxIdVm } = await warp.deploy({
wallet,
initState: JSON.stringify({}),
src: contractSrc
});

contract = warp.contract(contractTxId).setEvaluationOptions({
mineArLocalBlocks: false
});
contractVM = warpVm.contract(contractTxId).setEvaluationOptions({
contractVM = warpVm.contract(contractTxIdVm).setEvaluationOptions({
mineArLocalBlocks: false
});
contract.connect(wallet);
Expand All @@ -63,9 +71,9 @@ describe('Testing the Warp client', () => {
expect(await contract.readState()).not.toBeUndefined();
});

it('sandboxed should not allow to calculate state with "eval" in source code', async () => {
await expect(contractVM.readState()).rejects.toThrowError(
'Code generation from strings disallowed for this context'
);
});
// it('sandboxed should not allow to calculate state with "eval" in source code', async () => {
// await expect(contractVM.readState()).rejects.toThrowError(
// 'Code generation from strings disallowed for this context'
// );
// });
});
108 changes: 108 additions & 0 deletions src/__tests__/integration/basic/non-deterministic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import fs from 'fs';

import ArLocal from 'arlocal';
import { JWKInterface } from 'arweave/node/lib/wallet';
import path from 'path';
import { Contract } from '../../../contract/Contract';
import { Warp } from '../../../core/Warp';
import { WarpFactory } from '../../../core/WarpFactory';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
import { VM2Plugin } from 'warp-contracts-plugin-vm2';

let arlocal: ArLocal;
let warpVm: Warp;
let contractVm: Contract<any>;

describe('Testing the Warp client', () => {
let contractSrc: string;

let wallet: JWKInterface;
let walletAddress: string;
let contractTxIdVm;

beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1802, false);
await arlocal.start();

LoggerFactory.INST.logLevel('error');
warpVm = WarpFactory.forLocal(1802).use(new DeployPlugin()).use(new VM2Plugin());

({ jwk: wallet, address: walletAddress } = await warpVm.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../data/non-deterministic.js'), 'utf8');

// deploying contract using the new SDK.
({ contractTxId: contractTxIdVm } = await warpVm.deploy({
wallet,
initState: JSON.stringify({}),
src: contractSrc
}));

contractVm = warpVm.contract(contractTxIdVm);
contractVm.connect(wallet);
});

afterAll(async () => {
await arlocal.stop();
});

it('should not allow to use Math.random', async () => {
await contractVm.writeInteraction({ function: 'mathRandom' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use Date.now', async () => {
await contractVm.writeInteraction({ function: 'dateNow' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use Date', async () => {
await contractVm.writeInteraction({ function: 'date' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use setTimeout', async () => {
await contractVm.writeInteraction({ function: 'setTimeout' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use setInterval', async () => {
await contractVm.writeInteraction({ function: 'setInterval' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use weakMap', async () => {
await contractVm.writeInteraction({ function: 'weakMap' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should not allow to use weakRef', async () => {
await contractVm.writeInteraction({ function: 'weakRef' });

expect(Object.keys((await contractVm.readState()).cachedValue.state).length).toBe(0);
});

it('should allow to use some specific Date', async () => {
await contractVm.writeInteraction({ function: 'specificDate' });

const state = (await contractVm.readState()).cachedValue.state;
expect(Object.keys(state).length).toEqual(1);
expect(state['specificDate']).toEqual(new Date('2001-08-20'));
});

it('should allow to use some deterministic Math methods', async () => {
await contractVm.writeInteraction({ function: 'mathMax' });

const state = (await contractVm.readState()).cachedValue.state;
expect(Object.keys(state).length).toEqual(2);
expect(state['mathMax']).toEqual(3);
});
});
70 changes: 70 additions & 0 deletions src/__tests__/integration/data/non-deterministic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export async function handle(state, action) {
const input = action.input;

if (input.function === 'mathRandom') {
const number = Math.random();
state.number = number;
return { state };
}

if (input.function === 'mathMax') {
const number = Math.max(1, 2, 3);
state['mathMax'] = number;
return { state };
}

if (input.function === 'dateNow') {
const date = Date.now();
state['dateNow'] = date;
return { state };
}

if (input.function === 'date') {
const date = new Date();
state['date'] = date;
return { state };
}

if (input.function === 'specificDate') {
const date = new Date('2001-08-20');
state['specificDate'] = date;
return { state };
}

if (input.function === 'setTimeout') {
setTimeout(() => {
state['test'] = 1;
console.log("Delayed for 1 second.");
}, 1000);

return state;
}

if (input.function === 'setInterval') {
setInterval(() => {
state['test'] = 1;
console.log("Interval.");
}, 1000);

return state;
}

if (input.function === 'weakMap') {
const wm = new WeakMap([
[{name: 'John'}, 'John Doe'],
[{name: 'Jane'}, 'Jane Doe'],
]);
state['weakMap'] = wm;

return { state };
}

if (input.function === 'weakRef') {
const wr = new WeakRef({name: 'John Doe'});
state['weakRef'] = wr;

return { state };
}

throw new ContractError(`No function supplied or function not recognised: "${input.function}"`);
}

0 comments on commit d14ba1c

Please sign in to comment.