Увеличение моего значения

Последним шагом в нашем контракте с инкрементом является предоставление каждому пользователю возможности обновлять собственное значение инкремента.

Изменение HashMap

Внесение изменений в значение HashMap так же важно, как и получение значения. Если вы попытаетесь изменить какое-либо значение до того, как оно будет инициализировано, ваш контракт запаникует!

Но не бойтесь, мы можем продолжать использовать функцию my_number_or_zero, которую мы создали для защиты от таких ситуаций!

impl MyContract {

    /* --snip-- */

    // Set the value for the calling AccountId
    #[ink(message)]
    pub fn set_my_number(&mut self, value: u32) {
        let caller = self.env().caller();
        self.my_number_map.insert(caller, value);
    }

    // Add a value to the existing value for the calling AccountId
    #[ink(message)]
    pub fn add_my_number(&mut self, value: u32) {
        let caller = self.env().caller();
        let my_number = self.my_number_or_zero(&caller);
        self.my_number_map.insert(caller, my_number + value);
    }

    /// Returns the number for an AccountId or 0 if it is not set.
    fn my_number_or_zero(&self, of: &AccountId) -> u32 {
        *self.my_number_map.get(of).unwrap_or(&0)
    }
}

Здесь мы написали два вида функций, которые изменяют HashMap. Один просто вставляет значение непосредственно в хранилище, без необходимости сначала читать значение, а другой изменяет существующее значение. Обратите внимание, что мы всегда можем вставить значение, не беспокоясь, так как это инициализирует значение в хранилище, но прежде чем вы сможете получить или изменить что-либо, нам нужно вызвать my_number_or_zero, чтобы убедиться, что мы работаем с реальным значением.

Почувствуй боль (необязательно)

У нас не всегда будет существующее значение в хранилище нашего контракта. Мы можем воспользоваться преимуществом типа Rust Option<T> для решения этой задачи. Если в хранилище контракта нет значения, мы вставим новое; наоборот, если есть существующее значение, мы только обновим его.

ink! HashMaps предоставляет хорошо известный entry API, который мы можем использовать для достижения такого типа «расстроенного» поведения:

let caller = self.env().caller();
self.my_number_map
    .entry(caller)
    .and_modify(|old_value| old_value += by)
    .or_insert(by);

Твоя очередь!

Следуйте «ДЕЙСТВИЯМ», чтобы завершить свой смарт-контракт Incrementer.

Не забудьте запустить cargo +nightly test, чтобы проверить свою работу.

{% tabs %} {% tab title="🔨Starting Point" %}

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod incrementer {
    #[ink(storage)]
    pub struct Incrementer {
        value: i32,
        my_value: ink_storage::collections::HashMap<AccountId, u64>,
    }

    impl Incrementer {
        #[ink(constructor)]
        pub fn new(init_value: i32) -> Self {
            Self {
                value: init_value,
                my_value: ink_storage::collections::HashMap::new(),
            }
        }

        #[ink(constructor)]
        pub fn default() -> Self {
            Self {
                value: 0,
                my_value: Default::default(),
            }
        }

        #[ink(message)]
        pub fn get(&self) -> i32 {
            self.value
        }

        #[ink(message)]
        pub fn inc(&mut self, by: i32) {
            self.value += by;
        }

        #[ink(message)]
        pub fn get_mine(&self) -> u64 {
            self.my_value_or_zero(&self.env().caller())
        }

        #[ink(message)]
        pub fn inc_mine(&mut self, by: u64) {
            // ACTION: Get the `caller` of this function.
            // ACTION: Get `my_value` that belongs to `caller` by using `my_value_or_zero`.
            // ACTION: Insert the incremented `value` back into the mapping.
        }

        fn my_value_or_zero(&self, of: &AccountId) -> u64 {
            *self.my_value.get(of).unwrap_or(&0)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

{% endtab %}

{% tab title="✅Potential Solution" %}

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod incrementer {
    #[ink(storage)]
    pub struct Incrementer {
        value: i32,
        my_value: ink_storage::collections::HashMap<AccountId, u64>,
    }

    impl Incrementer {
        #[ink(constructor)]
        pub fn new(init_value: i32) -> Self {
            Self {
                value: init_value,
                my_value: ink_storage::collections::HashMap::new(),
            }
        }

        #[ink(constructor)]
        pub fn default() -> Self {
            Self {
                value: 0,
                my_value: Default::default(),
            }
        }

        #[ink(message)]
        pub fn get(&self) -> i32 {
            self.value
        }

        #[ink(message)]
        pub fn inc(&mut self, by: i32) {
            self.value += by;
        }

        #[ink(message)]
        pub fn get_mine(&self) -> u64 {
            self.my_value_or_zero(&self.env().caller())
        }

        #[ink(message)]
        pub fn inc_mine(&mut self, by: u64) {
            let caller = self.env().caller();
            let my_value = self.my_value_or_zero(&caller);
            self.my_value.insert(caller, my_value + by);
        }

        fn my_value_or_zero(&self, of: &AccountId) -> u64 {
            *self.my_value.get(of).unwrap_or(&0)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

{% endtab %} {% endtabs %}