From bbd02ceb40cd87dfbb2ac1b7b4ee4bcfc5e51ec9 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Tue, 24 May 2022 16:56:25 +0800 Subject: [PATCH] Add status message collections example --- .../status-message-collections/.gitignore | 2 + examples/status-message-collections/README.md | 18 ++++ .../test-status-message-collections.ava.js | 93 +++++++++++++++++++ .../status-message-collections/ava.config.cjs | 8 ++ .../babel.config.json | 6 ++ .../status-message-collections/package.json | 20 ++++ .../status-message-collections/src/index.js | 45 +++++++++ .../__tests__/test-status-message.ava.js | 2 +- examples/status-message/src/index.js | 2 +- src/collections/vector.js | 2 +- 10 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 examples/status-message-collections/.gitignore create mode 100644 examples/status-message-collections/README.md create mode 100644 examples/status-message-collections/__tests__/test-status-message-collections.ava.js create mode 100644 examples/status-message-collections/ava.config.cjs create mode 100644 examples/status-message-collections/babel.config.json create mode 100644 examples/status-message-collections/package.json create mode 100644 examples/status-message-collections/src/index.js diff --git a/examples/status-message-collections/.gitignore b/examples/status-message-collections/.gitignore new file mode 100644 index 000000000..48912d244 --- /dev/null +++ b/examples/status-message-collections/.gitignore @@ -0,0 +1,2 @@ +build +node_modules \ No newline at end of file diff --git a/examples/status-message-collections/README.md b/examples/status-message-collections/README.md new file mode 100644 index 000000000..c65e6b61a --- /dev/null +++ b/examples/status-message-collections/README.md @@ -0,0 +1,18 @@ +# Status message contract in JavaScript + +This is an JavaScript implementation of the status message collections example. Every user can store a message on chain. Every user can view everyone's message. It is also intended to test SDK collections. + +## Build the contract + +First ensure JSVM contract is build and deployed locally, follow [Local Installation](https://github.com/near/near-sdk-js#local-installation). Then run: +``` +npm i +npm run build +``` + +Result contract bytecode file will be in `build/contract.base64`. Intermediate JavaScript file can be found in `build/contract.js`. You'll only need the `base64` file to deploy contract to chain. The intermediate JavaScript file is for curious user and near-sdk-js developers to understand what code generation happened under the hood. + +## Test the contract with workspaces-js +``` +npm run test +``` diff --git a/examples/status-message-collections/__tests__/test-status-message-collections.ava.js b/examples/status-message-collections/__tests__/test-status-message-collections.ava.js new file mode 100644 index 000000000..1b034d364 --- /dev/null +++ b/examples/status-message-collections/__tests__/test-status-message-collections.ava.js @@ -0,0 +1,93 @@ +import { Worker } from 'near-workspaces'; +import {readFile} from 'fs/promises' +import test from 'ava'; + +function encodeCall(contract, method, args) { + return Buffer.concat([Buffer.from(contract), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(JSON.stringify(args))]) +} + +test.beforeEach(async t => { + // Init the worker and start a Sandbox server + const worker = await Worker.init(); + + // Prepare sandbox for tests, create accounts, deploy contracts, etx. + const root = worker.rootAccount; + + // Deploy the jsvm contract. + const jsvm = await root.createAndDeploy( + root.getSubAccount('jsvm').accountId, + './node_modules/near-sdk-js/res/jsvm.wasm', + ); + + // Deploy status-message JS contract + const statusMessage = await root.createSubAccount('status-message'); + let contract_base64 = (await readFile('build/contract.base64')).toString(); + await statusMessage.call(jsvm, 'deploy_js_contract', Buffer.from(contract_base64, 'base64'), {attachedDeposit: '400000000000000000000000'}); + await statusMessage.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'init', []), {attachedDeposit: '400000000000000000000000'}); + + // Test users + const ali = await root.createSubAccount('ali'); + const bob = await root.createSubAccount('bob'); + const carl = await root.createSubAccount('carl'); + + // Save state for test runs + t.context.worker = worker; + t.context.accounts = { root, jsvm, statusMessage, ali, bob, carl }; +}); + +test.afterEach(async t => { + await t.context.worker.tearDown().catch(error => { + console.log('Failed to tear down the worker:', error); + }); +}); + +test('Root gets null status', async t => { + const { root, jsvm, statusMessage } = t.context.accounts; + const result = await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_status', [root.accountId])); + t.is(result, null); +}); + +test('Ali sets then gets status', async t => { + const { ali, jsvm, statusMessage } = t.context.accounts; + await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['hello']), {attachedDeposit: '100000000000000000000000'}); + + t.is( + await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_status', [ali.accountId])), + 'hello' + ); +}); + +test('Bob and Carl have different statuses', async t => { + const {jsvm, statusMessage, bob, carl} = t.context.accounts; + await bob.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['hello']), {attachedDeposit: '100000000000000000000000'}); + await carl.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['world']), {attachedDeposit: '100000000000000000000000'}); + + const bobStatus = await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_status', [bob.accountId])); + const carlStatus = await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_status', [carl.accountId])); + t.is(bobStatus, 'hello'); + t.is(carlStatus, 'world'); +}); + +test('Get statuses from the contract', async t => { + const {jsvm, statusMessage, bob, carl} = t.context.accounts; + await bob.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['hello']), {attachedDeposit: '100000000000000000000000'}); + await carl.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['world']), {attachedDeposit: '100000000000000000000000'}); + + const statuses = await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_all_statuses', [])); + t.deepEqual(statuses, [[bob.accountId, 'hello'], [carl.accountId, 'world']]); +}); + +test('message has stored by someone', async t => { + const { ali, jsvm, statusMessage } = t.context.accounts; + await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessage.accountId, 'set_status', ['hello']), {attachedDeposit: '100000000000000000000000'}); + + t.is( + await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'has_status', ['hello'])), + true + ); + + t.is( + await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'has_status', ['world'])), + false + ); +}); \ No newline at end of file diff --git a/examples/status-message-collections/ava.config.cjs b/examples/status-message-collections/ava.config.cjs new file mode 100644 index 000000000..a100daf68 --- /dev/null +++ b/examples/status-message-collections/ava.config.cjs @@ -0,0 +1,8 @@ +require('util').inspect.defaultOptions.depth = 5; // Increase AVA's printing depth + +module.exports = { + timeout: '300000', + files: ['**/*.ava.js'], + failWithoutAssertions: false, + extensions: ['js'], +}; diff --git a/examples/status-message-collections/babel.config.json b/examples/status-message-collections/babel.config.json new file mode 100644 index 000000000..21021f332 --- /dev/null +++ b/examples/status-message-collections/babel.config.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "near-sdk-js/src/build-tools/near-bindgen-exporter", + ["@babel/plugin-proposal-decorators", {"version": "legacy"}] + ] +} diff --git a/examples/status-message-collections/package.json b/examples/status-message-collections/package.json new file mode 100644 index 000000000..084eec6f6 --- /dev/null +++ b/examples/status-message-collections/package.json @@ -0,0 +1,20 @@ +{ + "name": "status-message-collections", + "version": "1.0.0", + "description": "Status message collections example with near-sdk-js", + "main": "index.js", + "type": "module", + "scripts": { + "build": "near-sdk build", + "test": "ava" + }, + "author": "Near Inc ", + "license": "Apache-2.0", + "dependencies": { + "near-sdk-js": "file:../../" + }, + "devDependencies": { + "ava": "^4.2.0", + "near-workspaces": "^2.0.0" + } +} diff --git a/examples/status-message-collections/src/index.js b/examples/status-message-collections/src/index.js new file mode 100644 index 000000000..c64c1ae6a --- /dev/null +++ b/examples/status-message-collections/src/index.js @@ -0,0 +1,45 @@ +import {NearContract, NearBindgen, call, view, near, LookupSet, UnorderedMap, Vector} from 'near-sdk-js' + +@NearBindgen +class StatusMessage extends NearContract { + constructor() { + super() + this.records = new UnorderedMap('a') + this.uniqueValues = new LookupSet('b') + } + + deserialize() { + super.deserialize() + this.records.keys = Object.assign(new Vector, this.records.keys) + this.records.values = Object.assign(new Vector, this.records.values) + this.records = Object.assign(new UnorderedMap, this.records) + this.uniqueValues = Object.assign(new LookupSet, this.uniqueValues) + } + + @call + set_status(message) { + let account_id = near.signerAccountId() + env.log(`${account_id} set_status with message ${message}`) + this.records.set(account_id, message) + this.uniqueValues.set(message) + } + + @view + get_status(account_id) { + env.log(`get_status for account_id ${account_id}`) + return this.records.get(account_id) + } + + @view + has_status(message) { + // used for test LookupMap + return this.uniqueValues.contains(message) + } + + @view + get_all_statuses() { + // used for test UnorderedMap + return this.records.toArray() + } +} + diff --git a/examples/status-message/__tests__/test-status-message.ava.js b/examples/status-message/__tests__/test-status-message.ava.js index 9eaf62ab4..d11ca80d8 100644 --- a/examples/status-message/__tests__/test-status-message.ava.js +++ b/examples/status-message/__tests__/test-status-message.ava.js @@ -44,7 +44,7 @@ test.after(async t => { test('Root gets null status', async t => { const { root, jsvm, statusMessage } = t.context.accounts; const result = await jsvm.view('view_js_contract', encodeCall(statusMessage.accountId, 'get_status', [root.accountId])); - t.is(result, ''); + t.is(result, null); }); test('Ali sets then gets status', async t => { diff --git a/examples/status-message/src/index.js b/examples/status-message/src/index.js index 5867d17b3..0620bdb3c 100644 --- a/examples/status-message/src/index.js +++ b/examples/status-message/src/index.js @@ -17,7 +17,7 @@ class StatusMessage extends NearContract { @view get_status(account_id) { env.log(`get_status for account_id ${account_id}`) - return this.records[account_id] + return this.records[account_id] || null } } diff --git a/src/collections/vector.js b/src/collections/vector.js index 264aa4681..da3c848a2 100644 --- a/src/collections/vector.js +++ b/src/collections/vector.js @@ -29,7 +29,7 @@ export class Vector { get(index) { if (index >= this.length) { - return None + return null } let storageKey = this.indexToKey(index) return near.jsvmStorageRead(storageKey)