Skip to content

Commit ba8b04e

Browse files
authored
Merge pull request #17 from near/cross-js-contract-call
cross-contract-call example
2 parents 79314ea + 39e83c8 commit ba8b04e

File tree

11 files changed

+2382
-3
lines changed

11 files changed

+2382
-3
lines changed
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Enclaved cross-contract-call in JavaScript
2+
3+
This example demonstrates how you can make a cross-contract call (contract call that is made from inside of the contract). Here we have 2 smart contracts: `status_message` from `../status-message` example and `on-call` contract in `src`. When somebody is trying to set a person on-call, smart-contract will check if the person is `AVAILABLE`. A person's status is in control of the person itself.
4+
5+
A good place to start is the integration test stored in `__tests__/` folder.
6+
7+
## Build the contract
8+
9+
First ensure JSVM contract is built and deployed locally, follow [Local Installation](https://github.com/near/near-sdk-js#local-installation). Then run:
10+
```
11+
yarn
12+
yarn build
13+
```
14+
15+
The result contract bytecode file will be in `build/on-call.base64`. An intermediate JavaScript file can be found in `build/on-call.js`. You'll only need the `base64` file to deploy contract to chain. The intermediate JavaScript file is for the curious user and near-sdk-js developers to understand what code generation happened under the hood.
16+
17+
## Test the contract with workspaces-js
18+
```
19+
yarn test
20+
```
21+
22+
## Deploy the contract
23+
24+
Suppose JSVM contract was deployed to `jsvm.test.near`. Now you want to deploy the status-message contract to `status-message.test.near` and on-call contract to `on-call.test.near`. Create `status-message.test.near`, `on-call.test.near`, `alice.test.near` and `bob.test.near` locally. Then deploy the contracts following this pattern:
25+
26+
```
27+
export NEAR_ENV=local
28+
near call jsvm.test.near deploy_js_contract --accountId <accoundId> --args $(cat <contract-name>.base64) --base64 --deposit 0.1
29+
```
30+
31+
## Initialize the contract
32+
33+
Now we need to initialize `status-message` and `on-call` contracts after deployment is ready, call the `init` method which will execute `new OnCall()` or `new StatusMessage()` respectfully.
34+
35+
Go back to the root dir of near-sdk-js, where we have a helper `encode-call.js` and use this pattern to init contracts:
36+
37+
```
38+
near call jsvm.test.near call_js_contract --accountId <accountId> --base64 --args $(node encode_call.js <accountId> init '')
39+
```
40+
41+
## Call the contract
42+
Under the root dir of near-sdk-js, call the `set_status` and `set_person_on_call` using this pattern:
43+
44+
```
45+
near call jsvm.test.near call_js_contract --accountId <accountID> --base64 --args $(node encode_call.js <contract-account-id> <function-name> '[<parameter>]') --deposit 0.1
46+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Worker } from 'near-workspaces';
2+
import {readFile} from 'fs/promises'
3+
import test from 'ava';
4+
5+
// TODO: make this function part of the npm package when it is available
6+
function encodeCall(contract, method, args) {
7+
return Buffer.concat([Buffer.from(contract), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(JSON.stringify(args))])
8+
}
9+
10+
test.beforeEach(async t => {
11+
// Init the worker and start a Sandbox server
12+
const worker = await Worker.init();
13+
14+
// Prepare sandbox for tests, create accounts, deploy contracts, etx.
15+
const root = worker.rootAccount;
16+
17+
// Deploy the jsvm contract.
18+
const jsvm = await root.createAndDeploy(
19+
root.getSubAccount('jsvm').accountId,
20+
'build/jsvm.wasm',
21+
);
22+
23+
// Deploy status-message JS contract
24+
const statusMessageContract = await root.createSubAccount('status-message');
25+
let statusContractBase64 = (await readFile('res/status-message.base64')).toString();
26+
await statusMessageContract.call(jsvm, 'deploy_js_contract', Buffer.from(statusContractBase64, 'base64'), {attachedDeposit: '400000000000000000000000'});
27+
await statusMessageContract.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'init', []), {attachedDeposit: '400000000000000000000000'});
28+
29+
// Deploy on-call contrat
30+
const onCallContract = await root.createSubAccount('on-call');
31+
let cross_cc_contract_base64 = (await readFile('build/on-call.base64')).toString();
32+
await onCallContract.call(jsvm, 'deploy_js_contract', Buffer.from(cross_cc_contract_base64, 'base64'), {attachedDeposit: '400000000000000000000000'});
33+
await onCallContract.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'init', []), {attachedDeposit: '400000000000000000000000'});
34+
35+
// Create test accounts
36+
const ali = await root.createSubAccount('ali');
37+
const bob = await root.createSubAccount('bob');
38+
39+
// Save state for test runs, it is unique for each test
40+
t.context.worker = worker;
41+
t.context.accounts = {
42+
root,
43+
jsvm,
44+
statusMessageContract,
45+
onCallContract,
46+
ali,
47+
bob,
48+
};
49+
});
50+
51+
test.afterEach(async t => {
52+
await t.context.worker.tearDown().catch(error => {
53+
console.log('Failed tear down the worker:', error);
54+
});
55+
});
56+
57+
test('Nobody is on-call in the beginning', async t => {
58+
const { jsvm, onCallContract } = t.context.accounts;
59+
const result = await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', []));
60+
t.is(result, 'undefined');
61+
});
62+
63+
test('Person can be set on-call if AVAILABLE', async t => {
64+
const { ali, bob, jsvm, statusMessageContract, onCallContract } = t.context.accounts;
65+
66+
// Ali set her status as AVAILABLE
67+
await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'set_status', ['AVAILABLE']), {attachedDeposit: '100000000000000000000000'});
68+
// Bob sets Ali on-call
69+
await bob.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'set_person_on_call', [ali.accountId]), {attachedDeposit: '100000000000000000000000'});
70+
71+
// Check that Ali is on-call
72+
t.is(
73+
await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', [])),
74+
ali.accountId
75+
);
76+
});
77+
78+
test('Person can NOT be set on-call if UNAVAILABLE', async t => {
79+
const { ali, bob, jsvm, statusMessageContract, onCallContract } = t.context.accounts;
80+
81+
// Ali set her status as AVAILABLE
82+
await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'set_status', ['UNAVAILABLE']), {attachedDeposit: '100000000000000000000000'});
83+
// Bob tries to sets Ali on-call
84+
await bob.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'set_person_on_call', [ali.accountId]), {attachedDeposit: '100000000000000000000000'});
85+
86+
// Check that Ali is NOT on-call
87+
t.not(
88+
await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', [])),
89+
ali.accountId
90+
);
91+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require('util').inspect.defaultOptions.depth = 5; // Increase AVA's printing depth
2+
3+
module.exports = {
4+
timeout: '300000',
5+
files: ['**/*.ava.js'],
6+
failWithoutAssertions: false,
7+
extensions: ['js'],
8+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": [
3+
"../../sdk/near-bindgen-exporter",
4+
["@babel/plugin-proposal-decorators", {"version": "legacy"}]
5+
]
6+
}

examples/cross-contract-call/build.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
mkdir -p build
5+
rollup -c
6+
cd build
7+
cp ../../../jsvm.wasm .
8+
../../../builder.sh on-call.js
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "on-call",
3+
"version": "1.0.0",
4+
"description": "Cross contract call example with near-sdk-js",
5+
"main": "index.js",
6+
"type": "module",
7+
"scripts": {
8+
"build": "./build.sh",
9+
"test": "ava"
10+
},
11+
"author": "Near Inc <[email protected]>",
12+
"license": "Apache-2.0",
13+
"dependencies": {
14+
"lodash": "^4.17.21",
15+
"lodash-es": "^4.17.21"
16+
},
17+
"devDependencies": {
18+
"@babel/core": "^7.17.5",
19+
"@babel/plugin-proposal-decorators": "^7.17.2",
20+
"@rollup/plugin-babel": "^5.3.1",
21+
"@rollup/plugin-commonjs": "^21.0.1",
22+
"@rollup/plugin-node-resolve": "^13.1.1",
23+
"ava": "^4.2.0",
24+
"near-workspaces": "^2.0.0",
25+
"rollup": "^2.61.1",
26+
"rollup-plugin-sourcemaps": "^0.6.3"
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JS Contract from `../../status-message` example.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { nodeResolve } from '@rollup/plugin-node-resolve';
2+
import sourcemaps from 'rollup-plugin-sourcemaps';
3+
import babel from '@rollup/plugin-babel';
4+
5+
// import commonjs from '@rollup/plugin-commonjs';
6+
7+
export default {
8+
input: 'src/index.js',
9+
output: {
10+
sourcemap: true,
11+
file: 'build/on-call.js',
12+
format: 'es'
13+
},
14+
plugins: [
15+
nodeResolve(),
16+
sourcemaps(),
17+
// commonjs(),
18+
babel({babelHelpers: "bundled"})
19+
]
20+
};
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {NearContract, NearBindgen, call, view, near} from '../../../sdk'
2+
3+
@NearBindgen
4+
class OnCall extends NearContract {
5+
constructor() {
6+
super()
7+
this.personOnCall = "undefined"
8+
}
9+
10+
@call
11+
set_person_on_call(accountId) {
12+
env.log(`Trying to set ${accountId} on-call`)
13+
const status = near.jsvmCall('status-message.test.near', 'get_status', [accountId])
14+
env.log(`${accountId} status is ${status}`)
15+
if (status === 'AVAILABLE') {
16+
this.personOnCall = accountId
17+
env.log(`${accountId} set on-call`)
18+
} else {
19+
env.log(`${accountId} can not be set on-call`)
20+
}
21+
}
22+
23+
@view
24+
person_on_call() {
25+
env.log(`Returning person on-call: ${this.personOnCall}`)
26+
return this.personOnCall
27+
}
28+
}

0 commit comments

Comments
 (0)