diff --git a/Makefile b/Makefile index 2500cd624..c0cc412d5 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ jsvm: cd jsvm && ./build.sh && cd .. cp jsvm/jsvm.wasm res/jsvm.wasm +jsvm-nightly: + echo "Building jsvm.wasm..." + cd jsvm && NEAR_NIGHTLY=1 ./build.sh && cd .. + cp jsvm/jsvm_nightly.wasm res/jsvm_nightly.wasm + qjsc: echo "Building qjsc bytecode compiler" cd quickjs && ./build.sh && cd .. diff --git a/README.md b/README.md index 3107687e9..d2bef66eb 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,34 @@ -## Disclaimer: -This README is a work in progress. The best way to start using `near-sdk-js` is to check `./examples`. # NEAR-SDK-JS (Enclave) -## Local Installation -It is tested on Ubuntu 20.04 and Intel Mac. Other linux and M1 Macs should also work but they're not tested. +## Getting started with template project -1. Make sure you have `wget`, `make`, `cmake` and `nodejs`. On Linux, also make sure you have `gcc`. -2. Run `make` to get platform specific `qjsc` and `jsvm` contract in `res` folder. +The fastest and recommended way to develop with near-sdk-js is to create a project with our github template: https://github.com/near/near-sdk-js-template-project. + +## Running examples + +There are a couple of contract examples in the project: -## Usage -1. `cd examples/` -2. `yarn && yarn build` to get .base64 file (JS smart-contract). -3. Use near-cli to deploy `jsvm.wasm` from the `res` folder to your account. +- [Clean contract state](https://github.com/near/near-sdk-js/tree/master/examples/clean-state) +- [Doing cross contract call](https://github.com/near/near-sdk-js/tree/master/examples/cross-contract-call) +- [Fungible token](https://github.com/near/near-sdk-js/tree/master/examples/fungible-token) +- [Lockable fungible token](https://github.com/near/near-sdk-js/tree/master/examples/lockable-fungible-token) +- [Non fungible token](https://github.com/near/near-sdk-js/tree/master/examples/non-fungible-token) +- [Status message board](https://github.com/near/near-sdk-js/tree/master/examples/status-message) + +The general steps to run these contracts are same. You can also follow their corresponding READMEs to build, test and run the contracts. + +### General steps to run examples locally +1. Use near-cli to deploy `jsvm.wasm` from the `res` folder to one of account you controlled. For example, `jsvm.`: +```sh +export NEAR_ENV=local +near deploy res/jsvm.wasm +``` +2. `cd examples/` +3. `yarn && yarn build` to get .base64 file (JS smart-contract). 4. Deploy .base64 file to `JSVM` account from the previous step. ```sh -near js deploy --accountId --base64File .base64 --deposit 0.1 --jsvm +near js deploy --accountId --base64File build/.base64 --deposit 0.1 --jsvm ``` -
-Or use the raw CLI call instead -

- - near call deploy_js_contract --accountId --args $(cat .base64) --base64 --deposit 0.1 - -

-
5. Interact with your contract using NEAR CLI or `near-api-js`. Encode the parameters and call. If the call cause the state increasement, you also need to attach NEAR to cover the storage deposit for the delta. @@ -31,475 +36,485 @@ near js deploy --accountId --base64File .base64 -- near js call --args --deposit 0.1 --jsvm ``` -
-Or use the raw CLI call instead -

- - near call call_js_contract --accountId --args --base64 - - # where `` can be obtained by: - node encode_call.js - -

-
- 6. If you want to remove the js contract and withdraw the storage deposit, use: ```sh near js remove --accountId --jsvm ``` -
-Or use the raw CLI call instead -

+### General steps to run examples on testnet +1. `export NEAR_ENV=testnet` +2. `cd examples/` +3. `yarn && yarn build` to get .base64 file (JS smart-contract). +4. Deploy, call and remove JS contract is same as above, except is `jsvm.testnet`. This is also the default value, so you omit `--jsvm`. - near call remove_js_contract --accountId +## Error Handling in NEAR-SDK-JS -

-
+If you want to indicate an error happened and fail the transaction, just throw an error object in JavaScript. Our JSVM runtime will detect and automatically invoke `panic_utf8` with `"{error.message}\n:{error.stack}"`. As a result, transaction will fail with `"Smart contract panicked: {error.message}\n{error.stack}"` error message. You can also use an error utilities library to organize your errors, such as verror. -## Demo +When your JS code or library throws an error, uncaught, the transaction will also fail with GuestPanic error, with the error message and stacktrace. -### On a local node +When call host function with inappropriate type, means incorrect number of arguments or arg is not expected type: + - if arguments less than params, remaining argument are set as 'undefined' + - if arguments more than params, remaining argument are ignored + - if argument is different than the required type, it'll be coerced to required type + - if argument is different than the required type but cannot be coerced, will throw runtime type error, also with message and stacktrace -1. Build the jsvm contract -``` -make -``` +## Test +We recommend to use near-workspaces to write tests for your smart contracts. See any of the examples for how tests are setup and written. -2. Go to nearcore, Build and start a local node -``` -cargo build -p neard -target/debug/neard init -target/debug/neard run -``` +## NEAR-SDK-JS API Reference -3. Go back to `near-sdk-js`. Have `near-cli` installed. Deploy the jsvm contract. Example session: -```sh -near-sdk-js (master) export NEAR_ENV=local -near-sdk-js (master) near deploy test.near jsvm.wasm +All NEAR blockchain provided functionality (host functions) are defined in `src/api.js` and exported as `near`. You can use them by: +```js +import {near} from 'near-sdk-js' + +// near.. e.g.: +let signer = near.signerAccountId() ``` -
-Example output -

+To use nightly host functions, such as `altBn128G1Sum`, the enclave contract need to be built with `make jsvm-nightly` and deployed to a nearcore node that has nightly enabled. - Loaded master account test.near key from /home/bo/.near/validator_key.json with public key = ed25519:XXqxAHP1ZRcwCwBTr1MbdF9NM7UVynuTnxhZfFeE5UJ - Starting deployment. Account id: test.near, node: http://localhost:3030, helper: http://localhost:3000, file: jsvm.wasm - Transaction Id EGVd29tthMp7fqkDgP8frftgZhhb3FVazaFhvXpYXNhw - To see the transaction in the transaction explorer, please open this url in your browser - http://localhost:9001/transactions/EGVd29tthMp7fqkDgP8frftgZhhb3FVazaFhvXpYXNhw - Done deploying to test.near +### About Type -

