Skip to content

Commit

Permalink
Merge pull request #58 from near/status-message-collections
Browse files Browse the repository at this point in the history
Add status message collections example
  • Loading branch information
volovyks authored May 24, 2022
2 parents bbeff21 + bbd02ce commit 1ace075
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 3 deletions.
2 changes: 2 additions & 0 deletions examples/status-message-collections/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
node_modules
18 changes: 18 additions & 0 deletions examples/status-message-collections/README.md
Original file line number Diff line number Diff line change
@@ -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
```
Original file line number Diff line number Diff line change
@@ -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
);
});
8 changes: 8 additions & 0 deletions examples/status-message-collections/ava.config.cjs
Original file line number Diff line number Diff line change
@@ -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'],
};
6 changes: 6 additions & 0 deletions examples/status-message-collections/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"plugins": [
"near-sdk-js/src/build-tools/near-bindgen-exporter",
["@babel/plugin-proposal-decorators", {"version": "legacy"}]
]
}
20 changes: 20 additions & 0 deletions examples/status-message-collections/package.json
Original file line number Diff line number Diff line change
@@ -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 <hello@nearprotocol.com>",
"license": "Apache-2.0",
"dependencies": {
"near-sdk-js": "file:../../"
},
"devDependencies": {
"ava": "^4.2.0",
"near-workspaces": "^2.0.0"
}
}
45 changes: 45 additions & 0 deletions examples/status-message-collections/src/index.js
Original file line number Diff line number Diff line change
@@ -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()
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
2 changes: 1 addition & 1 deletion examples/status-message/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

2 changes: 1 addition & 1 deletion src/collections/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 1ace075

Please sign in to comment.