继续昨天的合约
1 定义变量
如果要更新值,Rust 希望你将变量声明为 mutable (mut):
let mut object = Self::get_object(object_id);
object.value = new_value;
>::insert(object_id, object);
2 检查权限
始终要对权限进行健全性检查
let owner = Self::owner_of(object_id).ok_or("No owner for this object")?;
ensure!(owner == sender, "You are not the owner");
ensure!(>::exists(object_id));
3 转让
swap and pop实现方法
let kitty_index = >::get(kitty_id);
if kitty_index != new_owned_kitty_count_from {
let last_kitty_id = >::get((from.clone(), new_owned_kitty_count_from));
>::insert((from.clone(), kitty_index), last_kitty_id);
>::insert(last_kitty_id, kitty_index);
}
4 购买
检查为空
let my_value = >::sa(0);
ensure!(my_value.is_zero(), "Value is not zero")
付款必须引用use support::traits::Currency;
as Currency<_>>::transfer(&from, &to, value)?;
建议你在调用 transfer() 之后直接执行 transfer_from() 函数。
// nothing after this line should fail
as Currency<_>>::transfer(&from, &to, value)?;
// but this function technically "could" fail
Self::transfer_from(owner.clone(), sender.clone(), kitty_id)?;
transfer_from() 中可能失败的检查:
kitty 不存在所有者
"from" 账户无法拥有相关的 kitty
从用户的 owned_kitty_count 中减去 kitty 时可能会出现下溢
将 kitty 添加到用户的 owned_kitty_count 时可能会出现溢出
Self::transfer_from(owner.clone(), sender.clone(), kitty_id)
.expect("`owner` is shown to own the kitty; \
`owner` must have greater than 0 kitties, so transfer cannot cause underflow; \
`all_kitty_count` shares the same type as `owned_kitty_count` \
and minting ensure there won't ever be more than `max()` kitties, \
which means transfer cannot cause an overflow; \
qed");
好恶心的语法啊...还要重新传输发现错误, 但这也是常见调bug内容, 虽然调bug不应该在开发阶段
5 培育
这就直接参考kitties的算法了...
6 Show me the code
话不多说了
use support::{decl_storage, decl_module, StorageValue, StorageMap,
dispatch::Result, ensure, decl_event, traits::Currency};
use system::ensure_signed;
use runtime_primitives::traits::{As, Hash, Zero};
use parity_codec::{Encode, Decode};
use rstd::cmp;
#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Kitty {
id: Hash,
dna: Hash,
price: Balance,
gen: u64,
}
pub trait Trait: balances::Trait {
type Event: From> + Into<::Event>;
}
decl_event!(
pub enum Event
where
::AccountId,
::Hash,
::Balance
{
Created(AccountId, Hash),
PriceSet(AccountId, Hash, Balance),
Transferred(AccountId, AccountId, Hash),
Bought(AccountId, AccountId, Hash, Balance),
}
);
decl_storage! {
trait Store for Module as KittyStorage {
Kitties get(kitty): map T::Hash => Kitty;
KittyOwner get(owner_of): map T::Hash => Option;
AllKittiesArray get(kitty_by_index): map u64 => T::Hash;
AllKittiesCount get(all_kitties_count): u64;
AllKittiesIndex: map T::Hash => u64;
OwnedKittiesArray get(kitty_of_owner_by_index): map (T::AccountId, u64) => T::Hash;
OwnedKittiesCount get(owned_kitty_count): map T::AccountId => u64;
OwnedKittiesIndex: map T::Hash => u64;
Nonce: u64;
}
}
decl_module! {
pub struct Module for enum Call where origin: T::Origin {
fn deposit_event() = default;
fn create_kitty(origin) -> Result {
let sender = ensure_signed(origin)?;
let nonce = >::get();
let random_hash = (>::random_seed(), &sender, nonce)
.using_encoded(::Hashing::hash);
let new_kitty = Kitty {
id: random_hash,
dna: random_hash,
price: >::sa(0),
gen: 0,
};
Self::mint(sender, random_hash, new_kitty)?;
>::mutate(|n| *n += 1);
Ok(())
}
fn set_price(origin, kitty_id: T::Hash, new_price: T::Balance) -> Result {
let sender = ensure_signed(origin)?;
ensure!(>::exists(kitty_id), "This cat does not exist");
let owner = Self::owner_of(kitty_id).ok_or("No owner for this kitty")?;
ensure!(owner == sender, "You do not own this cat");
let mut kitty = Self::kitty(kitty_id);
kitty.price = new_price;
>::insert(kitty_id, kitty);
Self::deposit_event(RawEvent::PriceSet(sender, kitty_id, new_price));
Ok(())
}
fn transfer(origin, to: T::AccountId, kitty_id: T::Hash) -> Result {
let sender = ensure_signed(origin)?;
let owner = Self::owner_of(kitty_id).ok_or("No owner for this kitty")?;
ensure!(owner == sender, "You do not own this kitty");
Self::transfer_from(sender, to, kitty_id)?;
Ok(())
}
fn buy_kitty(origin, kitty_id: T::Hash, max_price: T::Balance) -> Result {
let sender = ensure_signed(origin)?;
ensure!(>::exists(kitty_id), "This cat does not exist");
let owner = Self::owner_of(kitty_id).ok_or("No owner for this kitty")?;
ensure!(owner != sender, "You can't buy your own cat");
let mut kitty = Self::kitty(kitty_id);
let kitty_price = kitty.price;
ensure!(!kitty_price.is_zero(), "The cat you want to buy is not for sale");
ensure!(kitty_price <= max_price, "The cat you want to buy costs more than your max price");
as Currency<_>>::transfer(&sender, &owner, kitty_price)?;
Self::transfer_from(owner.clone(), sender.clone(), kitty_id)
.expect("`owner` is shown to own the kitty; \
`owner` must have greater than 0 kitties, so transfer cannot cause underflow; \
`all_kitty_count` shares the same type as `owned_kitty_count` \
and minting ensure there won't ever be more than `max()` kitties, \
which means transfer cannot cause an overflow; \
qed");
kitty.price = >::sa(0);
>::insert(kitty_id, kitty);
Self::deposit_event(RawEvent::Bought(sender, owner, kitty_id, kitty_price));
Ok(())
}
fn breed_kitty(origin, kitty_id_1: T::Hash, kitty_id_2: T::Hash) -> Result{
let sender = ensure_signed(origin)?;
ensure!(>::exists(kitty_id_1), "This cat 1 does not exist");
ensure!(>::exists(kitty_id_2), "This cat 2 does not exist");
let nonce = >::get();
let random_hash = (>::random_seed(), &sender, nonce)
.using_encoded(::Hashing::hash);
let kitty_1 = Self::kitty(kitty_id_1);
let kitty_2 = Self::kitty(kitty_id_2);
let mut final_dna = kitty_1.dna;
for (i, (dna_2_element, r)) in kitty_2.dna.as_ref().iter().zip(random_hash.as_ref().iter()).enumerate() {
if r % 2 == 0 {
final_dna.as_mut()[i] = *dna_2_element;
}
}
let new_kitty = Kitty {
id: random_hash,
dna: final_dna,
price: >::sa(0),
gen: cmp::max(kitty_1.gen, kitty_2.gen) + 1,
};
Self::mint(sender, random_hash, new_kitty)?;
>::mutate(|n| *n += 1);
Ok(())
}
}
}
impl Module {
fn mint(to: T::AccountId, kitty_id: T::Hash, new_kitty: Kitty) -> Result {
ensure!(!>::exists(kitty_id), "Kitty already exists");
let owned_kitty_count = Self::owned_kitty_count(&to);
let new_owned_kitty_count = owned_kitty_count.checked_add(1)
.ok_or("Overflow adding a new kitty to account balance")?;
let all_kitties_count = Self::all_kitties_count();
let new_all_kitties_count = all_kitties_count.checked_add(1)
.ok_or("Overflow adding a new kitty to total supply")?;
>::insert(kitty_id, new_kitty);
>::insert(kitty_id, &to);
>::insert(all_kitties_count, kitty_id);
>::put(new_all_kitties_count);
>::insert(kitty_id, all_kitties_count);
>::insert((to.clone(), owned_kitty_count), kitty_id);
>::insert(&to, new_owned_kitty_count);
>::insert(kitty_id, owned_kitty_count);
Self::deposit_event(RawEvent::Created(to, kitty_id));
Ok(())
}
fn transfer_from(from: T::AccountId, to: T::AccountId, kitty_id: T::Hash) -> Result {
let owner = Self::owner_of(kitty_id).ok_or("No owner for this kitty")?;
ensure!(owner == from, "'from' account does not own this kitty");
let owned_kitty_count_from = Self::owned_kitty_count(&from);
let owned_kitty_count_to = Self::owned_kitty_count(&to);
let new_owned_kitty_count_to = owned_kitty_count_to.checked_add(1)
.ok_or("Transfer causes overflow of 'to' kitty balance")?;
let new_owned_kitty_count_from = owned_kitty_count_from.checked_sub(1)
.ok_or("Transfer causes underflow of 'from' kitty balance")?;
let kitty_index = >::get(kitty_id);
if kitty_index != new_owned_kitty_count_from {
let last_kitty_id = >::get((from.clone(), new_owned_kitty_count_from));
>::insert((from.clone(), kitty_index), last_kitty_id);
>::insert(last_kitty_id, kitty_index);
}
>::insert(&kitty_id, &to);
>::insert(kitty_id, owned_kitty_count_to);
>::remove((from.clone(), new_owned_kitty_count_from));
>::insert((to.clone(), owned_kitty_count_to), kitty_id);
>::insert(&from, new_owned_kitty_count_from);
>::insert(&to, new_owned_kitty_count_to);
Self::deposit_event(RawEvent::Transferred(from, to, kitty_id));
Ok(())
}
}
7 测试
你应该运行以下手动测试:
使用 token 为多个用户提供资金,以便他们都可以参与
让每个用户创建多个 kitties
通过使用正确和错误的所有者,尝试将 kitty 从一个用户转移给另一个用户
通过使用正确和错误的所有者,尝试设置 kitty 的价格
使用所有者和其他用户购买 kitty
使用不足的资金购买 kitty
高价购买 kitty,确保余额被适当减少
培育 kitty,检查新 DNA 是新旧混合物
完成所有这些操作后,确认所有用户都具有正确数量的 kitty,确认 kitty 总数正确,并且所有其他存储变量都被正确表示
8 改进
其实合约还是很简单,以下有些改进方法:
在调用 breed_kitty() 期间追踪 kitty 的父母。也许只是一个 event...?
限制 create_kitty() 可以创建的 kitties 数量,添加价格曲线以使得创建每个新的 kitty 成本更高。
培育出新 kitty 后,当有人收到这只新 kitty,必须支付一定的费用。确保资金正确发送给每个用户。确保每个人都可以使用自己的 kitty 进行培育。
添加一个 kitty "fight",使得两个 kitties 可以参与基于随机数和一些加权统计的游戏。胜利的 kitty 的数据有所增加,这使他们有更好的机会赢得下一轮比赛。
添加基因突变算法到 kitty 培育中,这将引入父母 kitties 都没有的特征。
为那些不想简单地设定定价以购买 kitty 的所有者推出拍卖系统。如果没有人投标则一段时间后拍卖结束。