一:功能简介
- 任何人都可以发布彩票,自定义售价、奖金、终止日期等基础信息。
- 任何人都可以购买彩票并获得对应的购买凭证。
- 当彩票售罄或者超过售卖日期时,任何人都可以调用开奖函数,获奖信息将公示。
- 获奖者可凭票根兑奖,奖金将全额打入获奖者账户,与此同时,此彩票的收入将一分为二, 1 % \text 1\% 1% 将打入该彩票系统发布者账户,剩下的将打入该彩票发布者账户。
- 若中奖者长期不兑奖,提供回收奖金功能,奖金将打回该彩票发布者账户,而彩票收入依旧按照上述比例分配。
二:结构设计
2.1 彩票系统
维护发布的所有彩票信息,以及系统发布者的收入。
public struct LotterySystem has key {
id: UID,
lotteries: Table<ID, Lottery>,
ls_income: Balance<SUI>,
}
2.2 彩票
维护彩票信息,包括剩余可售数量、终止日期、单价、总奖金数等。
public struct Lottery has store {
lottery_name: String,
price: u64,
total_amount: u64,
remaining_amount: u64,
end_epoch: u64,
bonus: Balance<SUI>,
announcement: bool,
winner_code: vector<u8>,
income: Balance<SUI>,
creater: address,
}
2.3 购买凭证
存储有所购买的彩票的唯一编码,以及对应的兑奖码。
public struct LotteryStub has key {
id: UID,
lottery_id: ID,
code: vector<u8>,
}
2.4 一次性见证与事件
为了让系统管理员提现更安全,使用 O n e \mathit {One} One- T i m e \mathit {Time} Time- W i t n e s s \mathit {Witness} Witness 来生成唯一的Publisher
并转移给他/她。
public struct SELF_SERVICE_LOTTERY has drop {}
为了让系统运行过程中的一些流程更加公开透明,在关键节点将触发事件信息进行公示。
public struct NewLotteryEvent has copy, drop {
lottery_id: ID,
lottery_name: String,
price: u64,
total_amount: u64,
end_epoch: u64,
bonus_value: u64,
}
public struct WinEvent has copy, drop {
lottery_name: String,
winner_code: vector<u8>,
}
public struct EndEvent has copy, drop {
hint: String,
}
三:功能实现
3.1 初始化
生成唯一的Publisher
,同时构造并共享LotterySystem
。
fun init(otw: SELF_SERVICE_LOTTERY, ctx: &mut TxContext) {
// generate Publisher and transfer it
package::claim_and_keep(otw, ctx);
// generate LotterySystem and share it
transfer::share_object(LotterySystem {
id: object::new(ctx),
lotteries: table::new<ID, Lottery>(ctx),
ls_income: balance::zero(),
});
}
3.2 系统构建者提现
Publisher
只用作身份确认,所以用_
来表示函数中并不会实际使用。
当且仅当收入不为零时,将所有收入转入个人账户。
entry fun withdraw(_: &Publisher, lottery_system: &mut LotterySystem, ctx: &mut TxContext) {
// check coin value
let amount = lottery_system.ls_income.value();
assert!(amount > 0, ENotIncome);
// withdraw income
transfer::public_transfer(coin::from_balance(lottery_system.ls_income.split(amount), ctx), ctx.sender());
}
3.3 创建彩票
根据是否自定义可售期限,提供了两个方法:create_lottery
和create_lottery_with_epoch
,它们都将接受彩票的基本信息来生成一套可供售卖的彩票,唯一的区别是可售期限是否为默认。一旦创建成功,将触发事件信息公示这一款全新的彩票的基础信息。
entry fun create_lottery(lottery_system: &mut LotterySystem, lottery_name: String, price: u64, total_amount: u64, bonus: Coin<SUI>, ctx: &mut TxContext) {
// default continuous 15 epoch
create_lottery_with_epoch(lottery_system, lottery_name, price, total_amount, 15, bonus, ctx);
}
entry fun create_lottery_with_epoch(lottery_system: &mut LotterySystem, lottery_name: String, price: u64, total_amount: u64, continuous_epoch: u64, bonus: Coin<SUI>, ctx: &mut TxContext) {
// check bonus value
assert!(bonus.value() > 0, ENotBonus);
// generate Lottery ID
let lottery_id = object::id_from_address(ctx.fresh_object_address());
// generate Lottery
let lottery = Lottery {
lottery_name,
price,
total_amount,
remaining_amount: total_amount,
end_epoch: ctx.epoch() + continuous_epoch,
bonus: bonus.into_balance(),
announcement: false,
winner_code: vector<u8>[],
income: balance::zero(),
creater: ctx.sender(),
};
// emit event
event::emit(NewLotteryEvent {
lottery_id,
lottery_name,
price,
total_amount,
end_epoch: ctx.epoch() + continuous_epoch,
bonus_value: lottery.bonus.value(),
});
// store it
lottery_system.lotteries.add(lottery_id, lottery);
}
3.4 赎回奖金
当且仅当该套彩票一张未售,或者开奖后超过了兑奖期限时,可以通过此方法赎回奖金。
entry fun fedeem_bonus(lottery_system: &mut LotterySystem, lottery_id: ID, ctx: &mut TxContext) {
// check lottery_id
assert!(lottery_system.lotteries.contains(lottery_id), ENotCorrectLotteryID);
// get Lottery
let mut lottery = lottery_system.lotteries.remove(lottery_id);
// check sales status and epoch
assert!(lottery.total_amount == lottery.remaining_amount || ctx.epoch() > lottery.end_epoch + 15, ENotFedeem);
calculate_fee(lottery_system, &mut lottery.income);
destroy_lottery(lottery, ctx);
}
fun calculate_fee(lottery_system: &mut LotterySystem, income: &mut Balance<SUI>) {
// calc fee amount(1%)
let amount = income.value() / 100;
if (amount > 0) {
lottery_system.ls_income.join(income.split(amount));
};
}
fun destroy_lottery(lottery: Lottery, ctx: &mut TxContext) {
let Lottery {
lottery_name: _,
price: _,
total_amount: _,
remaining_amount: _,
end_epoch: _,
bonus,
announcement: _,
mut winner_code,
mut income,
creater,
} = lottery;
// destroy winner_code
while (winner_code.length() > 0) {
winner_code.pop_back();
};
winner_code.destroy_empty();
// destroy bonus
if (bonus.value() > 0) {
income.join(bonus);
} else {
bonus.destroy_zero();
};
// transfer income
transfer::public_transfer(coin::from_balance(income, ctx), creater);
}
其中,calculate_fee
的功能是计算将提供给系统管理者多少分成,destroy_lottery
是摧毁一套彩票,将里面所包含的 S U I \mathit {SUI} SUI 转入个人账户。
在转账过程中,bonus
和income
不可能都为零,而且同时支持赎回奖金以及中奖者兑奖后的彩票收入提现。
3.5 开奖公告
当彩票售罄时将自动触发开奖,如果超过了购买日期,所有人也可以手动触发。
随机过程使用了hmac_sha3_256
来生成一系列数字,并对这一系列数字进行运算得到第几个购买者为中奖者,再用同样的方法生成该顾客手上的兑奖码进行公示。
entry fun announcement(lottery_system: &mut LotterySystem, lottery_id: ID, ctx: &mut TxContext) {
// check lottery_id
assert!(lottery_system.lotteries.contains(lottery_id), ENotCorrectLotteryID);
// get Lotter