可调度项、事件和错误
在本教程的上一节中,我们奠定了旨在管理小猫的所有权的基础 - 即使它们还不存在!在这一部分中,我们将通过使用我们声明的存储项目使我们的托盘能够创建Kitty来使用这些基础。稍微分解一下,我们将写:
create_kitty
:一个可调度或可公开调用的函数,允许帐户铸造Kitty。mint()
:一个辅助功能,用于更新托的存储项目并执行错误检查,由 调用。create_kitty
- pallet
Events
: 使用 FRAME的#[pallet::event]
属性。
在本部分结束时,我们将检查所有内容是否编译无误,并使用外部PolkadotJS 应用程序 UI 调用我们的 create_kitty 。
公共和私人功能
在我们深入研究之前,了解我们将围绕 Kitty pallet的铸币和所有权管理功能进行编码的pallet设计决策是非常重要。
作为开发人员,我们希望确保我们编写的代码高效且优雅。 通常,针对一个进行优化会针对另一个进行优化。 我们将设置pallet以优化两者的方式是将“繁重”逻辑分解为私有辅助函数。 这也提高了代码的可读性和可重用性。 正如我们将看到的,我们可以创建可以由多个可调度函数调用的私有函数,而不会影响安全性。 事实上,以这种方式构建可以被认为是一种附加的安全功能。 查看这个关于编写和使用辅助函数的操作指南以了解更多信息。
在开始实施这种方法之前,让我们首先描绘一下组合可调度和辅助函数的样子。
create_kitty是一个可调度的功能或外在功能::
- 检查源是否已签名
- 使用签名帐户生成随机哈希
- 使用随机哈希创建新的 Kitty 对象
- 调用私有函数
mint()
mint是一个私有助手函数,它:
- 检查小猫咪是否不存在
- 使用新的 Kitty ID 更新存储(适用于所有 Kitty 和所有者的帐户)
- 更新存储和新所有者帐户的新小猫总数
- 存入一个事件,以指示已成功创建小猫
编写可调度create_kitty
FRAME 中的可调度始终遵循相同的结构。 所有的pallet dispatchable 都存在于#[pallet::call] 宏下,该宏需要使用impl<T: Config> Pallet {} 声明dispatchables 部分。 阅读有关这些 FRAME 宏的文档以了解它们的工作原理。 这里我们需要知道的是,它们是 FRAME 的一个有用功能,可以最大限度地减少为将pallet正确集成到 Substrate 链的runtime所需编写的代码。
权重
根据其文档中描述的#[pallet::call] 的要求,每个可调度函数都必须具有关联的权重。 权重是使用 Substrate 开发的重要部分,因为它们提供了围绕计算量的安全防护,以在执行时适合块。
Substrate 的加权系统迫使开发人员在调用每个外部函数之前考虑其计算复杂性。 这允许节点考虑最坏情况的执行时间,避免因外部因素导致网络滞后,这些外部因素可能需要比指定的块时间更长的时间。 权重也与任何已签名外在的收费系统密切相关。
由于这只是一个教程,我们将默认所有权重为 100 以保持简单。
假设您现在已经用本节的帮助文件替换了pallets/kitties/src/lib.rs 的内容,找到ACTION #1 并使用以下行完成函数的开头:
#[pallet::weight(100)]
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?; // <- add this line
let kitty_id = Self::mint(&sender, None, None)?; // <- add this line
// Logging to the console
log::info!("A kitty is born with ID: {:?}.", kitty_id); // <- add this line
// ACTION #4: Deposit `Created` event
Ok(())
}
我们不会进行调试,但登录到控制台是一个有用的提示,可确保您的托盘按预期运行。 为了使用 log::info,将其添加到您的托盘的 Cargo.toml 文件中。
编写函数mint()
正如我们在上一节中编写create_kitty
时所看到的,我们需要创建mint()
,以便将我们新的唯一Kitty对象写入本教程第二部分中声明的各种存储项。
让我们直接开始吧。我们的mint()
函数将采用以下参数:
owner
:&T::AccountId
dna
:Option<[u8; 16]>
gender
:Option<Gender>
它将返回Result<T::Hash, Error<T>>
。
粘贴以下代码片段以编写mint
函数,取代工作代码库中的ACTION #2:
// Helper to mint a Kitty.
pub fn mint(
owner: &T::AccountId,
dna: Option<[u8; 16]>,
gender: Option<Gender>,
) -> Result<T::Hash, Error<T>> {
let kitty = Kitty::<T> {
dna: dna.unwrap_or_else(Self::gen_dna),
price: None,
gender: gender.unwrap_or_else(Self::gen_gender),
owner: owner.clone(),
};
let kitty_id = T::Hashing::hash_of(&kitty);
// Performs this operation first as it may fail
let new_cnt = Self::count_for_kitties().checked_add(1)
.ok_or(<Error<T>>::CountForKittiesOverflow)?;
// Check if the kitty does not already exist in our storage map
ensure!(Self::kitties(&kitty_id) == None, <Error<T>>::KittyExists);
// Performs this operation first because as it may fail
<KittiesOwned<T>>::try_mutate(&owner, |kitty_vec| {
kitty_vec.try_push(kitty_id)
}).map_err(|_| <Error<T>>::ExceedMaxKittyOwned)?;
<Kitties<T>>::insert(kitty_id, kitty);
<CountForKitties<T>>::put(new_cnt);
Ok(kitty_id)
}
让我们来看看上面的代码在做什么。
我们正在做的第一件事就是创建一个新的Kitty对象。然后,我们使用基于kitty当前属性的哈希函数创建一个唯一的kitty_id
。
接下来,我们使用存储获取器函数Self::count_for_kitties()
增加CountForKitties
。我们还在检查check_add()
函数的溢出。
我们最后的验证是确保 kitty_id 不存在于我们的 Kitties StorageMap 中。 这是为了避免任何可能的哈希键重复插入。
一旦我们的支票通过,我们就会通过以下方式更新我们的存储项目:
- 利用
try_mutate
更新小猫的主人vector。 - 使用
insert
Substrate的StorageMap API提供的方法,用于存储实际的Kitty对象并将其与其kitty_id
相关联。 - 使用
put
由StorageValue API提供,用于存储最新的Kitty计数。
快速回顾我们的存储项目
<Kitties<T>>
:通过存储Kitty对象并将其与其Kitty ID相关联,存储Kitty的独特特征和价格。&