From dd2aab858e2255b26ad114e771299241b600d1f1 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 21 May 2021 10:52:23 -0700 Subject: [PATCH 01/37] Docs for how to start a sandbox node and run e2e test on it --- docs/develop/contracts/sandbox.md | 194 ++++++++++++++++++++++++++++++ website/sidebars.json | 23 ++-- 2 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 docs/develop/contracts/sandbox.md diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md new file mode 100644 index 00000000000..e1e9015997e --- /dev/null +++ b/docs/develop/contracts/sandbox.md @@ -0,0 +1,194 @@ +--- +id: sandbox +title: End-to-end Test in Sandbox +sidebar_label: Test in Sandbox +--- + +After you wrote some awesome contracts and did some unit tests, let's see how your contracts will behave in a real node. `near-sandbox` is the right tool to go, it includes all components as a real testnet node does but runs locally. And it provides additional features like patch blockchain state on the fly and fast forward in time that makes certain tests easier. + +> If you come from Ethereum, this workflow would sound familiar to you: You wrote e2e test in JavaScript, start a local `ganache` node, then run `truffle test` to execute tests on either local `ganache` or Ethereum Testnet. + +## Start and Stop Sandbox Node + +As of now, you need to start a sandbox manually. +First clone the nearcore repo: + +```bash +git clone https://github.com/near/nearcore +cd nearcore +``` + +Then let's build the sandbox binary, it takes several minutes depend on your CPU. + +```bash +make sandbox +``` + +Now we can start sandbox node: + +```bash +target/debug/near-sandbox --home /tmp/near-sandbox init +target/debug/near-sandbox --home /tmp/near-sandbox run +``` + +After you're done with sandbox node, you can stop it by `Ctrl-C`. And to clean up the data it generates, just `rm -rf /tmp/near-sandbox`. + +## Run an End-to-end Test in Sandbox + +Let's take status-message contract as an example. You can get this contract from: https://github.com/near/near-sdk-rs/blob/master/examples/status-message/res/status_message.wasm. This is a very straightforward contract. It has two methods: + +```text +set_status(message: string) +get_status(account_id: string) -> string or null +``` + +`set_status` method stores message string under the sender's account name key on chain. And get_status retrieve message of given account name. If it were set by `set_status` before, it returns that string, otherwise it returns null. + +1. Start a near-sandbox node. If you have run a sandbox node with some tests before, delete `/tmp/near-sandbox` before start near-sandbox. +2. Assume you clone the repo with status-message and source code stays in `status-message/`. The compiled contract lives in `status-message/res`. Let's do some preparation for the test: + +```bash +cd status-message +npm init +npm i near-api-js bn.js +``` + +3. Write a test `test.js` that does deploy the contract, test with the contract logic: + +```javascript +const nearAPI = require("near-api-js"); +const BN = require("bn.js"); +const fs = require("fs").promises; +const assert = require("assert").strict; + +function getConfig(env) { + switch (env) { + case "sandbox": + case "local": + return { + networkId: "sandbox", + nodeUrl: "http://localhost:3030", + masterAccount: "test.near", + contractAccount: "status-message.test.near", + keyPath: "/tmp/near-sandbox/validator_key.json", + }; + } +} + +const contractMethods = { + viewMethods: ["get_status"], + changeMethods: ["set_status"], +}; +let config; +let masterAccount; +let masterKey; +let pubKey; +let keyStore; +let near; + +async function initNear() { + config = getConfig(process.env.NEAR_ENV || "sandbox"); + const keyFile = require(config.keyPath); + masterKey = nearAPI.utils.KeyPair.fromString( + keyFile.secret_key || keyFile.private_key + ); + pubKey = masterKey.getPublicKey(); + keyStore = new nearAPI.keyStores.InMemoryKeyStore(); + keyStore.setKey(config.networkId, config.masterAccount, masterKey); + near = await nearAPI.connect({ + deps: { + keyStore, + }, + networkId: config.networkId, + nodeUrl: config.nodeUrl, + }); + masterAccount = new nearAPI.Account(near.connection, config.masterAccount); + console.log("Finish init NEAR"); +} + +async function createContractUser( + accountPrefix, + contractAccountId, + contractMethods +) { + let accountId = accountPrefix + "." + config.masterAccount; + await masterAccount.createAccount( + accountId, + pubKey, + new BN(10).pow(new BN(25)) + ); + keyStore.setKey(config.networkId, accountId, masterKey); + const account = new nearAPI.Account(near.connection, accountId); + const accountUseContract = new nearAPI.Contract( + account, + contractAccountId, + contractMethods + ); + return accountUseContract; +} + +async function initTest() { + const contract = await fs.readFile("./res/status_message.wasm"); + const _contractAccount = await masterAccount.createAndDeployContract( + config.contractAccount, + pubKey, + contract, + new BN(10).pow(new BN(25)) + ); + + const aliceUseContract = await createContractUser( + "alice", + config.contractAccount, + contractMethods + ); + + const bobUseContract = await createContractUser( + "bob", + config.contractAccount, + contractMethods + ); + console.log("Finish deploy contracts and create test accounts"); + return { aliceUseContract, bobUseContract }; +} + +async function test() { + // 1 + await initNear(); + const { aliceUseContract, bobUseContract } = await initTest(); + + // 2 + await aliceUseContract.set_status({ args: { message: "hello" } }); + let alice_message = await aliceUseContract.get_status({ + account_id: "alice.test.near", + }); + assert.equal(alice_message, "hello"); + + // 3 + let bob_message = await bobUseContract.get_status({ + account_id: "bob.test.near", + }); + assert.equal(bob_message, null); + + // 4 + await bobUseContract.set_status({ args: { message: "world" } }); + bob_message = await bobUseContract.get_status({ + account_id: "bob.test.near", + }); + assert.equal(bob_message, "world"); + alice_message = await aliceUseContract.get_status({ + account_id: "alice.test.near", + }); + assert.equal(alice_message, "hello"); +} + +test(); +``` + +The test itself is very straightfoward. It does: + +1. Create testing accounts and deploy contract. +2. Alice sign the transaction to set status, and get status to see set status works. +3. Get Bob's status and that should be null because Bob has not set status. +4. Bob set status and get status, should show Bob's status changed and not affect Alice status. + +> Most of the code above is boilerplate setup code to setup NEAR API, key pairs, testing accounts and deploy the contract. We're working on a near-cli `near test` command to do these setup code so you can focus on writing only `test()` for this kind of test. diff --git a/website/sidebars.json b/website/sidebars.json index 464fb1f282e..c73b110b0be 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -32,6 +32,7 @@ ], "Smart Contracts": [ "develop/contracts/overview", + "develop/contracts/sandbox", { "type": "subcategory", "label": "AssemblyScript", @@ -57,15 +58,15 @@ ], "Aurora Engine": [ { - "type": "subcategory", - "label": "EVM on NEAR", - "ids": [ - "develop/evm/introduction", - "develop/evm/evm-network-status", - "develop/evm/truffle", - "develop/evm/near-eth-rpc", - "develop/evm/near-web3-provider", - "develop/evm/evm-testing" + "type": "subcategory", + "label": "EVM on NEAR", + "ids": [ + "develop/evm/introduction", + "develop/evm/evm-network-status", + "develop/evm/truffle", + "develop/evm/near-eth-rpc", + "develop/evm/near-web3-provider", + "develop/evm/evm-testing" ] } ] @@ -90,7 +91,6 @@ "tutorials/front-end/naj-workshop" ], "Smart Contracts": [ - { "type": "subcategory", "label": "AssemblyScript", @@ -112,7 +112,6 @@ "Rainbow Bridge": [ "tutorials/rainbow-bridge-frontend-mainnet", "tutorials/rainbow-bridge-frontend-testnet" - ], "Videos": [ "videos/accounts-keys", @@ -184,4 +183,4 @@ "community/faq" ] } -} +} \ No newline at end of file From a46d05ef7b9220093f407b4799d7b3d2573a41f4 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 21 May 2021 17:32:12 -0700 Subject: [PATCH 02/37] example of use patch_state --- docs/develop/contracts/sandbox.md | 153 ++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index e1e9015997e..755370f4f5b 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -192,3 +192,156 @@ The test itself is very straightfoward. It does: 4. Bob set status and get status, should show Bob's status changed and not affect Alice status. > Most of the code above is boilerplate setup code to setup NEAR API, key pairs, testing accounts and deploy the contract. We're working on a near-cli `near test` command to do these setup code so you can focus on writing only `test()` for this kind of test. + +## Sandbox-only Features for Testing + +If you only use above test script that only uses standard NEAR rpcs, your tests can also be executed on a testnet node. Just replace the network ID, node url, key path and account names in above script and rerun. There's also some additional Sandbox-only features that make certain tests easier. We'll review some examples in this section. + +### Patch State on the Fly + +You can add or modify any contract state, contract code, account or access key during the test with `sandbox_patch_state` RPC. + +For arbitrary mutation on contract state, you cannot do it via transactions since transaction can only include contract calls that mutate state in contract programmed way. For example, for a NFT contract, you can do some operation with your owned NFTs but you cannot manipulate other people owned NFTs because smart contract has coded with checks to reject that. This is the expected behavior of the NFT contract. However you may want to change other people's NFT for test setup. This is called "arbitary mutation on contract state" and can be done by `sandbox_patch_state` RPC or stop the node, dump state as genesis, edit genesis and restart the node. The later approach is more complicated to do and also cannot be done without restart the node. + +For patch contract code, account or access key, you can add them with normal deploy contract, create account or add key actions in transaction, but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. + +Let's see an example of how patch_state would help in a test. Assume you want to mock the real state of a mainnet, where `alice.near` has set a status, and you want to retrieve that message. Above script doesn't work out of box, because your master account is `test.near` and you can only +create account of `alice.test.near`, not `alice.near`. Patch state can solve this problem. + +1. Fetch current state from sandbox node. You can also do this with `sendJsonRpc` of `near-api-js`, or with any http client from command line: + +```bash +$ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id":1, "method":"query", "params":{"request_type":"view_state","finality":"final", "account_id":"status-message.test.near","prefix_base64":""}}' + +{"jsonrpc":"2.0","result":{"values":[{"key":"U1RBVEU=","value":"AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==","proof":[]}],"proof":[],"block_height":24229,"block_hash":"XeCMK1jLNCu2UbkAKk1LLXEQVqvUASLoxSEz1YVBfGH"},"id":1} +``` + +You can see the contract only has one key-value pair in state, looks like base64 encoded. Let's figure out what it is. + +2. `npm i borsh` and create a JavaScript file with following content: + +```javascript +const borsh = require("borsh"); + +class Assignable { + constructor(properties) { + Object.keys(properties).map((key) => { + this[key] = properties[key]; + }); + } +} + +class StatusMessage extends Assignable {} + +class Record extends Assignable {} + +const schema = new Map([ + [StatusMessage, { kind: "struct", fields: [["records", [Record]]] }], + [ + Record, + { + kind: "struct", + fields: [ + ["k", "string"], + ["v", "string"], + ], + }, + ], +]); + +const stateKey = "U1RBVEU="; +console.log(Buffer.from(stateKey, "base64")); +console.log(Buffer.from(stateKey, "base64").toString()); +const stateValue = + "AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA=="; +const stateValueBuffer = Buffer.from(stateValue, "base64"); +const statusMessage = borsh.deserialize( + schema, + StatusMessage, + stateValueBuffer +); +console.log(statusMessage); +``` + +3. Run it with nodejs, we'll get: + +```text + +STATE +StatusMessage { + records: [ + Record { k: 'alice.test.near', v: 'hello' }, + Record { k: 'bob.test.near', v: 'world' } + ] +} +``` + +So the key of the key-value pair is ASCII string `STATE`. This is because all contracts written with near-sdk-rs store the main contract struct under this key. The value of the key-value pair is borsh serialized account-message items. The exact content is as expected as we inserted these two StatusMessage records in the previous test. + +4. Now let's add an message for `alice.near` directly to the state: + +```javascript +statusMessage.records.push(new Record({ k: "alice.near", v: "hello world" })); +console.log(statusMessage); +``` + +5. It looks good now, let's serialize it and base64 encode it so it can be used in patch_state RPC: + +```javascript +console.log( + Buffer.from(borsh.serialize(schema, statusMessage)).toString("base64") +); +``` + +You'll get: + +```text +AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk +``` + +6. Patch state with curl: + +``` +curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id":1, "method":"sandbox_patch_state", "params":{ + "records": [ + { + "Data": { + "account_id": "status-message.test.near", + "data_key": "U1RBVEU=", + "value": "AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk" + } + } + ] +}}' +``` + +7. Now we can back to the test file and rerun the test with new state. + Comment these two lines which create accounts and deploy contract, because they're already created in the first test: + +```javascript +// await masterAccount.createAccount( +// accountId, +// pubKey, +// new BN(10).pow(new BN(25)) +// ); + +// const _contractAccount = await masterAccount.createAndDeployContract( +// config.contractAccount, +// pubKey, +// contract, +// new BN(10).pow(new BN(25)) +// ); +``` + +7. comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: + +```javascript +let alice_mainnet_message = await bobUseContract.get_status({ + account_id: "alice.near", +}); +assert.equal(alice_mainnet_message, "hello world"); +``` + +Rerun the test it should pass. + +> In theory, you can also do sendJsonRpc with near-api-js to patch state during a test. However, current behavior of near-api-js doesn't allow empty string result from RPC response, which is the case for sandbox_patch_state. We're working on a more integrated and automatic way in near-api-js, so that we'll have an API to patch state without need to figure out all details of encoding state and do it with a raw sendJsonRpc. From d1b9ab4317ad0b6d69d9205fa9e3c0f2b8dfd022 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Mon, 24 May 2021 14:26:27 -0700 Subject: [PATCH 03/37] patch state with rpc in js --- docs/develop/contracts/sandbox.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 755370f4f5b..d5de034f6ef 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -336,6 +336,28 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " 7. comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: ```javascript +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +let client = new nearAPI.providers.JsonRpcProvider(config.nodeUrl); +let key = Buffer.from("STATE").toString("base64"); + +// Here is how patch state can be used +await client.sendJsonRpc("sandbox_patch_state", { + records: [ + { + Data: { + account_id: config.contractAccount, + data_key: key, + value: + "AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk", + }, + }, + ], +}); + +await sleep(5000); // patch state is async, will be handled in next block, sleep so it's applied let alice_mainnet_message = await bobUseContract.get_status({ account_id: "alice.near", }); @@ -343,5 +365,3 @@ assert.equal(alice_mainnet_message, "hello world"); ``` Rerun the test it should pass. - -> In theory, you can also do sendJsonRpc with near-api-js to patch state during a test. However, current behavior of near-api-js doesn't allow empty string result from RPC response, which is the case for sandbox_patch_state. We're working on a more integrated and automatic way in near-api-js, so that we'll have an API to patch state without need to figure out all details of encoding state and do it with a raw sendJsonRpc. From f6eef4a07f376a69fd1441c84d6de1b80c3aeb40 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Mon, 24 May 2021 15:42:17 -0700 Subject: [PATCH 04/37] use git version of near-api-js to fix a rpc bug --- docs/develop/contracts/sandbox.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index d5de034f6ef..74fc1ded76d 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -53,6 +53,14 @@ npm init npm i near-api-js bn.js ``` +Change `package.json`, specify near-api-js version to be: + +``` + "near-api-js": "near/near-api-js#bace1ee" +``` + +And run `npm i` again. + 3. Write a test `test.js` that does deploy the contract, test with the contract logic: ```javascript From a7651d8a55ea63cf88b48b413ffec1aadc29f0d5 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Thu, 27 May 2021 14:47:28 -0700 Subject: [PATCH 05/37] patch state is now sync, do not need sleep --- docs/develop/contracts/sandbox.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 74fc1ded76d..d261b753854 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -344,10 +344,6 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " 7. comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: ```javascript -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - let client = new nearAPI.providers.JsonRpcProvider(config.nodeUrl); let key = Buffer.from("STATE").toString("base64"); @@ -365,7 +361,6 @@ await client.sendJsonRpc("sandbox_patch_state", { ], }); -await sleep(5000); // patch state is async, will be handled in next block, sleep so it's applied let alice_mainnet_message = await bobUseContract.get_status({ account_id: "alice.near", }); From 2fb05f322fd543681a4626a3bd0c5cb14fe63634 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:56:02 -0700 Subject: [PATCH 06/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index d261b753854..107ade2a29d 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -4,7 +4,7 @@ title: End-to-end Test in Sandbox sidebar_label: Test in Sandbox --- -After you wrote some awesome contracts and did some unit tests, let's see how your contracts will behave in a real node. `near-sandbox` is the right tool to go, it includes all components as a real testnet node does but runs locally. And it provides additional features like patch blockchain state on the fly and fast forward in time that makes certain tests easier. +Once you've written some awesome contracts and performed a few unit tests the next step is to see how your contracts will behave on a real node. `near-sandbox` is the perfect solution for this as it includes all components of a live `testnet` node but runs locally on your machine. Additionally, it provides features such as patching blockchain state on the fly and fast forwarding in time that makes certain tests easier. > If you come from Ethereum, this workflow would sound familiar to you: You wrote e2e test in JavaScript, start a local `ganache` node, then run `truffle test` to execute tests on either local `ganache` or Ethereum Testnet. From 12c4dda35e92555460f737f343478a81f1dac267 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:56:17 -0700 Subject: [PATCH 07/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 107ade2a29d..c61e184fe63 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -6,7 +6,11 @@ sidebar_label: Test in Sandbox Once you've written some awesome contracts and performed a few unit tests the next step is to see how your contracts will behave on a real node. `near-sandbox` is the perfect solution for this as it includes all components of a live `testnet` node but runs locally on your machine. Additionally, it provides features such as patching blockchain state on the fly and fast forwarding in time that makes certain tests easier. -> If you come from Ethereum, this workflow would sound familiar to you: You wrote e2e test in JavaScript, start a local `ganache` node, then run `truffle test` to execute tests on either local `ganache` or Ethereum Testnet. +> Coming from Ethereum the typical workflow would be something like: + +- Writing e2e test in JavaScript +- Start a local `ganache` node +- Run `truffle test` to execute tests on either local `ganache` or Ethereum Testnet. ## Start and Stop Sandbox Node From 1048cec4786252addf2787465c66a52eeea0de3d Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:57:08 -0700 Subject: [PATCH 08/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index c61e184fe63..9a06c93f63c 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -14,7 +14,7 @@ Once you've written some awesome contracts and performed a few unit tests the ne ## Start and Stop Sandbox Node -As of now, you need to start a sandbox manually. +> Currently, to start the sandbox node you will need to do so manually. Here are the steps to start and stop a sandbox node: First clone the nearcore repo: ```bash From 1a633aca8c97938d8144e37554fd61ddeed73287 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:57:21 -0700 Subject: [PATCH 09/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 9a06c93f63c..ca07099588d 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -15,7 +15,7 @@ Once you've written some awesome contracts and performed a few unit tests the ne ## Start and Stop Sandbox Node > Currently, to start the sandbox node you will need to do so manually. Here are the steps to start and stop a sandbox node: -First clone the nearcore repo: +1. Clone the `nearcore` repo: ```bash git clone https://github.com/near/nearcore From b132d8825206f488a31af85efa0120028d40cd04 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:57:31 -0700 Subject: [PATCH 10/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index ca07099588d..ac775a9b436 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -22,7 +22,7 @@ git clone https://github.com/near/nearcore cd nearcore ``` -Then let's build the sandbox binary, it takes several minutes depend on your CPU. +2. Build the sandbox binary which will take several minutes depending on your CPU: ```bash make sandbox From c90c4e4f4f28cab704418297292e29d3d54130bd Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:57:50 -0700 Subject: [PATCH 11/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index ac775a9b436..604f3b9f3b7 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -28,7 +28,7 @@ cd nearcore make sandbox ``` -Now we can start sandbox node: +3. Start the sandbox node: ```bash target/debug/near-sandbox --home /tmp/near-sandbox init From 4fd6e77dab50b2d43b43661e2fa8c166395b213f Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:58:56 -0700 Subject: [PATCH 12/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 604f3b9f3b7..7e39d048fa9 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -35,7 +35,10 @@ target/debug/near-sandbox --home /tmp/near-sandbox init target/debug/near-sandbox --home /tmp/near-sandbox run ``` -After you're done with sandbox node, you can stop it by `Ctrl-C`. And to clean up the data it generates, just `rm -rf /tmp/near-sandbox`. +Once you're finished using the sandbox node you can stop it by using `Ctrl-C`. To clean up the data it generates, simply run: + +```bash +rm -rf /tmp/near-sandbox ## Run an End-to-end Test in Sandbox From dfaef3cf770edda6c8e6e76f5e29fba859c2e31b Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:59:21 -0700 Subject: [PATCH 13/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 7e39d048fa9..44d3b13dfec 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -42,7 +42,9 @@ rm -rf /tmp/near-sandbox ## Run an End-to-end Test in Sandbox -Let's take status-message contract as an example. You can get this contract from: https://github.com/near/near-sdk-rs/blob/master/examples/status-message/res/status_message.wasm. This is a very straightforward contract. It has two methods: +For this example we'll use a simple smart contract (status-message) with two methods; `set_status` & `get_status`. + +[ [Click here](https://github.com/near/near-sdk-rs/blob/master/examples/status-message/res/status_message.wasm) ] to download the contract (`status_message.wasm`). ```text set_status(message: string) From 41d4f6c4126ff61dd902ab67a779a527793abe9e Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 15:59:40 -0700 Subject: [PATCH 14/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 44d3b13dfec..245ab4032af 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -51,7 +51,8 @@ set_status(message: string) get_status(account_id: string) -> string or null ``` -`set_status` method stores message string under the sender's account name key on chain. And get_status retrieve message of given account name. If it were set by `set_status` before, it returns that string, otherwise it returns null. +- `set_status` stores a message as a string under the sender's account as the key on chain. +- `get_status` retrieves a message of given account name as a string. _(returns `null` if `set_status` was never called)_ 1. Start a near-sandbox node. If you have run a sandbox node with some tests before, delete `/tmp/near-sandbox` before start near-sandbox. 2. Assume you clone the repo with status-message and source code stays in `status-message/`. The compiled contract lives in `status-message/res`. Let's do some preparation for the test: From 32c18d147e80ca7ef214e6120561d57150507117 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:00:08 -0700 Subject: [PATCH 15/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 245ab4032af..1d056131c52 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -54,7 +54,7 @@ get_status(account_id: string) -> string or null - `set_status` stores a message as a string under the sender's account as the key on chain. - `get_status` retrieves a message of given account name as a string. _(returns `null` if `set_status` was never called)_ -1. Start a near-sandbox node. If you have run a sandbox node with some tests before, delete `/tmp/near-sandbox` before start near-sandbox. +1. Start a near-sandbox node. If you have already ran a sandbox node with tests make sure you delete `/tmp/near-sandbox` before restarting the node. 2. Assume you clone the repo with status-message and source code stays in `status-message/`. The compiled contract lives in `status-message/res`. Let's do some preparation for the test: ```bash From d2bed8bf21309446f8cb64ab7652d3b7f84509bf Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:03:58 -0700 Subject: [PATCH 16/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 1d056131c52..1085f6bb48b 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -202,7 +202,7 @@ async function test() { test(); ``` -The test itself is very straightfoward. It does: +The test itself is very straightfoward as it performs the following: 1. Create testing accounts and deploy contract. 2. Alice sign the transaction to set status, and get status to see set status works. From c4463b6e7b695a8c97444d899bfb028a2606fe43 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:04:13 -0700 Subject: [PATCH 17/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 1085f6bb48b..6e7a3f68875 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -333,7 +333,7 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " }}' ``` -7. Now we can back to the test file and rerun the test with new state. +7. Now we can go back to the test file and rerun the test with the new state. Comment these two lines which create accounts and deploy contract, because they're already created in the first test: ```javascript From 5008690ddbd301d21a22606effbb6e58dbfdc3d3 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:04:46 -0700 Subject: [PATCH 18/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 6e7a3f68875..6ce18222bbe 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -204,10 +204,10 @@ test(); The test itself is very straightfoward as it performs the following: -1. Create testing accounts and deploy contract. -2. Alice sign the transaction to set status, and get status to see set status works. -3. Get Bob's status and that should be null because Bob has not set status. -4. Bob set status and get status, should show Bob's status changed and not affect Alice status. +1. Creates testing accounts and deploys a contract. +2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked. +3. Gets Bob's status and which should be `null` as Bob has not yet set status. +4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status. > Most of the code above is boilerplate setup code to setup NEAR API, key pairs, testing accounts and deploy the contract. We're working on a near-cli `near test` command to do these setup code so you can focus on writing only `test()` for this kind of test. From 8229fc0a685020770498d556b5c5713f619a61f3 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:05:35 -0700 Subject: [PATCH 19/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 6ce18222bbe..5910ce9dc0f 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -209,7 +209,7 @@ The test itself is very straightfoward as it performs the following: 3. Gets Bob's status and which should be `null` as Bob has not yet set status. 4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status. -> Most of the code above is boilerplate setup code to setup NEAR API, key pairs, testing accounts and deploy the contract. We're working on a near-cli `near test` command to do these setup code so you can focus on writing only `test()` for this kind of test. +> Most of the code above is boilerplate code to setup NEAR API, key pairs, testing accounts, and deploy the contract. We're working on a near-cli `near test` command to do this setup code so you can focus on writing only `test()` for this. ## Sandbox-only Features for Testing From 480590fa5fe0326b378788ddfd798626e39b1ace Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:06:17 -0700 Subject: [PATCH 20/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 5910ce9dc0f..ce924ddbb21 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -213,7 +213,7 @@ The test itself is very straightfoward as it performs the following: ## Sandbox-only Features for Testing -If you only use above test script that only uses standard NEAR rpcs, your tests can also be executed on a testnet node. Just replace the network ID, node url, key path and account names in above script and rerun. There's also some additional Sandbox-only features that make certain tests easier. We'll review some examples in this section. +If you only use the above test script that just uses standard NEAR RPCs your tests can also be executed on a `testnet` node. Simply replace the network ID, node url, key path, and account names in the above script and rerun the tests. There's also some additional "Sandbox only" features that make certain tests easier. We'll review some examples of those in the following section. ### Patch State on the Fly From 75d602663e0c5ea40a6661e94f0287bf53083ce4 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:06:35 -0700 Subject: [PATCH 21/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index ce924ddbb21..f3b16d8f88f 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -334,7 +334,7 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " ``` 7. Now we can go back to the test file and rerun the test with the new state. - Comment these two lines which create accounts and deploy contract, because they're already created in the first test: + Comment out the following lines of code which create accounts and deploy the contract. We can do this because they're already created in the first test: ```javascript // await masterAccount.createAccount( From 904953aaa12486106bc6efd7f07e50158da852ee Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:06:48 -0700 Subject: [PATCH 22/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index f3b16d8f88f..d2e4ecb899a 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -303,7 +303,7 @@ statusMessage.records.push(new Record({ k: "alice.near", v: "hello world" })); console.log(statusMessage); ``` -5. It looks good now, let's serialize it and base64 encode it so it can be used in patch_state RPC: +5. Looks good! Now, let's serialize it and base64 encode it so it can be used in `patch_state` RPC: ```javascript console.log( From f0b5461547f2766061f1427bc476aa6902fcf5c2 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:06:56 -0700 Subject: [PATCH 23/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index d2e4ecb899a..96900a8542a 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -296,7 +296,7 @@ StatusMessage { So the key of the key-value pair is ASCII string `STATE`. This is because all contracts written with near-sdk-rs store the main contract struct under this key. The value of the key-value pair is borsh serialized account-message items. The exact content is as expected as we inserted these two StatusMessage records in the previous test. -4. Now let's add an message for `alice.near` directly to the state: +4. Now let's add a message for `alice.near` directly to the state: ```javascript statusMessage.records.push(new Record({ k: "alice.near", v: "hello world" })); From 1a88e3fa59a735e3d406404770f67c75af1385c1 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:07:06 -0700 Subject: [PATCH 24/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 96900a8542a..91a768bcf7f 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -234,7 +234,7 @@ $ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": {"jsonrpc":"2.0","result":{"values":[{"key":"U1RBVEU=","value":"AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==","proof":[]}],"proof":[],"block_height":24229,"block_hash":"XeCMK1jLNCu2UbkAKk1LLXEQVqvUASLoxSEz1YVBfGH"},"id":1} ``` -You can see the contract only has one key-value pair in state, looks like base64 encoded. Let's figure out what it is. +You can see the contract only has one key-value pair in state which looks like base64 encoded. Let's figure out what it is. 2. `npm i borsh` and create a JavaScript file with following content: From 451b3a89f8b62bec96cb14c32155529e7566f778 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:07:33 -0700 Subject: [PATCH 25/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 91a768bcf7f..fc0d2a48ea1 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -226,7 +226,9 @@ For patch contract code, account or access key, you can add them with normal dep Let's see an example of how patch_state would help in a test. Assume you want to mock the real state of a mainnet, where `alice.near` has set a status, and you want to retrieve that message. Above script doesn't work out of box, because your master account is `test.near` and you can only create account of `alice.test.near`, not `alice.near`. Patch state can solve this problem. -1. Fetch current state from sandbox node. You can also do this with `sendJsonRpc` of `near-api-js`, or with any http client from command line: +Here is a guide on running `patch_state`: + +1. Fetch the current state from the sandbox node: _(You can also do this with `sendJsonRpc` of `near-api-js` or with any http client from command line)_ ```bash $ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id":1, "method":"query", "params":{"request_type":"view_state","finality":"final", "account_id":"status-message.test.near","prefix_base64":""}}' From d810f911823b8c3344dd2fbeff8d7e8c050abc1d Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:08:24 -0700 Subject: [PATCH 26/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index fc0d2a48ea1..353f088258f 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -219,7 +219,7 @@ If you only use the above test script that just uses standard NEAR RPCs your tes You can add or modify any contract state, contract code, account or access key during the test with `sandbox_patch_state` RPC. -For arbitrary mutation on contract state, you cannot do it via transactions since transaction can only include contract calls that mutate state in contract programmed way. For example, for a NFT contract, you can do some operation with your owned NFTs but you cannot manipulate other people owned NFTs because smart contract has coded with checks to reject that. This is the expected behavior of the NFT contract. However you may want to change other people's NFT for test setup. This is called "arbitary mutation on contract state" and can be done by `sandbox_patch_state` RPC or stop the node, dump state as genesis, edit genesis and restart the node. The later approach is more complicated to do and also cannot be done without restart the node. +For arbitrary mutation on contract state you cannot perform this with transactions as transactions can only include contract calls that mutate state in a contract programmed way. For example with an NFT contract, you can perform some operation with NFTs you have ownership of but you cannot manipulate NFTs that are owned by other accounts as the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done by the `sandbox_patch_state` RPC. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. For patch contract code, account or access key, you can add them with normal deploy contract, create account or add key actions in transaction, but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. From 6217bba3a3dd85bc920791baecfeb47df9e96d57 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:08:56 -0700 Subject: [PATCH 27/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 353f088258f..df62850206c 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -221,7 +221,7 @@ You can add or modify any contract state, contract code, account or access key d For arbitrary mutation on contract state you cannot perform this with transactions as transactions can only include contract calls that mutate state in a contract programmed way. For example with an NFT contract, you can perform some operation with NFTs you have ownership of but you cannot manipulate NFTs that are owned by other accounts as the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done by the `sandbox_patch_state` RPC. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. -For patch contract code, account or access key, you can add them with normal deploy contract, create account or add key actions in transaction, but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. +For patch contract code, account, or access keys you can add them with a normal deploy contract, create account, or add key actions in a transaction but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. Let's see an example of how patch_state would help in a test. Assume you want to mock the real state of a mainnet, where `alice.near` has set a status, and you want to retrieve that message. Above script doesn't work out of box, because your master account is `test.near` and you can only create account of `alice.test.near`, not `alice.near`. Patch state can solve this problem. From 8e25ed20207341f30deef2ae3b19d7e6f13a9875 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Fri, 28 May 2021 16:09:28 -0700 Subject: [PATCH 28/37] Update docs/develop/contracts/sandbox.md Co-authored-by: Josh Ford --- docs/develop/contracts/sandbox.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index df62850206c..d8afd80da94 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -223,8 +223,7 @@ For arbitrary mutation on contract state you cannot perform this with transactio For patch contract code, account, or access keys you can add them with a normal deploy contract, create account, or add key actions in a transaction but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. -Let's see an example of how patch_state would help in a test. Assume you want to mock the real state of a mainnet, where `alice.near` has set a status, and you want to retrieve that message. Above script doesn't work out of box, because your master account is `test.near` and you can only -create account of `alice.test.near`, not `alice.near`. Patch state can solve this problem. +Let's explore an example of how `patch_state` would help in a test. Assume you want to mock the real state of `mainnet` where `alice.near` has set a status and you want to retrieve that status message. The above script doesn't work out of box because your master account is `test.near` and you can only create an account of `alice.test.near` not `alice.near`. Patch state can solve this problem. Here is a guide on running `patch_state`: From 1212e2ed7cba4e92fdea95fa62f9552f546c6c82 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Tue, 1 Jun 2021 10:19:55 -0700 Subject: [PATCH 29/37] clone instead of download contract because need source code --- docs/develop/contracts/sandbox.md | 195 +++++++++++++++--------------- 1 file changed, 96 insertions(+), 99 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index d8afd80da94..e5818f31817 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -6,7 +6,7 @@ sidebar_label: Test in Sandbox Once you've written some awesome contracts and performed a few unit tests the next step is to see how your contracts will behave on a real node. `near-sandbox` is the perfect solution for this as it includes all components of a live `testnet` node but runs locally on your machine. Additionally, it provides features such as patching blockchain state on the fly and fast forwarding in time that makes certain tests easier. -> Coming from Ethereum the typical workflow would be something like: +> Coming from Ethereum the typical workflow would be something like: - Writing e2e test in JavaScript - Start a local `ganache` node @@ -15,6 +15,7 @@ Once you've written some awesome contracts and performed a few unit tests the ne ## Start and Stop Sandbox Node > Currently, to start the sandbox node you will need to do so manually. Here are the steps to start and stop a sandbox node: + 1. Clone the `nearcore` repo: ```bash @@ -35,27 +36,27 @@ target/debug/near-sandbox --home /tmp/near-sandbox init target/debug/near-sandbox --home /tmp/near-sandbox run ``` -Once you're finished using the sandbox node you can stop it by using `Ctrl-C`. To clean up the data it generates, simply run: +Once you're finished using the sandbox node you can stop it by using `Ctrl-C`. To clean up the data it generates, simply run: -```bash +````bash rm -rf /tmp/near-sandbox ## Run an End-to-end Test in Sandbox -For this example we'll use a simple smart contract (status-message) with two methods; `set_status` & `get_status`. +For this example we'll use a simple smart contract (status-message) with two methods; `set_status` & `get_status`. -[ [Click here](https://github.com/near/near-sdk-rs/blob/master/examples/status-message/res/status_message.wasm) ] to download the contract (`status_message.wasm`). +Clone [near-sdk-rs](https://github.com/near/near-sdk-rs) and the contract is in `examples/status-message/res/status_message.wasm`. ```text set_status(message: string) get_status(account_id: string) -> string or null -``` +```` - `set_status` stores a message as a string under the sender's account as the key on chain. - `get_status` retrieves a message of given account name as a string. _(returns `null` if `set_status` was never called)_ 1. Start a near-sandbox node. If you have already ran a sandbox node with tests make sure you delete `/tmp/near-sandbox` before restarting the node. -2. Assume you clone the repo with status-message and source code stays in `status-message/`. The compiled contract lives in `status-message/res`. Let's do some preparation for the test: +2. Go to `near-sdk-rs` directory. The contract source code stays in `examples/status-message/`. The compiled contract lives in `examples/status-message/res`. Let's do some preparation for the test: ```bash cd status-message @@ -74,54 +75,54 @@ And run `npm i` again. 3. Write a test `test.js` that does deploy the contract, test with the contract logic: ```javascript -const nearAPI = require("near-api-js"); -const BN = require("bn.js"); -const fs = require("fs").promises; -const assert = require("assert").strict; +const nearAPI = require('near-api-js') +const BN = require('bn.js') +const fs = require('fs').promises +const assert = require('assert').strict function getConfig(env) { switch (env) { - case "sandbox": - case "local": + case 'sandbox': + case 'local': return { - networkId: "sandbox", - nodeUrl: "http://localhost:3030", - masterAccount: "test.near", - contractAccount: "status-message.test.near", - keyPath: "/tmp/near-sandbox/validator_key.json", - }; + networkId: 'sandbox', + nodeUrl: 'http://localhost:3030', + masterAccount: 'test.near', + contractAccount: 'status-message.test.near', + keyPath: '/tmp/near-sandbox/validator_key.json', + } } } const contractMethods = { - viewMethods: ["get_status"], - changeMethods: ["set_status"], -}; -let config; -let masterAccount; -let masterKey; -let pubKey; -let keyStore; -let near; + viewMethods: ['get_status'], + changeMethods: ['set_status'], +} +let config +let masterAccount +let masterKey +let pubKey +let keyStore +let near async function initNear() { - config = getConfig(process.env.NEAR_ENV || "sandbox"); - const keyFile = require(config.keyPath); + config = getConfig(process.env.NEAR_ENV || 'sandbox') + const keyFile = require(config.keyPath) masterKey = nearAPI.utils.KeyPair.fromString( keyFile.secret_key || keyFile.private_key - ); - pubKey = masterKey.getPublicKey(); - keyStore = new nearAPI.keyStores.InMemoryKeyStore(); - keyStore.setKey(config.networkId, config.masterAccount, masterKey); + ) + pubKey = masterKey.getPublicKey() + keyStore = new nearAPI.keyStores.InMemoryKeyStore() + keyStore.setKey(config.networkId, config.masterAccount, masterKey) near = await nearAPI.connect({ deps: { keyStore, }, networkId: config.networkId, nodeUrl: config.nodeUrl, - }); - masterAccount = new nearAPI.Account(near.connection, config.masterAccount); - console.log("Finish init NEAR"); + }) + masterAccount = new nearAPI.Account(near.connection, config.masterAccount) + console.log('Finish init NEAR') } async function createContractUser( @@ -129,77 +130,77 @@ async function createContractUser( contractAccountId, contractMethods ) { - let accountId = accountPrefix + "." + config.masterAccount; + let accountId = accountPrefix + '.' + config.masterAccount await masterAccount.createAccount( accountId, pubKey, new BN(10).pow(new BN(25)) - ); - keyStore.setKey(config.networkId, accountId, masterKey); - const account = new nearAPI.Account(near.connection, accountId); + ) + keyStore.setKey(config.networkId, accountId, masterKey) + const account = new nearAPI.Account(near.connection, accountId) const accountUseContract = new nearAPI.Contract( account, contractAccountId, contractMethods - ); - return accountUseContract; + ) + return accountUseContract } async function initTest() { - const contract = await fs.readFile("./res/status_message.wasm"); + const contract = await fs.readFile('./res/status_message.wasm') const _contractAccount = await masterAccount.createAndDeployContract( config.contractAccount, pubKey, contract, new BN(10).pow(new BN(25)) - ); + ) const aliceUseContract = await createContractUser( - "alice", + 'alice', config.contractAccount, contractMethods - ); + ) const bobUseContract = await createContractUser( - "bob", + 'bob', config.contractAccount, contractMethods - ); - console.log("Finish deploy contracts and create test accounts"); - return { aliceUseContract, bobUseContract }; + ) + console.log('Finish deploy contracts and create test accounts') + return { aliceUseContract, bobUseContract } } async function test() { // 1 - await initNear(); - const { aliceUseContract, bobUseContract } = await initTest(); + await initNear() + const { aliceUseContract, bobUseContract } = await initTest() // 2 - await aliceUseContract.set_status({ args: { message: "hello" } }); + await aliceUseContract.set_status({ args: { message: 'hello' } }) let alice_message = await aliceUseContract.get_status({ - account_id: "alice.test.near", - }); - assert.equal(alice_message, "hello"); + account_id: 'alice.test.near', + }) + assert.equal(alice_message, 'hello') // 3 let bob_message = await bobUseContract.get_status({ - account_id: "bob.test.near", - }); - assert.equal(bob_message, null); + account_id: 'bob.test.near', + }) + assert.equal(bob_message, null) // 4 - await bobUseContract.set_status({ args: { message: "world" } }); + await bobUseContract.set_status({ args: { message: 'world' } }) bob_message = await bobUseContract.get_status({ - account_id: "bob.test.near", - }); - assert.equal(bob_message, "world"); + account_id: 'bob.test.near', + }) + assert.equal(bob_message, 'world') alice_message = await aliceUseContract.get_status({ - account_id: "alice.test.near", - }); - assert.equal(alice_message, "hello"); + account_id: 'alice.test.near', + }) + assert.equal(alice_message, 'hello') } -test(); +test() ``` The test itself is very straightfoward as it performs the following: @@ -240,13 +241,13 @@ You can see the contract only has one key-value pair in state which looks like b 2. `npm i borsh` and create a JavaScript file with following content: ```javascript -const borsh = require("borsh"); +const borsh = require('borsh') class Assignable { constructor(properties) { Object.keys(properties).map((key) => { - this[key] = properties[key]; - }); + this[key] = properties[key] + }) } } @@ -255,31 +256,27 @@ class StatusMessage extends Assignable {} class Record extends Assignable {} const schema = new Map([ - [StatusMessage, { kind: "struct", fields: [["records", [Record]]] }], + [StatusMessage, { kind: 'struct', fields: [['records', [Record]]] }], [ Record, { - kind: "struct", + kind: 'struct', fields: [ - ["k", "string"], - ["v", "string"], + ['k', 'string'], + ['v', 'string'], ], }, ], -]); +]) -const stateKey = "U1RBVEU="; -console.log(Buffer.from(stateKey, "base64")); -console.log(Buffer.from(stateKey, "base64").toString()); +const stateKey = 'U1RBVEU=' +console.log(Buffer.from(stateKey, 'base64')) +console.log(Buffer.from(stateKey, 'base64').toString()) const stateValue = - "AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA=="; -const stateValueBuffer = Buffer.from(stateValue, "base64"); -const statusMessage = borsh.deserialize( - schema, - StatusMessage, - stateValueBuffer -); -console.log(statusMessage); + 'AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==' +const stateValueBuffer = Buffer.from(stateValue, 'base64') +const statusMessage = borsh.deserialize(schema, StatusMessage, stateValueBuffer) +console.log(statusMessage) ``` 3. Run it with nodejs, we'll get: @@ -300,16 +297,16 @@ So the key of the key-value pair is ASCII string `STATE`. This is because all co 4. Now let's add a message for `alice.near` directly to the state: ```javascript -statusMessage.records.push(new Record({ k: "alice.near", v: "hello world" })); -console.log(statusMessage); +statusMessage.records.push(new Record({ k: 'alice.near', v: 'hello world' })) +console.log(statusMessage) ``` 5. Looks good! Now, let's serialize it and base64 encode it so it can be used in `patch_state` RPC: ```javascript console.log( - Buffer.from(borsh.serialize(schema, statusMessage)).toString("base64") -); + Buffer.from(borsh.serialize(schema, statusMessage)).toString('base64') +) ``` You'll get: @@ -355,27 +352,27 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " 7. comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: ```javascript -let client = new nearAPI.providers.JsonRpcProvider(config.nodeUrl); -let key = Buffer.from("STATE").toString("base64"); +let client = new nearAPI.providers.JsonRpcProvider(config.nodeUrl) +let key = Buffer.from('STATE').toString('base64') // Here is how patch state can be used -await client.sendJsonRpc("sandbox_patch_state", { +await client.sendJsonRpc('sandbox_patch_state', { records: [ { Data: { account_id: config.contractAccount, data_key: key, value: - "AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk", + 'AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk', }, }, ], -}); +}) let alice_mainnet_message = await bobUseContract.get_status({ - account_id: "alice.near", -}); -assert.equal(alice_mainnet_message, "hello world"); + account_id: 'alice.near', +}) +assert.equal(alice_mainnet_message, 'hello world') ``` Rerun the test it should pass. From 6fb1c5cce6f28f8bf222c07198ef4d8d3e59ba74 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Tue, 1 Jun 2021 13:52:37 -0700 Subject: [PATCH 30/37] comments in the code --- docs/develop/contracts/sandbox.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index e5818f31817..c71216b7d6b 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -171,24 +171,24 @@ async function initTest() { } async function test() { - // 1 + // 1. Creates testing accounts and deploys a contract await initNear() const { aliceUseContract, bobUseContract } = await initTest() - // 2 + // 2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked await aliceUseContract.set_status({ args: { message: 'hello' } }) let alice_message = await aliceUseContract.get_status({ account_id: 'alice.test.near', }) assert.equal(alice_message, 'hello') - // 3 + // 3. Gets Bob's status and which should be `null` as Bob has not yet set status let bob_message = await bobUseContract.get_status({ account_id: 'bob.test.near', }) assert.equal(bob_message, null) - // 4 + // 4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status await bobUseContract.set_status({ args: { message: 'world' } }) bob_message = await bobUseContract.get_status({ account_id: 'bob.test.near', @@ -206,7 +206,7 @@ test() The test itself is very straightfoward as it performs the following: 1. Creates testing accounts and deploys a contract. -2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked. +2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked 3. Gets Bob's status and which should be `null` as Bob has not yet set status. 4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status. From 7fe840e68c0911c3fd151793874c08b23c27bd21 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Wed, 16 Jun 2021 15:22:48 +0800 Subject: [PATCH 31/37] remove near-api-js patch --- docs/develop/contracts/sandbox.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index c71216b7d6b..b4feffa60f4 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -64,14 +64,6 @@ npm init npm i near-api-js bn.js ``` -Change `package.json`, specify near-api-js version to be: - -``` - "near-api-js": "near/near-api-js#bace1ee" -``` - -And run `npm i` again. - 3. Write a test `test.js` that does deploy the contract, test with the contract logic: ```javascript From fcd8e3cf0bd066a970be2fb10654503674de4ff6 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Wed, 16 Jun 2021 15:27:55 +0800 Subject: [PATCH 32/37] avoid use my local formatter to sidebar.json --- website/sidebars.json | 72 +++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/website/sidebars.json b/website/sidebars.json index c01f985bb2d..fc71950ce13 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -1,6 +1,8 @@ { "concepts": { - "Overview": ["concepts/new-to-near"], + "Overview": [ + "concepts/new-to-near" + ], "Basics": [ "concepts/account", "concepts/data-storage", @@ -15,7 +17,11 @@ "concepts/architecture/specification", "concepts/architecture/papers" ], - "FAQ": ["faq/general-faq", "faq/technical-faq", "faq/economics-faq"] + "FAQ": [ + "faq/general-faq", + "faq/technical-faq", + "faq/economics-faq" + ] }, "develop": { "Basics": [ @@ -79,7 +85,9 @@ { "type": "subcategory", "label": "Run a RPC Node", - "ids": ["develop/node/rpc/hardware-rpc"] + "ids": [ + "develop/node/rpc/hardware-rpc" + ] }, { "type": "subcategory", @@ -92,15 +100,22 @@ { "type": "subcategory", "label": "Maintenance", - "ids": ["develop/node/maintenance/maintenance"] + "ids": [ + "develop/node/maintenance/maintenance" + ] }, { "type": "subcategory", "label": "Community", - "ids": ["develop/node/community/node-community-updates"] + "ids": [ + "develop/node/community/node-community-updates" + ] } ], - "NEAR <> ETH": ["develop/eth/evm", "develop/eth/rainbow-bridge"] + "NEAR <> ETH": [ + "develop/eth/evm", + "develop/eth/rainbow-bridge" + ] }, "tutorials": { "General": [ @@ -113,6 +128,7 @@ "tutorials/front-end/naj-workshop" ], "Smart Contracts": [ + { "type": "subcategory", "label": "AssemblyScript", @@ -125,14 +141,19 @@ { "type": "subcategory", "label": "Rust", - "ids": ["tutorials/contracts/intro-to-rust"] + "ids": [ + "tutorials/contracts/intro-to-rust" + ] } ], "Rainbow Bridge": [ "tutorials/rainbow-bridge-frontend-mainnet", "tutorials/rainbow-bridge-frontend-testnet" + + ], + "Indexer": [ + "tutorials/near-indexer" ], - "Indexer": ["tutorials/near-indexer"], "Videos": [ "videos/accounts-keys", "videos/contract-reviews", @@ -141,13 +162,18 @@ ] }, "API": { - "API": ["api/rpc", "api/near-api-rest-server"], + "API": [ + "api/rpc", + "api/near-api-rest-server" + ], "JavaScript Library": [ "api/naj-overview", "api/naj-quick-reference", "api/naj-cookbook" ], - "FAQ": ["faq/naj-faq"] + "FAQ": [ + "faq/naj-faq" + ] }, "integrate": { "Integration": [ @@ -159,25 +185,39 @@ "roles/integrator/errors/error-implementation", "roles/integrator/token-loss" ], - "FAQ": ["roles/integrator/faq"] + "FAQ": [ + "roles/integrator/faq" + ] }, "tokens/staking": { - "Overview": ["validator/staking-overview"], + "Overview": [ + "validator/staking-overview" + ], "Token Basics": [ "tokens/token-custody", "tokens/lockup", "validator/delegation" ], - "Validator": ["validator/staking", "validator/economics"], - "FAQ": ["validator/staking-faq"] + "Validator": [ + "validator/staking", + "validator/economics" + ], + "FAQ": [ + "validator/staking-faq" + ] }, "community": { - "Overview": ["community/community-channels"], + "Overview": [ + "community/community-channels" + ], "Contribute": [ "community/contribute/contribute-overview", "community/contribute/how-to-contribute", "community/contribute/contribute-nearcore" ], - "FAQ": ["community/community-faq", "community/contribute/contribute-faq"] + "FAQ": [ + "community/community-faq", + "community/contribute/contribute-faq" + ] } } From f0fedbfaf1c21a543046085d2dea5d2f0c48072e Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Sat, 3 Jul 2021 20:46:40 -0700 Subject: [PATCH 33/37] fix broken link to old NFT repo to use a commit/tag --- docs/concepts/storage-staking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/storage-staking.md b/docs/concepts/storage-staking.md index f5c0aba5a85..c405f79de44 100644 --- a/docs/concepts/storage-staking.md +++ b/docs/concepts/storage-staking.md @@ -55,7 +55,7 @@ A [non-fungible token](https://github.com/nearprotocol/NEPs/pull/4) is unique, w If such an NFT is used to track **1 million** tokens, how much storage will be required for the token-ID-to-owner mapping? And how many tokens will need to be staked for that storage? -Using [this basic AssemblyScript implementation](https://github.com/near-examples/NFT/tree/master/contracts/assemblyscript/nep4-basic) as inspiration, let's calculate the storage needs when using a [`PersistentMap`](https://near.github.io/near-sdk-as/classes/_sdk_core_assembly_collections_persistentmap_.persistentmap.html) from `near-sdk-as`. While its specific implementation may change in the future, at the time of writing `near-sdk-as` stored data as UTF-8 strings. We'll assume this below. +Using [this basic AssemblyScript implementation](https://github.com/near-examples/NFT/tree/ca92b2fdde0b1f969e5d5ffa50e64a8bab783565/contracts/assemblyscript/nep4-basic) as inspiration, let's calculate the storage needs when using a [`PersistentMap`](https://near.github.io/near-sdk-as/classes/_sdk_core_assembly_collections_persistentmap_.persistentmap.html) from `near-sdk-as`. While its specific implementation may change in the future, at the time of writing `near-sdk-as` stored data as UTF-8 strings. We'll assume this below. Here's our `PersistentMap`: From a188be17985613adcb588ea6a05070b98c0730b4 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 12 Jul 2021 14:47:25 -0700 Subject: [PATCH 34/37] minor fixes with formatting --- docs/develop/contracts/sandbox.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index b4feffa60f4..105538b476a 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -38,8 +38,9 @@ target/debug/near-sandbox --home /tmp/near-sandbox run Once you're finished using the sandbox node you can stop it by using `Ctrl-C`. To clean up the data it generates, simply run: -````bash +```bash rm -rf /tmp/near-sandbox +``` ## Run an End-to-end Test in Sandbox @@ -50,7 +51,7 @@ Clone [near-sdk-rs](https://github.com/near/near-sdk-rs) and the contract is in ```text set_status(message: string) get_status(account_id: string) -> string or null -```` +``` - `set_status` stores a message as a string under the sender's account as the key on chain. - `get_status` retrieves a message of given account name as a string. _(returns `null` if `set_status` was never called)_ @@ -195,14 +196,14 @@ async function test() { test() ``` -The test itself is very straightfoward as it performs the following: +The test itself is very straightforward as it performs the following: 1. Creates testing accounts and deploys a contract. 2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked 3. Gets Bob's status and which should be `null` as Bob has not yet set status. 4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status. -> Most of the code above is boilerplate code to setup NEAR API, key pairs, testing accounts, and deploy the contract. We're working on a near-cli `near test` command to do this setup code so you can focus on writing only `test()` for this. +> Most of the code above is boilerplate code to set up NEAR API, key pairs, testing accounts, and deploy the contract. We're working on a NEAR CLI `near test` command to do this setup code, so you can focus on writing only `test()` for this. ## Sandbox-only Features for Testing @@ -212,7 +213,7 @@ If you only use the above test script that just uses standard NEAR RPCs your tes You can add or modify any contract state, contract code, account or access key during the test with `sandbox_patch_state` RPC. -For arbitrary mutation on contract state you cannot perform this with transactions as transactions can only include contract calls that mutate state in a contract programmed way. For example with an NFT contract, you can perform some operation with NFTs you have ownership of but you cannot manipulate NFTs that are owned by other accounts as the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done by the `sandbox_patch_state` RPC. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. +For arbitrary mutation on contract state you cannot perform this with transactions as transactions can only include contract calls that mutate state in a contract programmed way. For example with an NFT contract, you can perform some operation with NFTs you have ownership of but you cannot manipulate NFTs that are owned by other accounts as the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done by the `sandbox_patch_state` RPC. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. For patch contract code, account, or access keys you can add them with a normal deploy contract, create account, or add key actions in a transaction but that's also limited to your account or sub-account. `sandbox_patch_state` RPC does not have this restriction. From 261ce33f9d64079294720a07bcf8aeaf4c9ac466 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 12 Jul 2021 15:15:35 -0700 Subject: [PATCH 35/37] update instructions to use rust-status-message-example, update from test.js file --- docs/develop/contracts/sandbox.md | 134 +++++++++++++++--------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index 105538b476a..f7d007d6016 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -4,7 +4,7 @@ title: End-to-end Test in Sandbox sidebar_label: Test in Sandbox --- -Once you've written some awesome contracts and performed a few unit tests the next step is to see how your contracts will behave on a real node. `near-sandbox` is the perfect solution for this as it includes all components of a live `testnet` node but runs locally on your machine. Additionally, it provides features such as patching blockchain state on the fly and fast forwarding in time that makes certain tests easier. +Once you've written some awesome contracts and performed a few unit tests the next step is to see how your contracts will behave on a real node. NEAR Sandbox is the perfect solution for this as it includes all components of a live `testnet` node but runs locally on your machine. Additionally, it provides features such as patching blockchain state on the fly and fast forwarding in time that makes certain tests easier. > Coming from Ethereum the typical workflow would be something like: @@ -46,7 +46,9 @@ rm -rf /tmp/near-sandbox For this example we'll use a simple smart contract (status-message) with two methods; `set_status` & `get_status`. -Clone [near-sdk-rs](https://github.com/near/near-sdk-rs) and the contract is in `examples/status-message/res/status_message.wasm`. +Clone the [status example example](https://github.com/near-examples/rust-status-message) where the contract is in `res/status_message.wasm`. + +Here are the two functions we'll be using: ```text set_status(message: string) @@ -56,8 +58,8 @@ get_status(account_id: string) -> string or null - `set_status` stores a message as a string under the sender's account as the key on chain. - `get_status` retrieves a message of given account name as a string. _(returns `null` if `set_status` was never called)_ -1. Start a near-sandbox node. If you have already ran a sandbox node with tests make sure you delete `/tmp/near-sandbox` before restarting the node. -2. Go to `near-sdk-rs` directory. The contract source code stays in `examples/status-message/`. The compiled contract lives in `examples/status-message/res`. Let's do some preparation for the test: +1. Start a NEAR Sandbox node. If you've already run a sandbox node with tests make sure you delete `/tmp/near-sandbox` before restarting the node. +2. Go to the contract source code in `src/lib.rs`. The compiled contract lives in the `res` directory. Let's do some preparation for the test: ```bash cd status-message @@ -67,55 +69,55 @@ npm i near-api-js bn.js 3. Write a test `test.js` that does deploy the contract, test with the contract logic: -```javascript -const nearAPI = require('near-api-js') -const BN = require('bn.js') -const fs = require('fs').promises -const assert = require('assert').strict +```js +const nearAPI = require("near-api-js"); +const BN = require("bn.js"); +const fs = require("fs").promises; +const assert = require("assert").strict; function getConfig(env) { switch (env) { - case 'sandbox': - case 'local': + case "sandbox": + case "local": return { - networkId: 'sandbox', - nodeUrl: 'http://localhost:3030', - masterAccount: 'test.near', - contractAccount: 'status-message.test.near', - keyPath: '/tmp/near-sandbox/validator_key.json', - } + networkId: "sandbox", + nodeUrl: "http://localhost:3030", + masterAccount: "test.near", + contractAccount: "status-message.test.near", + keyPath: "/tmp/near-sandbox/validator_key.json", + }; } } const contractMethods = { - viewMethods: ['get_status'], - changeMethods: ['set_status'], -} -let config -let masterAccount -let masterKey -let pubKey -let keyStore -let near + viewMethods: ["get_status"], + changeMethods: ["set_status"], +}; +let config; +let masterAccount; +let masterKey; +let pubKey; +let keyStore; +let near; async function initNear() { - config = getConfig(process.env.NEAR_ENV || 'sandbox') - const keyFile = require(config.keyPath) + config = getConfig(process.env.NEAR_ENV || "sandbox"); + const keyFile = require(config.keyPath); masterKey = nearAPI.utils.KeyPair.fromString( keyFile.secret_key || keyFile.private_key - ) - pubKey = masterKey.getPublicKey() - keyStore = new nearAPI.keyStores.InMemoryKeyStore() - keyStore.setKey(config.networkId, config.masterAccount, masterKey) + ); + pubKey = masterKey.getPublicKey(); + keyStore = new nearAPI.keyStores.InMemoryKeyStore(); + keyStore.setKey(config.networkId, config.masterAccount, masterKey); near = await nearAPI.connect({ deps: { keyStore, }, networkId: config.networkId, nodeUrl: config.nodeUrl, - }) - masterAccount = new nearAPI.Account(near.connection, config.masterAccount) - console.log('Finish init NEAR') + }); + masterAccount = new nearAPI.Account(near.connection, config.masterAccount); + console.log("Finish init NEAR"); } async function createContractUser( @@ -123,77 +125,77 @@ async function createContractUser( contractAccountId, contractMethods ) { - let accountId = accountPrefix + '.' + config.masterAccount + let accountId = accountPrefix + "." + config.masterAccount; await masterAccount.createAccount( accountId, pubKey, new BN(10).pow(new BN(25)) - ) - keyStore.setKey(config.networkId, accountId, masterKey) - const account = new nearAPI.Account(near.connection, accountId) + ); + keyStore.setKey(config.networkId, accountId, masterKey); + const account = new nearAPI.Account(near.connection, accountId); const accountUseContract = new nearAPI.Contract( account, contractAccountId, contractMethods - ) - return accountUseContract + ); + return accountUseContract; } async function initTest() { - const contract = await fs.readFile('./res/status_message.wasm') + const contract = await fs.readFile("./res/status_message.wasm"); const _contractAccount = await masterAccount.createAndDeployContract( config.contractAccount, pubKey, contract, new BN(10).pow(new BN(25)) - ) + ); const aliceUseContract = await createContractUser( - 'alice', + "alice", config.contractAccount, contractMethods - ) + ); const bobUseContract = await createContractUser( - 'bob', + "bob", config.contractAccount, contractMethods - ) - console.log('Finish deploy contracts and create test accounts') - return { aliceUseContract, bobUseContract } + ); + console.log("Finish deploy contracts and create test accounts"); + return { aliceUseContract, bobUseContract }; } async function test() { // 1. Creates testing accounts and deploys a contract - await initNear() - const { aliceUseContract, bobUseContract } = await initTest() + await initNear(); + const { aliceUseContract, bobUseContract } = await initTest(); // 2. Performs a `set_status` transaction signed by Alice and then calls `get_status` to confirm `set_status` worked - await aliceUseContract.set_status({ args: { message: 'hello' } }) + await aliceUseContract.set_status({ args: { message: "hello" } }); let alice_message = await aliceUseContract.get_status({ - account_id: 'alice.test.near', - }) - assert.equal(alice_message, 'hello') + account_id: "alice.test.near", + }); + assert.equal(alice_message, "hello"); // 3. Gets Bob's status and which should be `null` as Bob has not yet set status let bob_message = await bobUseContract.get_status({ - account_id: 'bob.test.near', - }) - assert.equal(bob_message, null) + account_id: "bob.test.near", + }); + assert.equal(bob_message, null); // 4. Performs a `set_status` transaction signed by Bob and then calls `get_status` to show Bob's changed status and should not affect Alice's status - await bobUseContract.set_status({ args: { message: 'world' } }) + await bobUseContract.set_status({ args: { message: "world" } }); bob_message = await bobUseContract.get_status({ - account_id: 'bob.test.near', - }) - assert.equal(bob_message, 'world') + account_id: "bob.test.near", + }); + assert.equal(bob_message, "world"); alice_message = await aliceUseContract.get_status({ - account_id: 'alice.test.near', - }) - assert.equal(alice_message, 'hello') + account_id: "alice.test.near", + }); + assert.equal(alice_message, "hello"); } -test() +test(); ``` The test itself is very straightforward as it performs the following: From 31692330c431817413cf4f94d5eaefff73ce0a4a Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 12 Jul 2021 15:19:31 -0700 Subject: [PATCH 36/37] make first curl command copyable, separate result into formatted JSON --- docs/develop/contracts/sandbox.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index f7d007d6016..f5cf7491da5 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -226,9 +226,27 @@ Here is a guide on running `patch_state`: 1. Fetch the current state from the sandbox node: _(You can also do this with `sendJsonRpc` of `near-api-js` or with any http client from command line)_ ```bash -$ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id":1, "method":"query", "params":{"request_type":"view_state","finality":"final", "account_id":"status-message.test.near","prefix_base64":""}}' +curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id":1, "method":"query", "params":{"request_type":"view_state","finality":"final", "account_id":"status-message.test.near","prefix_base64":""}}' +``` -{"jsonrpc":"2.0","result":{"values":[{"key":"U1RBVEU=","value":"AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==","proof":[]}],"proof":[],"block_height":24229,"block_hash":"XeCMK1jLNCu2UbkAKk1LLXEQVqvUASLoxSEz1YVBfGH"},"id":1} +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "values": [ + { + "key": "U1RBVEU=", + "value": "AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==", + "proof": [] + } + ], + "proof": [], + "block_height": 24229, + "block_hash": "XeCMK1jLNCu2UbkAKk1LLXEQVqvUASLoxSEz1YVBfGH" + }, + "id": 1 +} ``` You can see the contract only has one key-value pair in state which looks like base64 encoded. Let's figure out what it is. From 85864b91342020384e06e4d8c1978381fb8898ac Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 12 Jul 2021 15:32:37 -0700 Subject: [PATCH 37/37] linking to Borsh.io, updating JS from status-message example, minor updates --- docs/develop/contracts/sandbox.md | 63 ++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/docs/develop/contracts/sandbox.md b/docs/develop/contracts/sandbox.md index f5cf7491da5..b6ab6ae71e5 100644 --- a/docs/develop/contracts/sandbox.md +++ b/docs/develop/contracts/sandbox.md @@ -254,13 +254,13 @@ You can see the contract only has one key-value pair in state which looks like b 2. `npm i borsh` and create a JavaScript file with following content: ```javascript -const borsh = require('borsh') +const borsh = require("borsh"); class Assignable { constructor(properties) { Object.keys(properties).map((key) => { - this[key] = properties[key] - }) + this[key] = properties[key]; + }); } } @@ -269,30 +269,40 @@ class StatusMessage extends Assignable {} class Record extends Assignable {} const schema = new Map([ - [StatusMessage, { kind: 'struct', fields: [['records', [Record]]] }], + [StatusMessage, { kind: "struct", fields: [["records", [Record]]] }], [ Record, { - kind: 'struct', + kind: "struct", fields: [ - ['k', 'string'], - ['v', 'string'], + ["k", "string"], + ["v", "string"], ], }, ], -]) +]); -const stateKey = 'U1RBVEU=' -console.log(Buffer.from(stateKey, 'base64')) -console.log(Buffer.from(stateKey, 'base64').toString()) +const stateKey = "U1RBVEU="; +console.log(Buffer.from(stateKey, "base64")); +console.log(Buffer.from(stateKey, "base64").toString()); const stateValue = - 'AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA==' -const stateValueBuffer = Buffer.from(stateValue, 'base64') -const statusMessage = borsh.deserialize(schema, StatusMessage, stateValueBuffer) -console.log(statusMessage) + "AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA=="; +const stateValueBuffer = Buffer.from(stateValue, "base64"); +let statusMessage = borsh.deserialize(schema, StatusMessage, stateValueBuffer); +console.log(statusMessage); + +console.log( + Buffer.from(borsh.serialize(schema, statusMessage)).toString("base64") +); +statusMessage.records.push(new Record({ k: "alice.near", v: "hello world" })); +console.log(statusMessage); + +console.log( + Buffer.from(borsh.serialize(schema, statusMessage)).toString("base64") +); ``` -3. Run it with nodejs, we'll get: +3. `node borsh.js` to run it with NodeJS, and we'll get: ```text @@ -303,18 +313,27 @@ StatusMessage { Record { k: 'bob.test.near', v: 'world' } ] } +AgAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZA== +StatusMessage { + records: [ + Record { k: 'alice.test.near', v: 'hello' }, + Record { k: 'bob.test.near', v: 'world' }, + Record { k: 'alice.near', v: 'hello world' } + ] +} +AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk ``` -So the key of the key-value pair is ASCII string `STATE`. This is because all contracts written with near-sdk-rs store the main contract struct under this key. The value of the key-value pair is borsh serialized account-message items. The exact content is as expected as we inserted these two StatusMessage records in the previous test. +So the key of the key-value pair is the ASCII string `STATE`. This is because all contracts written with [`near-sdk-rs`](https://github.com/near/near-sdk-rs) store the main contract struct under this key. The value of the key-value pair are [borsh](https://borsh.io) serialized account ยป message items. The exact content is as expected as we inserted these two `StatusMessage` records in the previous test. -4. Now let's add a message for `alice.near` directly to the state: +4. Note at the bottom of the `borsh.js` file that we've added a message for `alice.near` directly to the state: ```javascript statusMessage.records.push(new Record({ k: 'alice.near', v: 'hello world' })) console.log(statusMessage) ``` -5. Looks good! Now, let's serialize it and base64 encode it so it can be used in `patch_state` RPC: +5. After that snippet, notice how it's serialize and base64 encoded, so it can be used in a `patch_state` remote procedure call: ```javascript console.log( @@ -322,7 +341,7 @@ console.log( ) ``` -You'll get: +The final output is: ```text AwAAAA8AAABhbGljZS50ZXN0Lm5lYXIFAAAAaGVsbG8NAAAAYm9iLnRlc3QubmVhcgUAAAB3b3JsZAoAAABhbGljZS5uZWFyCwAAAGhlbGxvIHdvcmxk @@ -362,7 +381,7 @@ curl http://localhost:3030 -H 'content-type: application/json' -d '{"jsonrpc": " // ); ``` -7. comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: +7. Comment everything after `const { aliceUseContract, bobUseContract } = await initTest();` and add: ```javascript let client = new nearAPI.providers.JsonRpcProvider(config.nodeUrl) @@ -388,4 +407,4 @@ let alice_mainnet_message = await bobUseContract.get_status({ assert.equal(alice_mainnet_message, 'hello world') ``` -Rerun the test it should pass. +Rerun the test (`node test.js`) and it should pass.