-
+- In arguments, `Uint64: Number | BigInt`. In return, `Uint64: BigInt`. Because JavaScript Number cannot hold all Uint64 without losing precision. But as arguments, interger number is also allowed for convinience. Same for `Uint128`. +- `String` in both arguments and return is a byte buffer encoded as a JavaScript String. Which means: + - If the string have only 1 byte chars, the representation is same. + - If the string have 2/3/4 byte char, it is break down to 2/3/4 bytes and each byte as a separate char. + - Arbitrary binary data `0x00-0xff` is as the char '\x00-\xff' -4. Build, deploy hello contract to jsvm contract, and call hello. Example session: -```sh -near-sdk-js (master) ./builder.sh examples/low-level/hello_near.js -near-sdk-js (master) near js deploy --accountId test.near --base64 hello_near.base64 --deposit 0.1 --jsvm test.near +It's intentional to represent string and bytes in this way because QuickJS doesn't have ArrayBuffer in C API. + +### Context API + +``` +function signerAccountId(): String; +function signerAccountPk(): String; +function predecessorAccountId(): String; +function blockIndex(): Uint64; +function blockTimestamp(): Uint64; +function epochHeight(): Uint64; ``` -
-Or use the raw CLI call instead -

- near-sdk-js (master) near call test.near deploy_js_contract --accountId test.near --base64 --args $(cat hello_near.base64) --deposit 0.1 +### Economics API +``` +function attachedDeposit(): Uint128; +function prepaidGas(): Uint64; +function usedGas(): Uint64; +``` -

-
+### Math API +``` +function randomSeed(): String; +function sha256(value: String): String; +function keccak256(value: String): String; +function keccak512(value: String): String; +function ripemd160(value: String): String; +function ecrecover(hash: String, sign: String, v: Uint64, malleability_flag: Uint64): String | null; +``` -
-Example output -

+### Miscellaneous API - Scheduling a call: test.near.deploy_js_contract(AgYsZXhhbXBsZXMvaGVsbG9fbmVhci5qcwpoZWxsbwxoZWxsbzIGZW52BmxvZxRIZWxsbyBOZWFyD7wDAAIAAL4DAAHAAwAADgAGAaABAAAAAQICCwC+AwABwAMBAQjqCMAA4cAB4ikpvAMBBAEACg4OQwYBvgMAAAADAAATADjhAAAAQuIAAAAE4wAAACQBACm8AwECA10OQwYBwAMAAAADAAEQADjhAAAAQuIAAAC/ACQBACm8AwUCA04HCDIyMjI=) with attached 0.1 NEAR - Loaded master account test.near key from /home/bo/.near/validator_key.json with public key = ed25519:XXqxAHP1ZRcwCwBTr1MbdF9NM7UVynuTnxhZfFeE5UJ - Doing account.functionCall() - Transaction Id Df7txPSFWwaBLTz61pSxoVrPPu6qY7fUTJ31xuQtXDBf - To see the transaction in the transaction explorer, please open this url in your browser - http://localhost:9001/transactions/Df7txPSFWwaBLTz61pSxoVrPPu6qY7fUTJ31xuQtXDBf - '' +``` +function panic(msg?: String); +function panicUtf8(msg: String); +function log(msg: String); +function logUtf8(msg: String); +function logUtf16(msg: String); +``` -

-
+### Storage API -```sh -near-sdk-js (master) near js call test.near hello --accountId test.near --deposit 0.1 --jsvm test.near +``` +function storageRead(key: String): String | null; +function storageHasKey(key: String): bool; ``` -
-Or use the raw CLI call instead -

+### Validator API - near-sdk-js (master) near call test.near call_js_contract --accountId test.near --base64 --args $(node encode_call.js test.near hello '') +``` +function validatorStake(account_id: String): Uint128; +function validatorTotalStake(): Uint128; +``` -

-
+### Alt BN128 -
-Example output -

+``` +function altBn128G1Multiexp(value: String): String; +function altBn128G1Sum(value: String): String; +function altBn128PairingCheck(value: String): bool; +``` + +### JSVM Specific APIs +Due to the design of JavaScript VM Contract, some additonal APIs are provided to obtain context, access storage and cross contract call. Since they're not documented at [NEAR nomicon](https://nomicon.io/). They're explained here. - Scheduling a call: test.near.call_js_contract(anN2bXRlc3Rlci50ZXN0bmV0AGhlbGxvAA==) - Loaded master account test.near key from /home/bo/.near/validator_key.json with public key = ed25519:XXqxAHP1ZRcwCwBTr1MbdF9NM7UVynuTnxhZfFeE5UJ - Doing account.functionCall() - Receipt: AcRRGeR16FYg5AEMZ163v5Av1NanZtRHocDUvmGTvoYN - Log [test.near]: Hello Near - Transaction Id GkitU1Cm5bdQJWe6bzkYganiS9tfetuY4buqGFypvQWL - To see the transaction in the transaction explorer, please open this url in your browser - http://localhost:9001/transactions/GkitU1Cm5bdQJWe6bzkYganiS9tfetuY4buqGFypvQWL - '' +#### Obtain Context +``` +function jsvmAccountId(): String; +function jsvmJsContractName(): String; +function jsvmMethodName(): String; +function jsvmArgs(): String; +``` -

-
+The `jsvmAccountId` returns the JavaScript VM's contract account ID. + +The `jsvmJsContractName`, when called, returns the JavaScript contract name that are called at the moment. + +The `jsvmJsContractName` returns the method name being called. -### On Testnet -Latest master version of near-sdk-js enclave has been deployed on `jsvm.testnet`. You can use it or deploy your own copy of jsvm, which is simiar to the steps for deploy on local node. The following is the step to deploy and call your contract on `jsvm.testnet`. +The `jsvmArgs` return the arguments passed to the method. -1. Build the contract +#### Storage Access ``` -near-sdk-js (master) ./builder.sh examples/low-level/hello_near.js +function jsvmStorageWrite(key: String, value: String): bool; +function jsvmStorageRead(key: String): String | null; +function jsvmStorageRemove(key: String): bool; +function jsvmStorageHasKey(key: String): bool; +function storageGetEvicted(): String; ``` -2. Create an account on testnet wallet. Login it with near-cli: -```sh -near-sdk-js (master) export NEAR_ENV=testnet -near-sdk-js (master) near login +These are equivalent to `storage*` but access limit to the substate of current JS contract. The `jsvmStorageWrite` and `jsvmStorageRemove` require and refund deposit to cover the storage delta. `jsvmStorage*` access the substate of current JS contract by prefix the key of current JS contract name (deployer's account id). You can use `storageRead` and `storageHasKey` to get code and state of other JS contracts. More specifically: code of `contractA` is stored under the key `contractA/code`. state of `contractA` is stored under `contractA/state/` concat with developer specifid key. And: +``` +jsvmStorageRead(k) +// equvalent to +storageRead(jsvmJsContractName() + '/state/' + k) ``` -
-Example output -

