original:
author: Greg
url: https://x.com/Greg_Nazario/status/1751025731839373612问题
key是什么意思?store是什么意思?”它的灵感来自于许多现有的 NFT 市场的邮箱系统,用于将遗留数字资产发送给其他用户。
您是否曾想过在 Move 中哪些结构体具备哪些能力?
key、copy、drop、store 是什么意思?
我将为您展示在一个普通的日常邮箱用例中这些是如何运作的。
让我们在本期的 DailyMove 中深入探讨。
与所有这些示例一样,我们会使用一个对象来存储邮箱的主要资源。我将其称为 MailboxRouter,它为所有用户的邮箱提供了一个单一的存放位置。
我们可以看到它具有 key 能力,这允许它作为资源存储在一个地址中。

但但这个 key 意味着什么呢?
它意味着能够从全局状态中借用,这在 Move 中是一个关键概念。您可以将全局状态视为每个地址以及该地图中每个资源的大型映射。
在这种情况下,mailbox_router_address 和 MailboxRouter

但是,我们怎样把邮件放入邮箱呢?
路由器有一个 SmartTable,它带有两个输入 MailboxId 和 Mailbox
MailboxId 必须具有 store 能力。这使得它能够原生地存储在另一个对象中,一般是一个 table 或 vector 。
Mailbox 也有这种能力。

但是,它也有 copy 和 drop 能力。
copy 允许您直接复制内部内容。
想想像地址这样的东西,并非只有一个,但是像 NFT 或一些 coin,您无法复制它。
所有可复制的结构体还要求所有内部字段也都是可复制的。
drop 允许您在任何时候丢弃该值。对于您最喜欢的 NFT 而言,有些东西您可能不希望它从区块链中消失。

让我们来聊聊我们要放入邮箱的信封。
这些只有 store,那是因为 Coin 和 Token 类型不具备 copy 或 drop 能力。
但是,您可能会想,读完邮件后我怎么销毁信封呢?在现实世界中,我会取出信件,然后扔掉信封。让我们看看。

像 Envelope 这样的结构体可以分解为它们的部分。
这意味着我们可以把它拆分为它的部分,丢弃我们不需要的那些部分,并将需要保留的部分分发至其他地方。
就像您可能会撕开一个信封,然后把里面装着您辛苦挣来的薪水的信封扔掉。

但是,Greg,我只想寄邮件啦。
信封可以用里面的每一块来搭建。 当用户想要领取时,他们根据自己的邮箱获得授权进行领取。但是,发件人也保存在邮件中。这是一种保障,如果邮件发给了错误的人可以取消邮件(要是美国邮政也能为我这样做就好了)。


打开邮件和查看邮件足够简单,因为这些功能使我们能够存储邮件并将其组件转移给其他人。您甚至可以在邮件在邮箱中时查看邮件,类似于我总是忘记删除的 1000 封电子邮件。

