diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5a54d46..ba0c871 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -22,7 +22,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
- toolchain: stable
+ toolchain: 1.81.0
default: true
target: wasm32-unknown-unknown
diff --git a/Cargo.lock b/Cargo.lock
index 0073d5c..a541c31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -79,6 +79,27 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "advanced-v1"
+version = "0.1.0"
+dependencies = [
+ "near-sdk",
+]
+
+[[package]]
+name = "advanced-v2"
+version = "0.1.0"
+dependencies = [
+ "near-sdk",
+]
+
+[[package]]
+name = "advanced-v3"
+version = "0.1.0"
+dependencies = [
+ "near-sdk",
+]
+
[[package]]
name = "aes"
version = "0.8.4"
@@ -284,6 +305,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
[[package]]
name = "base64ct"
version = "1.6.0"
@@ -1969,6 +1996,16 @@ dependencies = [
"serde",
]
+[[package]]
+name = "near-gas"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180edcc7dc2fac41f93570d0c7b759c1b6d492f6ad093d749d644a40b4310a97"
+dependencies = [
+ "borsh 1.3.1",
+ "serde",
+]
+
[[package]]
name = "near-jsonrpc-client"
version = "0.8.0"
@@ -2154,18 +2191,18 @@ dependencies = [
[[package]]
name = "near-sdk"
-version = "5.1.0"
+version = "5.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8"
+checksum = "befb9df6da1a6a0b6656388c0db76084867062a87f1cbc066c188a8e360b6463"
dependencies = [
- "base64 0.21.7",
+ "base64 0.22.1",
"borsh 1.3.1",
"bs58 0.5.0",
"near-account-id",
- "near-gas",
+ "near-gas 0.3.0",
"near-sdk-macros",
"near-sys",
- "near-token",
+ "near-token 0.3.0",
"once_cell",
"serde",
"serde_json",
@@ -2174,9 +2211,9 @@ dependencies = [
[[package]]
name = "near-sdk-macros"
-version = "5.1.0"
+version = "5.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7"
+checksum = "1268c4fc56bf53d70c200261fb8d57c6c1c6692243660f5f889c7fa4cf5771d2"
dependencies = [
"Inflector",
"darling",
@@ -2197,15 +2234,24 @@ checksum = "855fd5540e3b4ff6fedf12aba2db1ee4b371b36f465da1363a6d022b27cb43b8"
[[package]]
name = "near-sys"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b"
+checksum = "dbf4ca5c805cb78700e10e43484902d8da05f25788db277999d209568aaf4c8e"
[[package]]
name = "near-token"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "near-token"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3e60aa26a74dc514b1b6408fdd06cefe2eb0ff029020956c1c6517594048fd"
dependencies = [
"borsh 1.3.1",
"serde",
@@ -2258,12 +2304,12 @@ dependencies = [
"libc",
"near-account-id",
"near-crypto",
- "near-gas",
+ "near-gas 0.2.5",
"near-jsonrpc-client",
"near-jsonrpc-primitives",
"near-primitives",
"near-sandbox-utils",
- "near-token",
+ "near-token 0.2.0",
"rand 0.8.5",
"reqwest",
"serde",
@@ -3750,9 +3796,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.34"
+version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@@ -3771,9 +3817,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.17"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
diff --git a/Cargo.toml b/Cargo.toml
index cabca7d..e979a55 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,4 +14,7 @@ members = [
"enum-updates/update",
"self-updates/base",
"self-updates/update",
-]
\ No newline at end of file
+ "advanced-multi-version-updates/v1",
+ "advanced-multi-version-updates/v2",
+ "advanced-multi-version-updates/v3",
+]
diff --git a/advanced-multi-version-updates/v1/Cargo.toml b/advanced-multi-version-updates/v1/Cargo.toml
new file mode 100644
index 0000000..80c49ef
--- /dev/null
+++ b/advanced-multi-version-updates/v1/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "advanced-v1"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+near-sdk = "5.6"
diff --git a/advanced-multi-version-updates/v1/README.md b/advanced-multi-version-updates/v1/README.md
new file mode 100644
index 0000000..d283665
--- /dev/null
+++ b/advanced-multi-version-updates/v1/README.md
@@ -0,0 +1,79 @@
+# Guest Book Contract
+
+The smart contract stores messages, keeping track of how much money was deposited when adding the message.
+
+```rust
+#[payable]
+pub fn add_message(&mut self, text: String) {
+ let payment = env::attached_deposit();
+ let premium = payment >= POINT_ONE;
+ let sender = env::predecessor_account_id();
+
+ let message = PostedMessage {
+ premium,
+ sender,
+ text,
+ };
+ self.messages.push(message);
+ self.payments.push(payment);
+}
+```
+
+
+
+# Quickstart
+
+## 1. Build and Deploy the Contract
+
+Install [`cargo-near`](https://github.com/near/cargo-near) and run:
+
+```bash
+# from repo root
+cd advanced-multi-version-updates/v1
+cargo near build --no-docker
+```
+
+Build and deploy:
+
+```bash
+# `update-migrate-rust-advanced-multiversion-updates.testnet` was used as example of
+cargo near deploy --no-docker without-init-call network-config testnet sign-with-keychain send
+```
+
+## 2. How to interact?
+
+_In this example we will be using [NEAR CLI](https://github.com/near/near-cli)
+to intract with the NEAR blockchain and the smart contract and [near-cli-rs](https://near.cli.rs)
+which provides more control over interactions and has interactive menus for subcommands selection_
+
+### 1. Add a Message
+
+```bash
+# NEAR CLI
+near call add_message '{"text": "a message"}' --amount 0.1 --accountId
+# near-cli-rs
+near contract call-function as-transaction add_message json-args '{"text": "a message"}' prepaid-gas '100.0 Tgas' attached-deposit '0.1 NEAR' sign-as network-config testnet sign-with-keychain send
+```
+
+
+
+### 2. Retrieve the Stored Messages & Payments
+
+`get_messages` and `get_payments` are read-only method (`view` method)
+
+```bash
+# NEAR CLI
+near view get_messages
+# near-cli-rs
+near contract call-function as-read-only get_messages json-args {} network-config testnet now
+# NEAR CLI
+near view get_payments
+# near-cli-rs
+near contract call-function as-read-only get_payments json-args {} network-config testnet now
+```
+
+
+
+### 3. Continue in the V2 Folder
+
+Navigate to the [v2](../v2/) folder to continue
diff --git a/advanced-multi-version-updates/v1/rust-toolchain.toml b/advanced-multi-version-updates/v1/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/advanced-multi-version-updates/v1/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/advanced-multi-version-updates/v1/src/lib.rs b/advanced-multi-version-updates/v1/src/lib.rs
new file mode 100644
index 0000000..5c9f408
--- /dev/null
+++ b/advanced-multi-version-updates/v1/src/lib.rs
@@ -0,0 +1,81 @@
+use near_sdk::{near, BorshStorageKey};
+
+use near_sdk::borsh::BorshSerialize;
+use near_sdk::json_types::{U128, U64};
+use near_sdk::store::Vector;
+
+use near_sdk::{env, AccountId, NearToken};
+
+const POINT_ONE: NearToken = NearToken::from_millinear(100);
+
+#[derive(BorshSerialize, BorshStorageKey)]
+#[borsh(crate = "near_sdk::borsh")]
+pub enum StorageKey {
+ Messages,
+ Payments,
+}
+
+#[near(serializers=[json, borsh])]
+pub struct PostedMessage {
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+#[near(contract_state)]
+pub struct GuestBook {
+ messages: Vector,
+ payments: Vector,
+}
+
+impl Default for GuestBook {
+ fn default() -> Self {
+ Self {
+ messages: Vector::new(StorageKey::Messages),
+ payments: Vector::new(StorageKey::Payments),
+ }
+ }
+}
+
+#[near]
+impl GuestBook {
+ #[payable]
+ pub fn add_message(&mut self, text: String) {
+ let payment = env::attached_deposit();
+ let premium = payment >= POINT_ONE;
+ let sender = env::predecessor_account_id();
+
+ let message = PostedMessage {
+ premium,
+ sender,
+ text,
+ };
+ self.messages.push(message);
+ self.payments.push(payment);
+ }
+
+ pub fn get_messages(
+ &self,
+ from_index: Option,
+ limit: Option,
+ ) -> Vec<&PostedMessage> {
+ let from = u128::from(from_index.unwrap_or(U128(0)));
+
+ self.messages
+ .iter()
+ .skip(from as usize)
+ .take(u64::from(limit.unwrap_or(U64::from(10))) as usize)
+ .collect()
+ }
+
+ pub fn get_payments(&self, from_index: Option, limit: Option) -> Vec {
+ let from = u128::from(from_index.unwrap_or(U128(0)));
+
+ self.payments
+ .iter()
+ .skip(from as usize)
+ .take(u64::from(limit.unwrap_or(U64::from(10))) as usize)
+ .map(|x| U128(x.as_yoctonear()))
+ .collect()
+ }
+}
diff --git a/advanced-multi-version-updates/v2/Cargo.toml b/advanced-multi-version-updates/v2/Cargo.toml
new file mode 100644
index 0000000..4be3831
--- /dev/null
+++ b/advanced-multi-version-updates/v2/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "advanced-v2"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+near-sdk = "5.6"
diff --git a/advanced-multi-version-updates/v2/README.md b/advanced-multi-version-updates/v2/README.md
new file mode 100644
index 0000000..264d9c3
--- /dev/null
+++ b/advanced-multi-version-updates/v2/README.md
@@ -0,0 +1,140 @@
+# Guest Book Contract
+
+The [v1](../v1) contract was modified, adding `owner` field and view function `get_owner` to retrieve the value.
+
+```rust
+pub struct GuestBook {
+ messages: Vector,
+ payments: Vector,
+ owner: AccountId
+}
+```
+
+If we deploy this contract on top of the [v1](../v1/) one and call any method we will get the error:
+
+```
+panicked at 'Cannot deserialize the contract state.: ... }',
+```
+
+This is because the new contract expects to find metadata about 3 fields (`messages`, `payments`, `owner`) in the contract state, but the current contract only has 2 fields (it lacks the `owner` field).
+
+In order to fix this problem we need to `migrate` the state, i.e. place some account address under `owner` field.
+
+```rust
+impl GuestBook {
+ // Upgrades from V1 to V2
+ fn unsafe_add_owner() {
+ let GuestBookV1 { messages, payments } = env::state_read().unwrap();
+ let owner = AccountId::from_str("bob.near").unwrap();
+
+ env::state_write(&GuestBookV2 {
+ messages,
+ payments,
+ owner,
+ });
+ }
+
+ fn migration_done() {
+ near_sdk::log!("Migration done.");
+ env::value_return(b"\"done\"");
+ }
+
+ fn needs_migration() {
+ env::value_return(b"\"needs-migration\"");
+ }
+
+ pub fn unsafe_migrate() {
+ near_sdk::assert_self();
+ let current_version = state_version_read();
+ near_sdk::log!("Migrating from version: {:?}", current_version);
+ match current_version {
+ StateVersion::V1 => {
+ GuestBook::unsafe_add_owner();
+ state_version_write(&StateVersion::V2);
+ }
+ _ => {
+ return GuestBook::migration_done();
+ }
+ }
+ GuestBook::needs_migration();
+ }
+}
+```
+
+
+
+# Upgrading Base Contract
+
+## 1. Build & Deploy & Migrate State
+
+_In this example we will be using [NEAR CLI](https://github.com/near/near-cli)
+to intract with the NEAR blockchain and the smart contract and [near-cli-rs](https://near.cli.rs)
+which provides more control over interactions and has interactive menus for subcommands selection_
+
+To build contract install [`cargo-near`](https://github.com/near/cargo-near) and run:
+
+```bash
+# from repo root
+cd advanced-multi-version-updates/v2
+cargo near build --no-docker
+```
+
+You can deploy the updated contract by running:
+
+```bash
+# `update-migrate-rust-advanced-multiversion-updates.testnet` was used as example of
+cargo near deploy --no-docker without-init-call network-config testnet sign-with-keychain send
+```
+
+Run this command to see the "Cannot deserialize..." error
+
+```bash
+# NEAR CLI
+near view get_messages
+# near-cli-rs
+near contract call-function as-read-only get_messages json-args {} network-config testnet now
+```
+
+Ask the contract to migrate the state
+
+```bash
+# near-cli-rs (may be useful to specify more gas for large state migrations)
+near contract call-function as-transaction unsafe_migrate json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send
+```
+
+#### Deploying and Migrating
+
+You can actually deploy the contract and migrate the state in one line:
+
+```bash
+# near-cli-rs (may be useful to specify more gas for large state migrations)
+cargo near deploy --no-docker with-init-call unsafe_migrate json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send
+```
+
+
+
+## 2. Retrieve the Stored Messages
+
+`get_messages` will now return messages.
+
+```bash
+# NEAR CLI
+near view get_messages
+# near-cli-rs
+near contract call-function as-read-only get_messages json-args {} network-config testnet now
+```
+
+And `get_owner` will return the owner we've set, in our case it's `bob.near`.
+
+```bash
+# NEAR CLI
+near view get_owner
+# near-cli-rs
+near contract call-function as-read-only get_owner json-args {} network-config testnet now
+```
+
+
+
+### 3. Continue in the V3 Folder
+
+Navigate to the [v3](../v3/) folder to continue
diff --git a/advanced-multi-version-updates/v2/rust-toolchain.toml b/advanced-multi-version-updates/v2/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/advanced-multi-version-updates/v2/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/advanced-multi-version-updates/v2/src/lib.rs b/advanced-multi-version-updates/v2/src/lib.rs
new file mode 100644
index 0000000..7a000cf
--- /dev/null
+++ b/advanced-multi-version-updates/v2/src/lib.rs
@@ -0,0 +1,92 @@
+pub mod migrations;
+
+use near_sdk::{near, BorshStorageKey, PanicOnDefault};
+
+use near_sdk::borsh::BorshSerialize;
+use near_sdk::json_types::{U128, U64};
+use near_sdk::store::Vector;
+
+use near_sdk::{env, AccountId, NearToken};
+
+const POINT_ONE: NearToken = NearToken::from_millinear(100);
+
+#[derive(BorshSerialize, BorshStorageKey)]
+#[borsh(crate = "near_sdk::borsh")]
+pub enum StorageKey {
+ Messages,
+ Payments,
+}
+
+#[near(serializers=[json, borsh])]
+pub struct PostedMessage {
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+#[near(contract_state)]
+#[derive(PanicOnDefault)]
+pub struct GuestBook {
+ messages: Vector,
+ payments: Vector,
+ owner: AccountId,
+}
+
+#[near]
+impl GuestBook {
+ #[init]
+ pub fn new(owner: AccountId) -> Self {
+ // New contracts will use the latest state version
+ migrations::state_version_write(&migrations::StateVersion::V2);
+
+ Self {
+ messages: Vector::new(StorageKey::Messages),
+ payments: Vector::new(StorageKey::Payments),
+ owner,
+ }
+ }
+
+ #[payable]
+ pub fn add_message(&mut self, text: String) {
+ let payment = env::attached_deposit();
+ let premium = payment >= POINT_ONE;
+ let sender = env::predecessor_account_id();
+
+ let message = PostedMessage {
+ premium,
+ sender,
+ text,
+ };
+ self.messages.push(message);
+ self.payments.push(payment);
+ }
+
+ pub fn get_messages(
+ &self,
+ from_index: Option,
+ limit: Option,
+ ) -> Vec<&PostedMessage> {
+ let from = u128::from(from_index.unwrap_or(U128(0)));
+
+ self.messages
+ .iter()
+ .skip(from as usize)
+ .take(u64::from(limit.unwrap_or(U64::from(10))) as usize)
+ .collect()
+ }
+
+ pub fn get_payments(&self, from_index: Option, limit: Option) -> Vec {
+ let from = u128::from(from_index.unwrap_or(U128(0)));
+
+ self.payments
+ .iter()
+ .skip(from as usize)
+ .take(u64::from(limit.unwrap_or(U64::from(10))) as usize)
+ .map(|x| U128(x.as_yoctonear()))
+ .collect()
+ }
+
+ pub fn get_owner(&self) -> AccountId {
+ self.owner.clone()
+ }
+}
diff --git a/advanced-multi-version-updates/v2/src/migrations.rs b/advanced-multi-version-updates/v2/src/migrations.rs
new file mode 100644
index 0000000..aa3ed2f
--- /dev/null
+++ b/advanced-multi-version-updates/v2/src/migrations.rs
@@ -0,0 +1,104 @@
+use std::str::FromStr;
+
+use crate::*;
+use near_sdk::{
+ borsh::{to_vec, BorshDeserialize},
+ near, PanicOnDefault, Promise,
+};
+
+#[near]
+#[derive(Debug)]
+pub(crate) enum StateVersion {
+ V1,
+ V2,
+ V3,
+}
+
+#[near]
+#[derive(PanicOnDefault)]
+struct GuestBookV1 {
+ messages: Vector,
+ payments: Vector,
+}
+
+// From V1 to V2
+impl GuestBook {
+ fn unsafe_add_owner() {
+ let GuestBookV1 { messages, payments } = env::state_read().unwrap();
+ let owner = AccountId::from_str("bob.near").unwrap();
+
+ env::state_write(&GuestBookV2 {
+ messages,
+ payments,
+ owner,
+ });
+ }
+}
+
+#[near]
+#[derive(PanicOnDefault)]
+struct GuestBookV2 {
+ messages: Vector,
+ payments: Vector,
+ owner: AccountId,
+}
+
+// Implement publicly available functions of the contract for self-upgrade and migration
+#[near]
+impl GuestBook {
+ pub fn unsafe_self_upgrade() {
+ near_sdk::assert_self();
+
+ let contract = env::input().expect("No contract code is attached in input");
+ Promise::new(env::current_account_id())
+ .deploy_contract(contract)
+ .then(Promise::new(env::current_account_id()).function_call(
+ "unsafe_migrate".to_string(),
+ Vec::new(),
+ NearToken::from_near(0),
+ env::prepaid_gas().saturating_sub(near_sdk::Gas::from_tgas(100)),
+ ))
+ .as_return();
+ }
+
+ fn migration_done() {
+ near_sdk::log!("Migration done.");
+ env::value_return(b"\"done\"");
+ }
+
+ fn needs_migration() {
+ env::value_return(b"\"needs-migration\"");
+ }
+
+ pub fn unsafe_migrate() {
+ near_sdk::assert_self();
+ let current_version = state_version_read();
+ near_sdk::log!("Migrating from version: {:?}", current_version);
+ match current_version {
+ StateVersion::V1 => {
+ GuestBook::unsafe_add_owner();
+ state_version_write(&StateVersion::V2);
+ }
+ _ => {
+ return GuestBook::migration_done();
+ }
+ }
+ GuestBook::needs_migration();
+ }
+}
+
+const VERSION_KEY: &[u8] = b"VERSION";
+
+fn state_version_read() -> StateVersion {
+ env::storage_read(VERSION_KEY)
+ .map(|data| {
+ StateVersion::try_from_slice(&data).expect("Cannot deserialize the contract state.")
+ })
+ .unwrap_or(StateVersion::V1) // StateVersion is introduced in V2 State.
+}
+
+pub(crate) fn state_version_write(version: &StateVersion) {
+ let data = to_vec(&version).expect("Cannot serialize the contract state.");
+ env::storage_write(VERSION_KEY, &data);
+ near_sdk::log!("Migrated to version: {:?}", version);
+}
diff --git a/advanced-multi-version-updates/v3/Cargo.toml b/advanced-multi-version-updates/v3/Cargo.toml
new file mode 100644
index 0000000..2d9bbd4
--- /dev/null
+++ b/advanced-multi-version-updates/v3/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "advanced-v3"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+near-sdk = "5.6"
diff --git a/advanced-multi-version-updates/v3/README.md b/advanced-multi-version-updates/v3/README.md
new file mode 100644
index 0000000..766d685
--- /dev/null
+++ b/advanced-multi-version-updates/v3/README.md
@@ -0,0 +1,165 @@
+# Guest Book Contract
+
+The [v2](../v2) contract was modified, removing the `payments` field and including that information in the `PostedMessage` structure.
+
+```rust
+pub struct PostedMessage {
+ pub payment: NearToken,
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+pub struct GuestBook {
+ messages: Vector,
+ owner: AccountId
+}
+```
+
+If we deploy this contract on top of the [v2](../v2/) one and call any method we again will get the error:
+
+```
+panicked at 'Cannot deserialize the contract state.: ... }',
+```
+
+This is because the new contract expects to find `PostedMessages` with 4 fields (`payment`, `premium`, `sender`, `text`)
+but the saved messages only have 3 fields (they lack the `payment` field).
+
+In order to fix this problem we need to `migrate` the state, i.e. iterate through the current saved messages transforming them to the new version.
+
+We're getting rid of Vector of `payments` entirely, so just removing the field from the state won't cut it. The data is spread across multiple keys in the storage, so to clean it up properly, we need to explicitly call `payments::clear()`.
+
+```rust
+impl GuestBook {
+ // Upgrades from V2 to V3
+ fn unsafe_add_payment_to_message() {
+ let GuestBookV2 {
+ messages: old_messages,
+ mut payments,
+ owner,
+ } = env::state_read().unwrap();
+
+ let default_payment = NearToken::from_yoctonear(0);
+
+ // New messages must be written to storage
+ let mut messages = Vector::new(StorageKey::Messages);
+
+ for (idx, old_message) in old_messages.iter().enumerate() {
+ let payment = payments.get(idx as u32).unwrap_or(&default_payment);
+
+ messages.push(PostedMessageV3 {
+ premium: old_message.premium.clone(),
+ sender: old_message.sender.clone(),
+ text: old_message.text.clone(),
+ payment: payment.clone(),
+ });
+ }
+
+ // Payments must be removed from storage
+ payments.clear();
+
+ env::state_write(&GuestBookV3 { messages, owner });
+ }
+
+ fn migration_done() {
+ near_sdk::log!("Migration done.");
+ env::value_return(b"\"done\"");
+ }
+
+ fn needs_migration() {
+ env::value_return(b"\"needs-migration\"");
+ }
+
+ pub fn unsafe_migrate() {
+ near_sdk::assert_self();
+ let current_version = state_version_read();
+ near_sdk::log!("Migrating from version: {:?}", current_version);
+ match current_version {
+ StateVersion::V1 => {
+ GuestBook::unsafe_add_owner();
+ state_version_write(&StateVersion::V2);
+ }
+ StateVersion::V2 => {
+ GuestBook::unsafe_add_payment_to_message();
+ state_version_write(&StateVersion::V3);
+ }
+ _ => {
+ return GuestBook::migration_done();
+ }
+ }
+ GuestBook::needs_migration();
+ }
+}
+```
+
+
+
+# Upgrading Base Contract
+
+## 1. Build & Deploy & Migrate State
+
+_In this example we will be using [NEAR CLI](https://github.com/near/near-cli)
+to intract with the NEAR blockchain and the smart contract and [near-cli-rs](https://near.cli.rs)
+which provides more control over interactions and has interactive menus for subcommands selection_
+
+To build contract install [`cargo-near`](https://github.com/near/cargo-near) and run:
+
+```bash
+# from repo root
+cd advanced-multi-version-updates/v2
+cargo near build --no-docker
+```
+
+You can deploy the updated contract by running:
+
+```bash
+# `update-migrate-rust-advanced-multiversion-updates.testnet` was used as example of
+cargo near deploy --no-docker without-init-call network-config testnet sign-with-keychain send
+```
+
+Run this command to see the "Cannot deserialize..." error
+
+```bash
+# NEAR CLI
+near view get_messages
+# near-cli-rs
+near contract call-function as-read-only get_messages json-args {} network-config testnet now
+```
+
+Ask the contract to migrate the state
+
+```bash
+# near-cli-rs (may be useful to specify more gas for large state migrations)
+near contract call-function as-transaction unsafe_migrate json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send
+```
+
+#### Deploying and Migrating
+
+You can actually deploy the contract and migrate the state in one line:
+
+```bash
+# near-cli-rs (may be useful to specify more gas for large state migrations)
+cargo near deploy --no-docker with-init-call unsafe_migrate json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send
+```
+
+
+
+## 2. Retrieve the Stored Messages
+
+`get_messages` will now return messages that include a `payment` field.
+
+```bash
+# NEAR CLI
+near view get_messages
+# near-cli-rs
+near contract call-function as-read-only get_messages json-args {} network-config testnet now
+```
+
+`get_payments` will raise the error `MethodResolveError(MethodNotFound)` since the method does not exist anymore.
+
+```bash
+# NEAR CLI
+near view get_payments
+# near-cli-rs
+near contract call-function as-read-only get_payments json-args {} network-config testnet now
+```
diff --git a/advanced-multi-version-updates/v3/rust-toolchain.toml b/advanced-multi-version-updates/v3/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/advanced-multi-version-updates/v3/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/advanced-multi-version-updates/v3/src/lib.rs b/advanced-multi-version-updates/v3/src/lib.rs
new file mode 100644
index 0000000..20e38cb
--- /dev/null
+++ b/advanced-multi-version-updates/v3/src/lib.rs
@@ -0,0 +1,79 @@
+pub mod migrations;
+
+use near_sdk::{near, BorshStorageKey, PanicOnDefault};
+
+use near_sdk::borsh::BorshSerialize;
+use near_sdk::json_types::{U128, U64};
+use near_sdk::store::Vector;
+
+use near_sdk::{env, AccountId, NearToken};
+
+const POINT_ONE: NearToken = NearToken::from_millinear(100);
+
+#[derive(BorshSerialize, BorshStorageKey)]
+#[borsh(crate = "near_sdk::borsh")]
+pub enum StorageKey {
+ Messages,
+}
+
+#[near(serializers=[json, borsh])]
+pub struct PostedMessage {
+ pub payment: NearToken,
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+#[near(contract_state)]
+#[derive(PanicOnDefault)]
+pub struct GuestBook {
+ messages: Vector,
+ owner: AccountId,
+}
+
+#[near]
+impl GuestBook {
+ #[init]
+ pub fn new(owner: AccountId) -> Self {
+ // New contracts will use the latest state version
+ migrations::state_version_write(&migrations::StateVersion::V3);
+
+ Self {
+ messages: Vector::new(StorageKey::Messages),
+ owner,
+ }
+ }
+
+ #[payable]
+ pub fn add_message(&mut self, text: String) {
+ let payment = env::attached_deposit();
+ let premium = payment >= POINT_ONE;
+ let sender = env::predecessor_account_id();
+
+ let message = PostedMessage {
+ payment,
+ premium,
+ sender,
+ text,
+ };
+ self.messages.push(message);
+ }
+
+ pub fn get_messages(
+ &self,
+ from_index: Option,
+ limit: Option,
+ ) -> Vec<&PostedMessage> {
+ let from = u128::from(from_index.unwrap_or(U128(0)));
+
+ self.messages
+ .iter()
+ .skip(from as usize)
+ .take(u64::from(limit.unwrap_or(U64::from(10))) as usize)
+ .collect()
+ }
+
+ pub fn get_owner(&self) -> AccountId {
+ self.owner.clone()
+ }
+}
diff --git a/advanced-multi-version-updates/v3/src/migrations.rs b/advanced-multi-version-updates/v3/src/migrations.rs
new file mode 100644
index 0000000..65d3d78
--- /dev/null
+++ b/advanced-multi-version-updates/v3/src/migrations.rs
@@ -0,0 +1,166 @@
+use std::str::FromStr;
+
+use crate::*;
+use near_sdk::{
+ borsh::{to_vec, BorshDeserialize},
+ near, PanicOnDefault, Promise,
+};
+
+#[near]
+#[derive(Debug)]
+pub(crate) enum StateVersion {
+ V1,
+ V2,
+ V3,
+}
+
+#[near]
+#[derive(PanicOnDefault)]
+struct GuestBookV1 {
+ messages: Vector,
+ payments: Vector,
+}
+
+// This structure is implemented from V1
+#[near]
+#[derive(Clone)]
+struct PostedMessageV1 {
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+// From V1 to V2
+impl GuestBook {
+ fn unsafe_add_owner() {
+ let GuestBookV1 { messages, payments } = env::state_read().unwrap();
+ let owner = AccountId::from_str("bob.near").unwrap();
+
+ env::state_write(&GuestBookV2 {
+ messages,
+ payments,
+ owner,
+ });
+ }
+}
+
+#[near]
+#[derive(PanicOnDefault)]
+struct GuestBookV2 {
+ messages: Vector,
+ payments: Vector,
+ owner: AccountId,
+}
+
+// From V2 to V3
+impl GuestBook {
+ fn unsafe_add_payment_to_message() {
+ let GuestBookV2 {
+ messages: old_messages,
+ mut payments,
+ owner,
+ } = env::state_read().unwrap();
+
+ let default_payment = NearToken::from_yoctonear(0);
+
+ // New messages must be written to storage
+ let mut messages = Vector::new(StorageKey::Messages);
+
+ for (idx, old_message) in old_messages.iter().enumerate() {
+ let payment = payments.get(idx as u32).unwrap_or(&default_payment);
+
+ messages.push(PostedMessageV3 {
+ premium: old_message.premium.clone(),
+ sender: old_message.sender.clone(),
+ text: old_message.text.clone(),
+ payment: payment.clone(),
+ });
+ }
+
+ // Payments must be removed from storage
+ payments.clear();
+
+ env::state_write(&GuestBookV3 { messages, owner });
+ }
+}
+
+#[near]
+#[derive(PanicOnDefault)]
+struct GuestBookV3 {
+ messages: Vector,
+ owner: AccountId,
+}
+
+// New field was introduced as part of V3
+#[near]
+#[derive(Clone)]
+struct PostedMessageV3 {
+ pub payment: NearToken,
+ pub premium: bool,
+ pub sender: AccountId,
+ pub text: String,
+}
+
+// Implement publicly available functions of the contract for self-upgrade and migration
+#[near]
+impl GuestBook {
+ pub fn unsafe_self_upgrade() {
+ near_sdk::assert_self();
+
+ let contract = env::input().expect("No contract code is attached in input");
+ Promise::new(env::current_account_id())
+ .deploy_contract(contract)
+ .then(Promise::new(env::current_account_id()).function_call(
+ "unsafe_migrate".to_string(),
+ Vec::new(),
+ NearToken::from_near(0),
+ env::prepaid_gas().saturating_sub(near_sdk::Gas::from_tgas(100)),
+ ))
+ .as_return();
+ }
+
+ fn migration_done() {
+ near_sdk::log!("Migration done.");
+ env::value_return(b"\"done\"");
+ }
+
+ fn needs_migration() {
+ env::value_return(b"\"needs-migration\"");
+ }
+
+ pub fn unsafe_migrate() {
+ near_sdk::assert_self();
+ let current_version = state_version_read();
+ near_sdk::log!("Migrating from version: {:?}", current_version);
+ match current_version {
+ StateVersion::V1 => {
+ GuestBook::unsafe_add_owner();
+ state_version_write(&StateVersion::V2);
+ }
+ StateVersion::V2 => {
+ GuestBook::unsafe_add_payment_to_message();
+ state_version_write(&StateVersion::V3);
+ }
+ _ => {
+ return GuestBook::migration_done();
+ }
+ }
+ GuestBook::needs_migration();
+ }
+}
+
+const VERSION_KEY: &[u8] = b"VERSION";
+
+fn state_version_read() -> StateVersion {
+ env::storage_read(VERSION_KEY)
+ .map(|data| {
+ StateVersion::try_from_slice(&data).expect("Cannot deserialize the contract state.")
+ })
+ .unwrap_or(StateVersion::V1) // StateVersion is introduced in V2 State.
+}
+
+pub(crate) fn state_version_write(version: &StateVersion) {
+ let data = to_vec(&version).expect("Cannot serialize the contract state.");
+ env::storage_write(VERSION_KEY, &data);
+ near_sdk::log!("Migrated to version: {:?}", version);
+}
diff --git a/basic-updates/base/rust-toolchain.toml b/basic-updates/base/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/basic-updates/base/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/enum-updates/base/rust-toolchain.toml b/enum-updates/base/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/enum-updates/base/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/enum-updates/update/rust-toolchain.toml b/enum-updates/update/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/enum-updates/update/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/enum-updates/update/tests/workspaces.rs b/enum-updates/update/tests/workspaces.rs
index c7e023f..b9f6aeb 100644
--- a/enum-updates/update/tests/workspaces.rs
+++ b/enum-updates/update/tests/workspaces.rs
@@ -1,4 +1,4 @@
-use near_sdk::NearToken;
+use near_workspaces::types::NearToken;
use serde_json::json;
use std::fs;
diff --git a/self-updates/base/rust-toolchain.toml b/self-updates/base/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/self-updates/base/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/self-updates/update/rust-toolchain.toml b/self-updates/update/rust-toolchain.toml
new file mode 100644
index 0000000..1966b4e
--- /dev/null
+++ b/self-updates/update/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.81.0"
+components = ["rustfmt"]
+targets = ["wasm32-unknown-unknown"]
diff --git a/self-updates/update/tests/workspaces.rs b/self-updates/update/tests/workspaces.rs
index 8e9313e..7635960 100644
--- a/self-updates/update/tests/workspaces.rs
+++ b/self-updates/update/tests/workspaces.rs
@@ -1,8 +1,8 @@
use std::fs;
use near_sdk::json_types::U128;
-use near_sdk::{AccountId, Gas};
-use near_workspaces::types::NearToken;
+use near_sdk::AccountId;
+use near_workspaces::types::{Gas, NearToken};
use near_workspaces::Account;
use near_workspaces::Contract;
use rstest::{fixture, rstest};