+When `jsvmStorageWrite` write to a key that already exists, the old value would be saved and can be obtained by `storageGetEvicted()`. In this case, jsvmStorageWrite returns `true`. If key doesn't exist before, returns `false`. - Please authorize NEAR CLI on at least one of your accounts. +When `jsvmStroageRemove` remove a key that exists, the old value would be saved and can be obtained by `storageGetEvicted()`. In this case, jsvmStroageRemove returns `true`. If key doesn't exist before, nothing is removed and returns `false`. - If your browser doesn't automatically open, please visit this URL - https://wallet.testnet.near.org/login/?referrer=NEAR+CLI&public_key=ed25519%3A6eNw1uLsVbvHJhPrcN9Rj9wefqfzxJ2tz7VqxY8m3F88&success_url=http%3A%2F%2F127.0.0.1%3A5000 - Please authorize at least one account at the URL above. +#### Cross Contract Call +``` +function jsvmValueReturn(value: String); +function jsvmCall(contract_name: String, method: String, args: String): any; +function jsvmCallRaw(contract_name: String, method: String, args: String): String; +``` - Which account did you authorize for use with NEAR CLI? - Enter it here (if not redirected automatically): - Logged in as [ jsvmtester.testnet ] with public key [ ed25519:6eNw1u... ] successfully +The `jsvmValueReturn` is the version of `valueReturn` that should be used in all JavaScript contracts. It play well with `jsvmCall`. -

-
+The `jsvmCall` invoke a synchronous cross contract call, to the given JavaScript `contract_name`, `method` with `args`. And returned the return value parsed as JSON into a JS object. -3. Deploy the JS contract: -```sh -near-sdk-js (master) export JSVM_ACCOUNT=jsvm.testnet -near-sdk-js (master) near js call deploy --accountId jsvmtester.testnet --base64File hello_near.base64 --deposit 0.1 --jsvm $JSVM_ACCOUNT -``` +The `jsvmCallRaw` is similar to `jsvmCall`, but return the raw, unparsed String. -
-Or use the raw CLI call instead -

+### Collections +A few useful on-chain persistent collections are provided. - near-sdk-js (master) near call $JSVM_ACCOUNT deploy_js_contract --accountId jsvmtester.testnet --base64 --args $(cat hello_near.base64) --deposit 0.1 +#### Vector +Vector is an iterable implementation of vector that stores its content on the trie. Usage: -

-
+```js +import {Vector} from 'near-sdk-js' +// in contract class constructor: +constructor() { + super() + this.v = new Vector('my_prefix_') +} -
-Example output -

+// Override the deserializer to load vector from chain +deserialize() { + super.deserialize() + this.v = Object.assign(new Vector, this.v) +} - Scheduling a call: jsvm.testnet.deploy_js_contract(AgYsZXhhbXBsZXMvaGVsbG9fbmVhci5qcwpoZWxsbwxoZWxsbzIGZW52BmxvZxRIZWxsbyBOZWFyD7wDAAIAAL4DAAHAAwAADgAGAaABAAAAAQICCwC+AwABwAMBAQjqCMAA4cAB4ikpvAMBBAEACg4OQwYBvgMAAAADAAATADjhAAAAQuIAAAAE4wAAACQBACm8AwECA10OQwYBwAMAAAADAAEQADjhAAAAQuIAAAC/ACQBACm8AwUCA04HCDIyMjI=) with attached 0.1 NEAR - Doing account.functionCall() - Transaction Id 46vC327SWs7JNV7G3XMHkRzETyc6WuxwkdwLhV6Go2kp - To see the transaction in the transaction explorer, please open this url in your browser - https://explorer.testnet.near.org/transactions/46vC327SWs7JNV7G3XMHkRzETyc6WuxwkdwLhV6Go2kp - '' +someMethod() { + // insert + this.v.push('abc') + this.v.push('def') + this.v.push('ghi') -

-
+ // batch insert, extend: + this.v.extend(['xyz', '123']) -Note that, in order to deploy the contract, the deployer need to deposit sufficient amount of NEAR to cover storage deposit. It's roughly 0.01 NEAR for 1KB of contract. If you deposit more than required, the additional part will be refunded. If no adequate deposit is attached, the deploy will failed with a panic. If future deployment increase or decrease in size, the difference will need to be paid or refunded, respectively. + // get + let first = this.v.get(0) -4. Call the JS contract: -```sh -near-sdk-js (master) near js call jsvmtester.testnet hello --accountId jsvmtester.testnet --deposit 0.1 --jsvm $JSVM_ACCOUNT -``` -
-Or use the raw CLI call instead -

+ // remove, move the last element to the given index + this.v.swapRemove(0) - near-sdk-js (master) near call $JSVM_ACCOUNT call_js_contract --accountId jsvmtester.testnet --args $(node encode_call.js jsvmtester.near hello '') --base64 + // replace + this.v.replace(1, 'jkl') -

-
+ // remove the last + this.v.pop() -
-Example output -

+ // len, isEmpty + let len = this.v.len() + let isEmpty = this.v.isEnpty() - Scheduling a call: jsvm.testnet.call_js_contract(anN2bXRlc3Rlci50ZXN0bmV0AGhlbGxvAA==) - Doing account.functionCall() - Receipt: 4Mn5d3Kc4n67MxQkcEmi4gxKbrrKXvJE9Rin3q3fdCsQ - Log [jsvm.testnet]: Hello Near - Transaction Id 43K5sjgVeWCYzuDJ3S6j5XHxQnRY8w1TQ84MiDxdtHp1 - To see the transaction in the transaction explorer, please open this url in your browser - https://explorer.testnet.near.org/transactions/43K5sjgVeWCYzuDJ3S6j5XHxQnRY8w1TQ84MiDxdtHp1 - '' + // iterate + for (let element of this.v) { + near.log(element) + } -

-
+ // toArray, convert to JavaScript Array + let a = this.v.toArray() -```sh -near-sdk-js (master) near js call jsvmtester.testnet hello --accountId jsvmtester2.testnet --deposit 0.1 --jsvm $JSVM_ACCOUNT + // clear + ths.v.clear() +} ``` -
-Or use the raw CLI call instead -