感谢阅读,希望您对 Move 结构体的功能有了些许了解。
所有源代码都可在此处获取: struct-capabilities
/// 邮箱示例。这展示了如何使用中间人合约创建一个邮箱,向其他用户发送多种不同类型的项目。
///
/// 信封包装了在两方之间发送的对象、代币和硬币,并且如果收件人没有认领,可以由发件人取回。
module deploy_addr::mailbox {
use std::option::{Self, Option};
use std::signer;
use std::string::String;
use std::vector;
use aptos_std::smart_table::{Self, SmartTable};
use aptos_std::smart_vector::{Self, SmartVector};
use aptos_framework::aptos_coin::AptosCoin;
use aptos_framework::coin::{Self, Coin};
use aptos_framework::object::{Self, Object, ObjectCore, ExtendRef};
use aptos_token::token::{Self, Token, TokenId};
/// 不是所有的代币输入(creator_addresses, collection_names, token_names)长度都匹配
const E_TOKEN_INPUT_LENGTH_MISMATCH: u64 = 2;
/// 收件人没有邮箱存在
const E_NO_MAILBOX_EXISTS: u64 = 3;
/// 邮箱不为空,不能删除它
const E_MAILBOX_NOT_EMPTY: u64 = 4;
/// 邮箱为空
const E_MAILBOX_EMPTY: u64 = 5;
/// 邮件索引超出范围,可能已经被打开
const E_OUT_OF_BOUNDS: u64 = 6;
/// 不能退回信封,调用者不是信封的发送者
const E_NOT_SENDER: u64 = 7;
const SEED: vector<u8> = b"Mailbox";
/// 一个结构体,代表共享位置的邮箱
///
/// 在这个示例中,它只能在合约创建时创建的对象中存在
struct MailboxRouter has key {
mailboxes: SmartTable<MailboxId, Mailbox>,
extend_ref: ExtendRef,
}
/// 这是一个用于 SmartTable 中的键的结构体
///
/// 它必须具有 store 能力,以便能够放入像 SmartTable 或 Vector 这样的集合中
///
/// 它必须具有 copy 能力,以便能够从引用版本中被取消引用或“copy ”
///
/// 例如:
/// ```move
/// let id = MailboxId { receiver: @0x1 }
/// let reference = &id;
/// let copied_id = *reference;
/// ```
///
/// 如果它不是 copy 的,那么它就不能直接被复制,而是需要手动传递它的内容:
/// 例如:
/// ```move
/// let id = MailboxId { receiver: @0x1 }
/// let reference = &id;
/// let copied_id = MailboxId { receiver: *reference.receiver };
/// ```
struct MailboxId has store, copy, drop {
receiver: address
}
/// 一个邮箱,跟踪所有信封按时间顺序插入的顺序
struct Mailbox has store {
mail: SmartVector<Envelope>,
}
/// 一个信封,存储要发送给收件人的物品
///
/// 这种类型不能复制或丢弃,因为硬币和代币不能复制或丢弃。
///
/// 这些物品不能复制或丢弃的目的是防止NFT丢失和硬币丢失。
///
/// 然而,信封可以通过分解来拆卸,这将允许直接移除每一块
/// 例如:
/// ```move
/// let Envelope {
/// sender,
/// note,
/// coins,
/// legacy_tokens,
/// objects
/// } = envelope;
/// ```
struct Envelope has store {
/// 信封的发送者
/// 这是必需的,以便能够退回信封
sender: address,
/// 给收件人的字符串备注
note: Option<String>,
/// 这只支持AptosCoin,但可以扩展
coins: Option<Coin<AptosCoin>>,
/// 旧版代币标准
legacy_tokens: vector<Token>,
/// 任何对象,包括数字资产
objects: vector<Object<ObjectCore>>
}
```plaintext
/// 在部署此合约时设置,对象中邮箱的唯一实例
fun init_module(deployer: &signer) {
// 创建一个以deployer命名的对象,并使用SEED作为种子
let constructor_ref = object::create_named_object(deployer, SEED);
let extend_ref = object::generate_extend_ref(&constructor_ref);
// 禁用邮箱对象的转移,并丢弃它,这样就没有人设法转移邮箱
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
object::disable_ungated_transfer(&transfer_ref);
// 生成对象的签名者
let object_signer = object::generate_signer(&constructor_ref);
// 将MailboxRouter对象转移到签名者地址
move_to(&object_signer, MailboxRouter {
mailboxes: smart_table::new(),
extend_ref
});
}
/// 向地址发送信封,信封中包含对象、硬币、代币和备注
entry fun send_mail(
caller: &signer,
receiver: address,
note: Option<String>,
coin_amount: u64,
objects: vector<Object<ObjectCore>>,
legacy_token_creator_addresses: vector<address>,
legacy_token_collection_names: vector<String>,
legacy_token_names: vector<String>,
) acquires MailboxRouter {
// 确保所有输入的长度都匹配(代币ID将有效)
assert!(
vector::length(&legacy_token_creator_addresses) == vector::length(&legacy_token_collection_names),
E_TOKEN_INPUT_LENGTH_MISMATCH
);
assert!(
vector::length(&legacy_token_creator_addresses) == vector::length(&legacy_token_names),
E_TOKEN_INPUT_LENGTH_MISMATCH
);
// 为旧版代币构建代币ID
let token_ids = vector[];
let length = vector::length(&legacy_token_names);
for(i
in
0..length)
{
let creator_address = *vector::borrow(&legacy_token_creator_addresses, i);
let collection_name = *vector::borrow(&legacy_token_collection_names, i);
let token_name = *vector::borrow(&legacy_token_names, i);
let data_id = token::create_token_data_id(creator_address, collection_name, token_name);
let latest_property_version = token::get_tokendata_largest_property_version(creator_address, data_id);
let token_id = token::create_token_id(data_id, latest_property_version);
vector::push_back(&mut token_ids, token_id);
};
// 调用内部函数发送邮件
send_mail_internal(caller, receiver, note, coin_amount, objects, token_ids);
}
/// 打开最新的信封
entry fun open_latest_envelope(caller: &signer) acquires MailboxRouter {
// 获取调用者的邮箱
let mailbox = get_mailbox(signer::address_of(caller));
// 获取邮箱中邮件的数量
let length = smart_vector::length(&mailbox.mail);
// 打开最后一封信,即索引为length - 1的信
open_envelope(caller, length - 1)
}
/// 打开最旧的信封
entry fun open_oldest_envelope(caller: &signer) acquires MailboxRouter {
// 打开第一封信,即索引为0的信
open_envelope(caller, 0)
}
/// 打开任意编号的信封
entry fun open_envelope(caller: &signer, num: u64) acquires MailboxRouter {
// 获取调用者的地址
let caller_address = signer::address_of(caller);
// 打开指定编号的信
let envelope = open_mail(caller_address, num);
// 存入信封的内容
deposit_contents(caller, envelope);
}
/// 将信封退回给发送者,但前提是该人已发送邮件
entry fun return_envelope(sender: &signer, receiver: address, num: u64) acquires MailboxRouter {
// 打开指定编号的信
let envelope = open_mail(receiver, num);
// 只有发送者可以取回信封的内容
assert!(envelope.sender == signer::address_of(sender), E_NOT_SENDER);
// 存入信封的内容
deposit_contents(sender, envelope);
}
/// 存入信封的内容
fun deposit_contents(receiver: &signer, envelope: Envelope) acquires MailboxRouter {
// 获取收件人的地址
let receiver_address = signer::address_of(receiver);
// 由于信封不可丢弃,它必须被分解为其组成部分以销毁
let Envelope {
sender: _, // 通过使用_代替名称来丢弃以后不需要的字段
note: _, // 丢弃备注,仅在交易中查看
coins,
legacy_tokens,
objects,
} = envelope;
// 如果有,存入硬币,这些不能被丢弃
if (option::is_some(&coins)) {
coin::deposit(receiver_address, option::destroy_some(coins))
} else {
option::destroy_none(coins);
};
// 存入所有旧版代币,这些不能被丢弃
vector::for_each(legacy_tokens, |legacy_token| {
token::deposit_token(receiver, legacy_token);
});
// 存入所有对象,如果错过这一步,对象将被困在路由器账户上
let mailbox_signer = get_mailbox_signer();
vector::for_each(objects, |obj| {
object::transfer(mailbox_signer, obj, receiver_address)
});
// 此时,所有部分都已转移到收件人
}
/// 移除你的邮箱,并取回存储 gas
entry fun destroy_mailbox(caller: &signer) acquires MailboxRouter {
// 获取调用者的地址
let receiver = signer::address_of(caller);
// 获取邮箱路由器的可变引用
let router = get_mailbox_router_mut();
let mailbox_id = MailboxId { receiver };
if (smart_table::contains(&router.mailboxes, mailbox_id)) {
// 检查邮箱是否为空
let is_empty = smart_vector::is_empty(&smart_table::borrow(&router.mailboxes, mailbox_id).mail);
assert!(is_empty, E_MAILBOX_NOT_EMPTY);
// 分解并销毁邮箱
let Mailbox {
mail,
} = smart_table::remove(&mut router.mailboxes, mailbox_id);
smart_vector::destroy_empty(mail);
}
}
/// 向另一个账户发送邮件
fun send_mail_internal(
caller: &signer,
receiver: address,
note: Option<String>,
coin_amount: u64,
objects: vector<Object<ObjectCore>>,
legacy_token_ids: vector<TokenId>
) acquires MailboxRouter {
// 将硬币放入信封
let coins = coin::withdraw<AptosCoin>(caller, coin_amount);
// 出于此演示的目的,我们将对象和代币的所有权转移到合约中,但通常这可以在没有中间人的情况下完成
// 将所有对象转移到合约
vector::for_each_ref(&objects, |obj| {
object::transfer(caller, *obj, @deploy_addr);
});
// 为信封检索所有代币
let legacy_tokens = vector::map(legacy_token_ids, |token_id| {
// 对于此演示,我们只考虑非同质化代币
token::withdraw_token(caller, token_id, 1)
});
let envelope = Envelope {
sender: signer::address_of(caller),
note,
objects,
legacy_tokens,
coins: option::some(coins),
};
// 检索邮箱,如果不存在则创建它
let router = get_mailbox_router_mut();
let mailbox_id = MailboxId {
receiver
};
if (!smart_table::contains(&router.mailboxes, mailbox_id)) {
smart_table::add(&mut router.mailboxes, mailbox_id, Mailbox {
mail: smart_vector::new(),
})
};
let mailbox = smart_table::borrow_mut(&mut router.mailboxes, mailbox_id);
// 将信封推到邮箱上
smart_vector::push_back(&mut mailbox.mail, envelope);
}
/// 打开索引的邮件
fun open_mail(receiver: address, num: u64): Envelope acquires MailboxRouter {
let mailbox = get_mailbox_mut(receiver);
// 检查num是否可以移除
let length = smart_vector::length(&mailbox.mail);
assert!(length > 0, E_MAILBOX_EMPTY); // 这是为了在没有邮件时给出友好的消息
assert!(num < length, E_OUT_OF_BOUNDS);
// 这将从智能向量中移除项目。
// 从gas的角度来看,移除最旧的邮件比最新的要昂贵得多,但它保留了顺序。
//
// 如果顺序不重要,可以使用smart_vector::swap_remove
smart_vector::remove(&mut mailbox.mail, num)
}
```plaintext
#[view]
/// 查看用户邮箱中的邮件
fun view_mail(receiver: address, num: u64): Envelope acquires MailboxRouter {
// 获取可变的邮箱引用
let mailbox = get_mailbox_mut(receiver);
// 移除并返回指定索引的邮件
smart_vector::remove(&mut mailbox.mail, num)
}
/// 获取邮箱路由器对象的可变引用
inline fun get_mailbox_router_mut(): &mut MailboxRouter {
// 使用deploy_addr和SEED创建邮箱路由器对象的地址
let mailbox_router_address = object::create_object_address(&@deploy_addr, SEED);
// 借用全局状态中的邮箱路由器对象
borrow_global_mut<MailboxRouter>(mailbox_router_address)
}
/// 获取用于移动对象的邮箱签名者
inline fun get_mailbox_signer(): &signer {
// 获取邮箱路由器对象的可变引用
let router = get_mailbox_router_mut();
// 生成用于扩展操作的签名者
&object::generate_signer_for_extending(&router.extend_ref)
}
/// 为用户检索可变的邮箱以进行读取
inline fun get_mailbox_mut(receiver: address): &mut Mailbox {
// 获取邮箱路由器对象的可变引用
let router = get_mailbox_router_mut();
// 创建邮箱ID
let mailbox_id = MailboxId {
receiver
};
// 确保邮箱存在
assert!(smart_table::contains(&router.mailboxes, mailbox_id), E_NO_MAILBOX_EXISTS);
// 借用邮箱路由器中的邮箱
smart_table::borrow_mut(&mut router.mailboxes, mailbox_id)
}
/// 以不可变方式检索用户的邮箱
inline fun get_mailbox(receiver: address): &Mailbox {
// 获取邮箱路由器对象的可变引用
let router = get_mailbox_router_mut();
// 创建邮箱ID
let mailbox_id = MailboxId {
receiver
};
// 确保邮箱存在
assert!(smart_table::contains(&router.mailboxes, mailbox_id), E_NO_MAILBOX_EXISTS);
// 借用邮箱路由器中的邮箱,以不可变方式
smart_table::borrow(&mut router.mailboxes, mailbox_id)
}
}