-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from near/cross-js-contract-call
cross-contract-call example
- Loading branch information
Showing
11 changed files
with
2,382 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Enclaved cross-contract-call in JavaScript | ||
|
||
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. | ||
|
||
A good place to start is the integration test stored in `__tests__/` folder. | ||
|
||
## Build the contract | ||
|
||
First ensure JSVM contract is built and deployed locally, follow [Local Installation](https://github.com/near/near-sdk-js#local-installation). Then run: | ||
``` | ||
yarn | ||
yarn build | ||
``` | ||
|
||
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. | ||
|
||
## Test the contract with workspaces-js | ||
``` | ||
yarn test | ||
``` | ||
|
||
## Deploy the contract | ||
|
||
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: | ||
|
||
``` | ||
export NEAR_ENV=local | ||
near call jsvm.test.near deploy_js_contract --accountId <accoundId> --args $(cat <contract-name>.base64) --base64 --deposit 0.1 | ||
``` | ||
|
||
## Initialize the contract | ||
|
||
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. | ||
|
||
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: | ||
|
||
``` | ||
near call jsvm.test.near call_js_contract --accountId <accountId> --base64 --args $(node encode_call.js <accountId> init '') | ||
``` | ||
|
||
## Call the contract | ||
Under the root dir of near-sdk-js, call the `set_status` and `set_person_on_call` using this pattern: | ||
|
||
``` | ||
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 | ||
``` |
91 changes: 91 additions & 0 deletions
91
examples/cross-contract-call/__tests__/test-cross-contract-call.ava.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { Worker } from 'near-workspaces'; | ||
import {readFile} from 'fs/promises' | ||
import test from 'ava'; | ||
|
||
// TODO: make this function part of the npm package when it is available | ||
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, | ||
'build/jsvm.wasm', | ||
); | ||
|
||
// Deploy status-message JS contract | ||
const statusMessageContract = await root.createSubAccount('status-message'); | ||
let statusContractBase64 = (await readFile('res/status-message.base64')).toString(); | ||
await statusMessageContract.call(jsvm, 'deploy_js_contract', Buffer.from(statusContractBase64, 'base64'), {attachedDeposit: '400000000000000000000000'}); | ||
await statusMessageContract.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'init', []), {attachedDeposit: '400000000000000000000000'}); | ||
|
||
// Deploy on-call contrat | ||
const onCallContract = await root.createSubAccount('on-call'); | ||
let cross_cc_contract_base64 = (await readFile('build/on-call.base64')).toString(); | ||
await onCallContract.call(jsvm, 'deploy_js_contract', Buffer.from(cross_cc_contract_base64, 'base64'), {attachedDeposit: '400000000000000000000000'}); | ||
await onCallContract.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'init', []), {attachedDeposit: '400000000000000000000000'}); | ||
|
||
// Create test accounts | ||
const ali = await root.createSubAccount('ali'); | ||
const bob = await root.createSubAccount('bob'); | ||
|
||
// Save state for test runs, it is unique for each test | ||
t.context.worker = worker; | ||
t.context.accounts = { | ||
root, | ||
jsvm, | ||
statusMessageContract, | ||
onCallContract, | ||
ali, | ||
bob, | ||
}; | ||
}); | ||
|
||
test.afterEach(async t => { | ||
await t.context.worker.tearDown().catch(error => { | ||
console.log('Failed tear down the worker:', error); | ||
}); | ||
}); | ||
|
||
test('Nobody is on-call in the beginning', async t => { | ||
const { jsvm, onCallContract } = t.context.accounts; | ||
const result = await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', [])); | ||
t.is(result, 'undefined'); | ||
}); | ||
|
||
test('Person can be set on-call if AVAILABLE', async t => { | ||
const { ali, bob, jsvm, statusMessageContract, onCallContract } = t.context.accounts; | ||
|
||
// Ali set her status as AVAILABLE | ||
await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'set_status', ['AVAILABLE']), {attachedDeposit: '100000000000000000000000'}); | ||
// Bob sets Ali on-call | ||
await bob.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'set_person_on_call', [ali.accountId]), {attachedDeposit: '100000000000000000000000'}); | ||
|
||
// Check that Ali is on-call | ||
t.is( | ||
await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', [])), | ||
ali.accountId | ||
); | ||
}); | ||
|
||
test('Person can NOT be set on-call if UNAVAILABLE', async t => { | ||
const { ali, bob, jsvm, statusMessageContract, onCallContract } = t.context.accounts; | ||
|
||
// Ali set her status as AVAILABLE | ||
await ali.call(jsvm, 'call_js_contract', encodeCall(statusMessageContract.accountId, 'set_status', ['UNAVAILABLE']), {attachedDeposit: '100000000000000000000000'}); | ||
// Bob tries to sets Ali on-call | ||
await bob.call(jsvm, 'call_js_contract', encodeCall(onCallContract.accountId, 'set_person_on_call', [ali.accountId]), {attachedDeposit: '100000000000000000000000'}); | ||
|
||
// Check that Ali is NOT on-call | ||
t.not( | ||
await jsvm.view('view_js_contract', encodeCall(onCallContract.accountId, 'person_on_call', [])), | ||
ali.accountId | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"plugins": [ | ||
"../../sdk/near-bindgen-exporter", | ||
["@babel/plugin-proposal-decorators", {"version": "legacy"}] | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
|
||
mkdir -p build | ||
rollup -c | ||
cd build | ||
cp ../../../jsvm.wasm . | ||
../../../builder.sh on-call.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "on-call", | ||
"version": "1.0.0", | ||
"description": "Cross contract call example with near-sdk-js", | ||
"main": "index.js", | ||
"type": "module", | ||
"scripts": { | ||
"build": "./build.sh", | ||
"test": "ava" | ||
}, | ||
"author": "Near Inc <[email protected]>", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"lodash": "^4.17.21", | ||
"lodash-es": "^4.17.21" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.17.5", | ||
"@babel/plugin-proposal-decorators": "^7.17.2", | ||
"@rollup/plugin-babel": "^5.3.1", | ||
"@rollup/plugin-commonjs": "^21.0.1", | ||
"@rollup/plugin-node-resolve": "^13.1.1", | ||
"ava": "^4.2.0", | ||
"near-workspaces": "^2.0.0", | ||
"rollup": "^2.61.1", | ||
"rollup-plugin-sourcemaps": "^0.6.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
JS Contract from `../../status-message` example. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { nodeResolve } from '@rollup/plugin-node-resolve'; | ||
import sourcemaps from 'rollup-plugin-sourcemaps'; | ||
import babel from '@rollup/plugin-babel'; | ||
|
||
// import commonjs from '@rollup/plugin-commonjs'; | ||
|
||
export default { | ||
input: 'src/index.js', | ||
output: { | ||
sourcemap: true, | ||
file: 'build/on-call.js', | ||
format: 'es' | ||
}, | ||
plugins: [ | ||
nodeResolve(), | ||
sourcemaps(), | ||
// commonjs(), | ||
babel({babelHelpers: "bundled"}) | ||
] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import {NearContract, NearBindgen, call, view, near} from '../../../sdk' | ||
|
||
@NearBindgen | ||
class OnCall extends NearContract { | ||
constructor() { | ||
super() | ||
this.personOnCall = "undefined" | ||
} | ||
|
||
@call | ||
set_person_on_call(accountId) { | ||
env.log(`Trying to set ${accountId} on-call`) | ||
const status = near.jsvmCall('status-message.test.near', 'get_status', [accountId]) | ||
env.log(`${accountId} status is ${status}`) | ||
if (status === 'AVAILABLE') { | ||
this.personOnCall = accountId | ||
env.log(`${accountId} set on-call`) | ||
} else { | ||
env.log(`${accountId} can not be set on-call`) | ||
} | ||
} | ||
|
||
@view | ||
person_on_call() { | ||
env.log(`Returning person on-call: ${this.personOnCall}`) | ||
return this.personOnCall | ||
} | ||
} |
Oops, something went wrong.