+#### LookupMap +LookupMap is an non-iterable implementation of a map that stores its content directly on the trie. It's like a big hash map, but on trie. Usage: +```js +import {LookupMap} from 'near-sdk-js' - near-sdk-js (master) near call $JSVM_ACCOUNT call_js_contract --accountId jsvmtester2.testnet --args $(node encode_call.js jsvmtester.near hello '') --base64 +// in contract class constructor: +constructor() { + super() + this.m = new LookupMap('prefix_a') +} -

-
+// Override the deserializer to load vector from chain +deserialize() { + super.deserialize() + this.m = Object.assign(new LookupMap, this.m) +} -
-Example output -

+someMethod() { + // insert + this.m.set('abc', 'aaa') + this.m.set('def', 'bbb') + this.m.set('ghi', 'ccc') - Scheduling a call: jsvm.testnet.call_js_contract(anN2bXRlc3Rlci50ZXN0bmV0AGhlbGxvAA==) - Doing account.functionCall() - Receipt: DzysE3ZNG8fBY4djq1KDYyDLs53jga2Lxpou2kjm3HzC - Log [jsvm.testnet]: Hello Near - Transaction Id AGRHcCCBCFex2hiXQh5BhFDoq7bN1eVoULhSyL4zgMRA - To see the transaction in the transaction explorer, please open this url in your browser - https://explorer.testnet.near.org/transactions/AGRHcCCBCFex2hiXQh5BhFDoq7bN1eVoULhSyL4zgMRA - '' + // batch insert, extend: + this.m.extend([['xyz', '123'], ['key2', 'value2']]) -

-
+ // check exist + let exist = this.m.containsKey('abc') -Note that, The second call shows this method can be call by anyone (`jsvmtester2.testnet` in above example, make sure you login the account with `near login`), not just the one who deployed this JS contract (`jsvmtester.testnet`). + // get + let value = this.m.get('abc') -## NEAR-SDK-JS Low Level API Reference + // remove + this.m.remove('def') -Use `env.func_name(args)` to call low level APIs in JavaScript contracts. `env` is already imported before contract start. For example, `env.read_register(0)`. -To use nightly host functions, such as `alt_bn128_g1_sum`, the enclave contract need to be built with `NEAR_NIGHTLY=1 ./build.sh` and deployed to a nearcore node that has nightly enabled. + // replace + this.m.set('ghi', 'ddd') +} +``` -### About Type +#### LookupSet +LookupSet is an non-iterable implementation of a set that stores its content directly on the trie. It's like LookupMap, but it only stores whether the value presents. Usage: +```js +import {LookupSet} from 'near-sdk-js' + +// in contract class constructor: +constructor() { + super() + this.s = new LookupSet('prefix_b') +} + +// Override the deserializer to load vector from chain +deserialize() { + super.deserialize() + this.s = Object.assign(new LookupSet, this.s) +} + +someMethod() { + // insert + this.s.set('abc') + this.s.set('def') + this.s.set('ghi') + + // batch insert, extend: + this.s.extend(['xyz', '123']) -- In arguments, `Uint64: Number | BigInt`. In return, `Uint64: BigInt`. Because JavaScript Number cannot hold all Uint64 without losing precision. But as arguments, interger number is also allowed for convinience. Same for `Uint128`. -- `String` in both arguments and return is a byte buffer encoded as a JavaScript String. Which means: - - If the string have only 1 byte chars, the representation is same. - - If the string have 2/3/4 byte char, it is break down to 2/3/4 bytes and each byte as a separate char. - - Arbitrary binary data `0x00-0xff` is as the char '\x00-\xff' + // check exist + let exist = this.s.contains('abc') + + // remove + this.s.remove('def') +} +``` -It's intentional to represent string and bytes in this way because QuickJS doesn't have ArrayBuffer in C API. +#### UnorderedMap +UnorderedMap is an iterable implementation of a map that stores its content directly on the trie. Usage: +```js +import {UnorderedMap} from 'near-sdk-js' -- The signature may differs from Rust APIs. This is because JavaScript doesn't have pointers and not possible to pass pointer as arguments. So, Instead of `data_len: u64, data_ptr: u64`, JavaScript API pass the data directly: `data: String`. -- The lowest level Rust API cannot return value bigger than 64 bit integer. So some of the API pass pointer as Uint64 and the Rust function write return data at the location specified by pointer. In JavaScript we don't have this limitation and value is returned as API function return. +// in contract class constructor: +constructor() { + super() + this.m = new UnorderedMap('prefix_c') +} +// Override the deserializer to load vector from chain +deserialize() { + super.deserialize() + this.m.keys = Object.assign(new Vector, this.m.keys) + this.m.values = Object.assign(new Vector, this.m.values) + this.m = Object.assign(new UnorderedMap, this.m) +} -### About commented APIs -Some of the APIs below starts with `//`. This means this API is provided by nearcore, however they are intentionally removed for the JavaScript Enclave. The reason and alternative are documented in each API section. +someMethod() { + // insert + this.m.set('abc', 'aaa') + this.m.set('def', 'bbb') + this.m.set('ghi', 'ccc') -### Registers API + // batch insert, extend: + this.m.extend([['xyz', '123'], ['key2', 'value2']]) -``` -function read_register(register_id: Uint64): String; -function register_len(register_id: Uint64): Uint64; -function write_register(register_id: Uint64, data: String); -``` + // get + let value = this.m.get('abc') -### Context API + // remove + this.m.remove('def') -``` -// function current_account_id(register_id: Uint64); -function signer_account_id(register_id: Uint64); -function signer_account_pk(register_id: Uint64); -function predecessor_account_id(register_id: Uint64); -// function input(register_id: Uint64); -function block_index(): Uint64; -function block_timestamp(): Uint64; -function epoch_height(): Uint64; -// function storage_usage(): Uint64; -``` + // replace + this.m.set('ghi', 'ddd') -The `current_account_id` would always puts the account id of the JavaScript VM contract account in given register. The naming `current_account_id` is therefore confusing and not as helpful as a Rust contract. In some case, developer may want to get JavaScript VM contract account name, for example, determines whether it's running on testnet or mainnet, and behaves differently. So we expose this functionality under `jsvm_account_id()`. + // len, isEmpty + let len = this.m.len() + let isEmpty = this.m.isEnpty() -The `input` puts the argument passed to call the contract in given register. In JavaScript VM, this is encoded as `"js_contract_name\0method_name\0args...`. This format isn't very convinient to developer, therefore, separate API `jsvm_js_contract_name`, `jsvm_method_name` and `jsvm_args` are provided. + // iterate + for (let [k, v] of this.m) { + near.log(k+v) + } -The `storage_usage` return the storage bytes used by JavaScript VM contract. User doesn't care about the storage usage of the JSVM. Instead, users care about storage usage of a given JavaScript contract. This can be obtained by `storage_read` and count the sum of `register_len`. + // toArray, convert to JavaScript Array + let a = this.m.toArray() -### Economics API -``` -// function account_balance(): Uint128; -// function account_locked_balance(): Uint128; -function attached_deposit(): Uint128; -function prepaid_gas(): Uint64; -function used_gas(): Uint64; + // clear + this.m.clear() +} ``` -The `account_balance` and `account_locked_balance` returns balance and locked_balance of JavaScript VM. Those are also not cared by users. +#### UnorderedSet +UnorderedSet is an iterable implementation of a set that stores its content directly on the trie. It's like UnorderedMap but it only stores whether the value presents. Usage: +```js +import {UnorderedSet} from 'near-sdk-js' -### Math API +// in contract class constructor: +constructor() { + super() + this.s = new UnorderedSet('prefix_d') +} +// Override the deserializer to load vector from chain +deserialize() { + super.deserialize() + this.s.elements = Object.assign(new Vector, this.s.elements) + this.s = Object.assign(new UnorderedSet, this.s) +} -``` -function random_seed(register_id: Uint64); -function sha256(value: String, register_id: Uint64); -function keccak256(value: String, register_id: Uint64); -function keccak512(value: String, register_id: Uint64); -function ripemd160(value: String, register_id: Uint64); -function ecrecover(hash: String, sign: String, v: Uint64, malleability_flag: Uint64, register_id: Uint64): Uint64; -``` +someMethod() { + // insert + this.s.set('abc') + this.s.set('def') + this.s.set('ghi') -### Miscellaneous API + // batch insert, extend: + this.s.extend(['xyz', '123']) + // check exist + let exist = this.s.contains('abc') -``` -// function value_return(value: String); -function panic(msg?: String); -function panic_utf8(msg: String); -function log(msg: String); -function log_utf8(msg: String); -function log_utf16(msg: String); -// Name confliction with WASI. Can be re-exported with a different name on NEAR side with a protocol upgrade -// Or, this is actually not a primitive, can be implement with log and panic host functions in C side or JS side. -// function abort(msg_ptr: Uint32, filename_ptr: Uint32, u32: Uint32, col: Uint32); -``` + // remove + this.s.remove('def') -The `value_return` is a NEAR primitive that puts the value to return in a receipt. However we would want to access it as a JavaScript return value in a cross contract call. So we have a new API `jsvm_value_return`, which does return the value in receipt and also as a JavaScript value returned by `jsvm_call`. The `jsvm_value_return` should be used whenever you need `value_return`. + // len, isEmpty + let len = this.s.len() + let isEmpty = this.s.isEnpty() -### Promises API + // iterate + for (let e of this.s) { + near.log(e) + } -``` -function promise_create(account_id: String, method_name: String, arguments: String, amount: Uint128, gas: Uint64): Uint64; -function promise_then(promise_index: Uint64, account_id: String, method_name: String, arguments: String, amount: Uint128, gas: Uint64): Uint64; -function promise_and(...promise_idx: Uint64): Uint64; -function promise_batch_create(account_id: String): Uint64; -function promise_batch_then(promise_index: Uint64, account_id: String): Uint64; + // toArray, convert to JavaScript Array + let a = this.s.toArray() + + // clear + this.s.clear() +} ``` -### Promise API actions +### APIs not available in JSVM +Due to the architecture of the JSVM, some NEAR host functions, part of Standalone SDK or Rust SDK, are not revelant or being replaced by above JSVM specific APIs. Those unavailable APIs are explained here. -``` -// function promise_batch_action_create_account(promise_index: Uint64); // not allow users to create *.jsvm account -// function promise_batch_action_deploy_contract(promise_index: Uint64, code: String); // batch actions are applied to create_account_action, but that is not allowed -// function promise_batch_action_function_call(promise_index: Uint64, method_name: String, arguments: String, amount: Uint128, gas: Uint64); -// function promise_batch_action_transfer(promise_index: Uint64, amount: Uint128); -// function promise_batch_action_stake(promise_index: Uint64, amount: Uint128, public_key: String); -// function promise_batch_action_add_key_with_full_access(promise_index: Uint64, public_key: String, nonce: Uint64); -// function promise_batch_action_add_key_with_function_call(promise_index: Uint64, public_key: String, nonce: Uint64, allowance: Uint128, receiver_id: String, method_names: String); -// function promise_batch_action_delete_key(promise_index: Uint64, public_key: String); -// function promise_batch_action_delete_account(promise_index: Uint64, beneficiary_id: String); -``` +- The `current_account_id` would always puts the account id of the JavaScript VM contract account in given register. The naming `current_account_id` is therefore confusing and not as helpful as a Rust contract. In some case, developer may want to get JavaScript VM contract account name, for example, determines whether it's running on testnet or mainnet, and behaves differently. So we expose this functionality under `jsvm_account_id()`. -All Promise batch actions act on the JSVM contract, creating a subaccount of it and acting on it. JSVM would be a common VM used by the community instead of a Rust contract owned by the deployer. Terefore, creating subaccounts and subsequent actions towards subaccounts are not allowed. +- The `input` puts the argument passed to call the contract in given register. In JavaScript VM, this is encoded as `"js_contract_name\0method_name\0args...`. This format isn't very convinient to developer, therefore, separate API `jsvm_js_contract_name`, `jsvm_method_name` and `jsvm_args` are provided. -### Promise API results +- The `storage_usage` return the storage bytes used by JavaScript VM contract. User doesn't care about the storage usage of the JSVM. Instead, users care about storage usage of a given JavaScript contract. This can be obtained by `storage_read` and count the sum of `register_len`. -``` -function promise_results_count(void): Uint64; -function promise_result(result_idx: Uint64, register_id: Uint64): Uint64; -function promise_return(promise_idx: Uint64); -``` +- The `account_balance` and `account_locked_balance` returns balance and locked_balance of JavaScript VM. Those are also not cared by users. -### Storage API +- The `value_return` is a NEAR primitive that puts the value to return in a receipt. However we would want to access it as a JavaScript return value in a cross contract call. So we have a new API `jsvmValueReturn`, which does return the value in receipt and also as a JavaScript value returned by `jsvm_call`. The `jsvmValueReturn` should be used whenever you need `value_return`. -``` -// function storage_write(key: String, value: String, register_id: Uint64): Uint64; // user can only access contract's storage -function storage_read(key: String, register_id: Uint64): Uint64; -// function storage_remove(key: String, register_id: Uint64): Uint64; // same as storage_write -function storage_has_key(key: String): Uint64; -``` +- `abort` is intended to mark error location (line number). A full stacktrace with line numbers is provided by QuickJS, available when you throw a JS Error. So this API isn't needed. -The `storage_write` and `storage_remove` have access to all JavaScript contract codes and states deployed on JSVM. User can only write to their account owned code and state, as a substate of the JSVM. Therefor these two APIs are disallowed. Use `jsvm_storage_write` and `jsvm_storage_remove` instead. Read to other people owned code and state is allowed, as they're public as part of the blockchain anyway. +- Promise APIs act on the JSVM contract and could create subaccount, use the balance from JSVM account.JSVM would be a common VM used by the community instead of a Rust contract owned by the deployer. Therefore, promise APIs are not allowed. -### Validator API +- The `storage_write` and `storage_remove` have access to all JavaScript contract codes and states deployed on JSVM. User can only write to their account owned code and state, as a substate of the JSVM. Therefor these two APIs are disallowed. Use `jsvm_storage_write` and `jsvm_storage_remove` instead. Read to other people owned code and state is allowed, as they're public as part of the blockchain anyway. -``` -function validator_stake(account_id: String): Uint128; -function validator_total_stake(): Uint128; -``` -### Alt BN128 +## Advanced guides +### Manual setup with npm package + +You can also layout your project by install the npm package manually: ``` -function alt_bn128_g1_multiexp(value: String, register_id: Uint64); -function alt_bn128_g1_sum(value: String, register_id: Uint64); -function alt_bn128_pairing_check(value: String): Uint64; +yarn add near-sdk-js +# or +npm install near-sdk-js ``` -## JSVM Specific APIs -Due to the design of JavaScript VM Contract, some additonal APIs are provided to obtain context, access storage and cross contract call. Since they're not documented at [NEAR nomicon](https://nomicon.io/). They're explained here. +### NEAR-SDK-JS contributor setup -### Obtain Context -``` -function jsvm_account_id(register_id: Uint64); -function jsvm_js_contract_name(register_id: Uint64); -function jsvm_method_name(register_id: Uint64); -function jsvm_args(register_id: Uint64); -``` +It is tested on Ubuntu 20.04, Intel Mac and M1 Mac. Other linux should also work but they're not tested. -The `jsvm_account_id` put the JavaScript VM's contract account ID into given register. +1. Make sure you have `wget`, `make`, `cmake` and `nodejs`. On Linux, also make sure you have `gcc`. +2. Run `make` to get platform specific `qjsc` and `jsvm` contract in `res` folder. -The `jsvm_js_contract_name`, when called, put the JavaScript contract name that are called at the moment, into given register. -The `jsvm_method_name` put the method name being called into given register. +### Run NEAR-SDK-JS tests +See https://github.com/near/near-sdk-js/tree/master/tests -The `jsvm_args` return the arguments passed to the method, into given register. -### Storage Access -``` -function jsvm_storage_write(key: String, value: String, register_id: Uint64): Uint64; -function jsvm_storage_read(key: String, register_id: Uint64): Uint64; -function jsvm_storage_remove(key: String, register_id: Uint64): Uint64; -function jsvm_storage_has_key(key: String): Uint64; -``` +### Low level way to invoke NEAR-CLI -These are equivalent to `storage_*` but access limit to the substate of current JS contract. The `jsvm_storage_write` and `jsvm_storage_remove` require and refund deposit to cover the storage delta. `jsvm_storage_*` access the substate of current JS contract by prefix the key of current JS contract name (deployer's account id). You can use `storage_read` and `storage_has_key` to get code and state of other JS contracts. More specifically: code of `contractA` is stored under the key `contractA/code`. state of `contractA` is stored under `contractA/state/` concat with developer specifid key. And: -``` -jsvm_storage_read(k, register_id) -// equvalent to -storage_read(jsvm_js_contract_name + '/state/' + k) -``` +`near js` subcommand in near-cli is a recent feature. Under the hood, it is encoding a special function call to jsvm contract. -### Cross Contract Call -``` -function jsvm_value_return(value: String); -function jsvm_call(contract_name: String, method: String, args: String, register_id: Uint64); -``` +#### Deploy a JS contract -The `jsvm_value_return` is the version of `value_return` that should be used in all JavaScript contracts. It play well with `jsvm_call`. +
+The equivalent raw command is: +

-The `jsvm_call` invoke a synchronous cross contract call, to the given JavaScript `contract_name`, `method` with `args`. And capture the value returned from the call and stored in `register_id`. + near call deploy_js_contract --accountId --args $(cat .base64) --base64 --deposit 0.1 -## Error Handling in NEAR-SDK-JS +

+
-### Error handling behavior +#### Call a JS contract -- when js throws an error, uncatched, then transaction fails with GuestPanic error, with the user js error message and stacktrace -- when call host function with inappropriate type, means incorrect number of arguments or arg is not expected type: - - if arguments less than params, remaining argument are set as 'undefined' - - if arguments more than params, remaining argument are ignored - - if argument is different than the required type, it'll be coerced to required type - - if argument is different than the required type but cannot be coerced, will throw runtime type error, also with message and stacktrace +
+The equivalent raw command is: +

-### The error reporting capability of a wasm contract -Smart contract can only use `panic` or `panic_utf8` to abort from execution. That is of error kind `GuestPanic {msg}`. It displays in RPC as `"Smart contract panicked: {msg}"` -And only `panic_utf8` can set that message. -Other than this, if calls a host function, it can returns error provided by that host function. For example, any host function can return a `GasExceeded`. `log_utf8` can return `BadUTF8`. This behavior is part of protocol and we cannot control or trigger in JavaScript (without calling `env.*`). + near call call_js_contract --accountId --args --base64 -### Use errors -You can throw an error in JavaScript. Our quickjs runtime will detect and automatically invoke `panic_utf8` with `"{error.message}\n:{error.stack}"`. As a result, transaction will fail with `"Smart contract panicked: {error.message}\n{error.stack}"` error message. + # where `` can be obtained by: + node scripts/encode_call.js -### Use verror -User can use verror this way: -1. catch an error, attach information to it -2. return/rethrow the error, attach more information to it -3. throw the final verror, `throw e`, same as in nodejs. +

+
-Under the hood, our quickjs runtime would take the final throwed error, and invoke panic_utf8("{error.message}\n{error.stack}") +#### Remove a JS contract -## Debug and Test -To get more debug utilities, such as debug print (`debug.log`) and logging stacktrace, you can build JSVM with sandbox flag: `NEAR_SANDBOX=1 ./build.sh`. A `jsvm_sandbox.wasm` will be build in current directory. You can then deploy sandbox versioned jsvm to a local [near-sandbox](https://github.com/near/sandbox). near-sandbox can be launched either manually or via the official testing framework [near-workspaces](https://github.com/near/workspaces-js). We recommend to use near-workspaces to write tests for your smart contracts. An example of use near-workspaces in a JS contract project can be found in `examples/project/`. +
+The equivalent raw command is: +

+ + near call remove_js_contract --accountId -Note that, `jsvm.wasm` can be used for sandbox/near-workspaces as well. But to debug print, you need to use `jsvm_sandbox.wasm` instead. +

+
diff --git a/jsvm/jsvm.c b/jsvm/jsvm.c index c44926b87..4befcd311 100644 --- a/jsvm/jsvm.c +++ b/jsvm/jsvm.c @@ -65,17 +65,10 @@ extern void panic(void); extern void panic_utf8(uint64_t len, uint64_t ptr); extern void log_utf8(uint64_t len, uint64_t ptr); extern void log_utf16(uint64_t len, uint64_t ptr); -// Name confliction with WASI. Can be re-exported with a different name on NEAR side with a protocol upgrade -// Or, this is actually not a primitive, can be implement with log and panic host functions in C side or JS side. -// extern void abort(uint32_t msg_ptr, uint32_t filename_ptr, uint32_t u32, uint32_t col); // ################ // # Promises API # // ################ -extern uint64_t promise_create(uint64_t account_id_len, uint64_t account_id_ptr, uint64_t method_name_len, uint64_t method_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas); -extern uint64_t promise_then(uint64_t promise_index, uint64_t account_id_len, uint64_t account_id_ptr, uint64_t method_name_len, uint64_t method_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas); -extern uint64_t promise_and(uint64_t promise_idx_ptr, uint64_t promise_idx_count); extern uint64_t promise_batch_create(uint64_t account_id_len, uint64_t account_id_ptr); -extern uint64_t promise_batch_then(uint64_t promise_index, uint64_t account_id_len, uint64_t account_id_ptr); // ####################### // # Promise API actions # // ####################### @@ -83,8 +76,6 @@ extern void promise_batch_action_transfer(uint64_t promise_index, uint64_t amoun // ####################### // # Promise API results # // ####################### -extern uint64_t promise_results_count(void); -extern uint64_t promise_result(uint64_t result_idx, uint64_t register_id); extern void promise_return(uint64_t promise_idx); // ############### // # Storage API # @@ -460,128 +451,6 @@ static JSValue near_log_utf16(JSContext *ctx, JSValueConst this_val, int argc, J return JS_UNDEFINED; } -static JSValue near_promise_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - const char *account_id_ptr, *method_name_ptr, *arguments_ptr; - size_t account_id_len, method_name_len, arguments_len; - uint64_t amount_ptr[2]; // amount is u128 - uint64_t gas, ret; - - account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[0]); - method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[1]); - arguments_ptr = JS_ToCStringLenRaw(ctx, &arguments_len, argv[2]); - if (quickjs_to_u128(ctx, argv[3], amount_ptr) != 0) { - return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); - } - if (JS_ToUint64Ext(ctx, &gas, argv[4]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); - } - - ret = promise_create(account_id_len, (uint64_t)account_id_ptr, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas); - - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_then(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t promise_index; - const char *account_id_ptr, *method_name_ptr, *arguments_ptr; - size_t account_id_len, method_name_len, arguments_len; - uint64_t amount_ptr[2]; // amount is u128 - uint64_t gas, ret; - - if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); - } - account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[1]); - method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[2]); - arguments_ptr = JS_ToCStringLenRaw(ctx, &arguments_len, argv[3]); - if (quickjs_to_u128(ctx, argv[4], amount_ptr) != 0) { - return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); - } - if (JS_ToUint64Ext(ctx, &gas, argv[5]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); - } - - ret = promise_then(promise_index, account_id_len, (uint64_t)account_id_ptr, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas); - - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_and(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t promise_idx_ptr[argc], ret; - - for(int i = 0; i < argc; i++) { - if (JS_ToUint64Ext(ctx, &promise_idx_ptr[i], argv[i]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_id"); - } - } - ret = promise_and((uint64_t)promise_idx_ptr, argc); - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_batch_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - const char *account_id_ptr; - size_t account_id_len; - uint64_t ret; - - account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[0]); - ret = promise_batch_create(account_id_len, (uint64_t)account_id_ptr); - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_batch_then(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t promise_index; - const char *account_id_ptr; - size_t account_id_len; - uint64_t ret; - - if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); - } - account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[1]); - ret = promise_batch_then(promise_index, account_id_len, (uint64_t)account_id_ptr); - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_results_count(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t value; - - value = promise_results_count(); - return JS_NewBigUint64(ctx, value); -} - -static JSValue near_promise_result(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t result_idx, register_id; - uint64_t ret; - - if (JS_ToUint64Ext(ctx, &result_idx, argv[0]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for result_idx"); - } - if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); - } - ret = promise_result(result_idx, register_id); - - return JS_NewBigUint64(ctx, ret); -} - -static JSValue near_promise_return(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - uint64_t promise_idx; - if (JS_ToUint64Ext(ctx, &promise_idx, argv[0]) < 0) { - return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_idx"); - } - promise_return(promise_idx); - - return JS_UNDEFINED; -} - const uint64_t STORAGE_PRICE_PER_BYTE_U64 = 10000000000000000000ul; const char *STORAGE_PRICE_PER_BYTE = "10000000000000000000"; @@ -1151,14 +1020,6 @@ static void js_add_near_host_functions(JSContext* ctx) { JS_SetPropertyStr(ctx, env, "log", JS_NewCFunction(ctx, near_log, "log", 1)); JS_SetPropertyStr(ctx, env, "log_utf8", JS_NewCFunction(ctx, near_log_utf8, "log_utf8", 1)); JS_SetPropertyStr(ctx, env, "log_utf16", JS_NewCFunction(ctx, near_log_utf16, "log_utf16", 1)); - JS_SetPropertyStr(ctx, env, "promise_create", JS_NewCFunction(ctx, near_promise_create, "promise_create", 5)); - JS_SetPropertyStr(ctx, env, "promise_then", JS_NewCFunction(ctx, near_promise_then, "promise_then", 6)); - JS_SetPropertyStr(ctx, env, "promise_and", JS_NewCFunction(ctx, near_promise_and, "promise_and", 1)); - JS_SetPropertyStr(ctx, env, "promise_batch_create", JS_NewCFunction(ctx, near_promise_batch_create, "promise_batch_create", 1)); - JS_SetPropertyStr(ctx, env, "promise_batch_then", JS_NewCFunction(ctx, near_promise_batch_then, "promise_batch_then", 2)); - JS_SetPropertyStr(ctx, env, "promise_results_count", JS_NewCFunction(ctx, near_promise_results_count, "promise_results_count", 0)); - JS_SetPropertyStr(ctx, env, "promise_result", JS_NewCFunction(ctx, near_promise_result, "promise_result", 2)); - JS_SetPropertyStr(ctx, env, "promise_return", JS_NewCFunction(ctx, near_promise_return, "promise_return", 1)); JS_SetPropertyStr(ctx, env, "storage_read", JS_NewCFunction(ctx, near_storage_read, "storage_read", 2)); JS_SetPropertyStr(ctx, env, "storage_has_key", JS_NewCFunction(ctx, near_storage_has_key, "storage_has_key", 1)); JS_SetPropertyStr(ctx, env, "validator_stake", JS_NewCFunction(ctx, near_validator_stake, "validator_stake", 2)); diff --git a/res/jsvm.wasm b/res/jsvm.wasm index 1326e3d0d..a480b98da 100755 Binary files a/res/jsvm.wasm and b/res/jsvm.wasm differ diff --git a/res/jsvm_nightly.wasm b/res/jsvm_nightly.wasm new file mode 100755 index 000000000..84a591033 Binary files /dev/null and b/res/jsvm_nightly.wasm differ diff --git a/src/api.js b/src/api.js index aa2c34956..c58faf387 100644 --- a/src/api.js +++ b/src/api.js @@ -16,6 +16,30 @@ export function predecessorAccountId() { return env.read_register(0) } +export function blockIndex() { + return env.block_index() +} + +export function blockTimestamp() { + return env.block_timestamp() +} + +export function epochHeight() { + return env.epoch_height() +} + +export function attachedDeposit() { + return env.attached_deposit() +} + +export function prepaidGas() { + return env.prepaid_gas() +} + +export function usedGas() { + return env.used_gas() +} + export function randomSeed() { env.random_seed(0) return env.read_register(0) @@ -49,7 +73,21 @@ export function ecrecover(hash, sign, v, malleabilityFlag) { return env.read_register(0) } -// TODO: env.promise_result returns need additioonal handling +export function panic(msg) { + env.panic(msg) +} + +export function panicUtf8(msg) { + env.panic_utf8(msg) +} + +export function logUtf8(msg) { + env.log_utf8(msg) +} + +export function logUtf16(msg) { + env.log_utf16(msg) +} export function storageRead(key) { let ret = env.storage_read(key, 0) @@ -60,6 +98,23 @@ export function storageRead(key) { } } +export function storageHasKey(key) { + let ret = env.storage_has_key(key) + if (ret === 1n) { + return true + } else { + return false + } +} + +export function validatorStake(accountId) { + return env.validator_stake(accountId) +} + +export function validatorTotalStake() { + return env.validator_total_stake() +} + export function altBn128G1Multiexp(value) { env.alt_bn128_g1_multiexp(value, 0) return env.read_register(0) @@ -70,6 +125,15 @@ export function altBn128G1Sum(value) { return env.read_register(0) } +export function altBn128PairingCheck(value) { + let ret = env.alt_bn128_pairing_check(value) + if (ret === 1n) { + return true + } else { + return false + } +} + export function jsvmAccountId() { env.jsvm_account_id(0) return env.read_register(0) @@ -122,11 +186,23 @@ export function jsvmStorageHasKey(key) { return false } -export function jsvmCall(contractName, method, args) { +export function jsvmCallRaw(contractName, method, args) { env.jsvm_call(contractName, method, JSON.stringify(args), 0) - return JSON.parse(env.read_register(0) || 'null') + return env.read_register(0) +} + +export function jsvmCall(contractName, method, args) { + let ret = jsvmCallRaw(contractName, method, args) + if (ret === null) { + return ret + } + return JSON.parse(ret) } export function storageGetEvicted() { return env.read_register(EVICTED_REGISTER) +} + +export function jsvmValueReturn(value) { + env.jsvm_value_return(value) } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index f78570d02..79a336afb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -12,4 +12,8 @@ export function stringToU8Array(string) { ret[i] = string.charCodeAt(i) } return ret +} + +export 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))]) } \ No newline at end of file