Недавно я обнаружил, что можно указать функцию защиты, которая будет выполняться перед функциями обновления и запроса смарт-контрактов контейнеров, написанных на Rust, на Интернет-компьютере.
Вы можете сказать мне, что следующий пост является просто плагиатом документации Crate, но, поскольку я понял, что это возможно, только взглянув на репозиторий уведомления о транзакциях OpenChat, я подумал, что это стоило поста 😄.
Хотите начать создавать децентрализованные приложения, не изучая новый язык программирования? Ознакомьтесь с Juno, платформой Блокчейн как услуга с открытым исходным кодом, которая позволяет быстрее и проще, чем когда-либо прежде, создавать децентрализованные приложения только с внешним кодом! ⚡️🚀🤯
Оригинальный подход
Я начал свой путь с Rust, когда перенес свой существующий код Motoko — то есть, когда я обновил существующие смарт-контракты Papyrs.
Поскольку эти контейнеры предназначались для пользовательских данных, мне пришлось перенести и функции, требующие контроля доступа.
Для этого я реализовал сравнение принципалов — т.е. сопоставляю вызывающих функций с пользователями, которые сохранены в состоянии. Если они равны, методы могут быть выполнены, если нет, я выдаю ошибки и отклоняю вызов.
Не зная, как писать охранники, я в основном дублировал if
повсюду в каждом вызове, который нужно было защитить.
use candid::Principal; use ic_cdk::{caller, trap}; use ic_cdk_macros::{query, init}; use std::cell::RefCell; #[derive(Default)] pub struct State { pub user: Option<Principal>, } // This canister cannot be created without user #[init] fn init(user: Principal) { STATE.with(|state| { *state.borrow_mut() = State { user: Some(user) }; }); } // Mutable global state. // See Roman Kashitsyn's post for more info: // https://mmapped.blog/posts/01-effective-rust-canisters.html thread_local! { static STATE: RefCell<State> = RefCell::default(); } #[query] fn greet(name: String) -> String { let user: Principal = STATE.with(|state| state.borrow().user).unwrap(); // 🖖 Here I check if the caller matches the user who owns the canister if user != caller() { trap("User does not have the permission to say hello."); } format!("Hello, {}!", name) }
Хотя он работает так, как я ожидал, вы можете легко себе представить, что дублирование одного и того же кода — особенно по мере роста количества функций — медленно увеличивает читабельность.
Это было до того, как я, к счастью, обнаружил функцию guard 💪.
Сторожить
Стандартные сторожевые функции — это функции, которые могут выполняться перед функцией обновления или запроса. Когда они возвращают ошибку, соответствующий метод не выполняется.
Поэтому я изменил свой код, чтобы воспользоваться им и избежать дублирования.
Я создал новый модуль guards.rs
для выполнения точно такого же сопоставления с образцом, как тот, который я реализовал в приведенном выше фрагменте кода.
use candid::Principal; use ic_cdk::caller; use crate::STATE; pub fn caller_is_user() -> Result<(), String> { let caller = caller(); let user: Principal = STATE.with(|state| state.borrow().user).unwrap(); if caller == user { Ok(()) } else { Err("Caller is not the user of the canister.".to_string()) } }
После установки я «просто» заменил свои существующие псевдотесты объявлением гвардии.
// ️🖖1️⃣ declare the new module mod guards; use candid::Principal; use ic_cdk::{caller, trap}; use ic_cdk_macros::{query, init}; use std::cell::RefCell; // 🖖2️⃣ the function needs to be imported use crate::guards::caller_is_user; #[derive(Default)] pub struct State { pub user: Option<Principal>, } // This canister cannot be created without user #[init] fn init(user: Principal) { STATE.with(|state| { *state.borrow_mut() = State { user: Some(user) }; }); } // Mutable global state. // See Roman Kashitsyn's post for more info: // https://mmapped.blog/posts/01-effective-rust-canisters.html thread_local! { static STATE: RefCell<State> = RefCell::default(); } // 🖖3️⃣ set the guard by its function's name #[query(guard = "caller_is_user")] fn greet(name: String) -> String { format!("Hello, {}!", name) }
И, в принципе, это уже 🥳.
Рецепт:
- Создать сторожевую функцию, которая возвращает ошибку, если условия не выполняются
- Импортируйте модуль
- Аннотировать функции, которые необходимо защитить
- Весело 😁
Резюме
Не самый длинный мой пост, который я когда-либо писал, но я надеюсь, что он когда-нибудь будет полезен для кого-то, как это было для меня, чтобы открыть для себя эти маленькие хитрости.
В бесконечность и дальше
Дэвид
Чтобы узнать больше о приключениях, подписывайтесь на меня в Твиттере 🖖